Merge branch 'develop' into ci/address-sanitiser_on_debug_builds
This commit is contained in:
commit
7f138e3538
26
.github/workflows/update-flake.yml
vendored
Normal file
26
.github/workflows/update-flake.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Update Flake Lockfile
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run weekly on sunday
|
||||
- cron: "0 0 * * 0"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-flake:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
|
||||
- uses: DeterminateSystems/update-flake-lock@v19
|
||||
with:
|
||||
commit-msg: "chore(nix): update lockfile"
|
||||
pr-title: "chore(nix): update lockfile"
|
||||
pr-labels: |
|
||||
Linux
|
||||
simple change
|
48
flake.lock
generated
48
flake.lock
generated
@ -21,11 +21,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683560683,
|
||||
"narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
|
||||
"lastModified": 1688254665,
|
||||
"narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "006c75898cf814ef9497252b022e91c946ba8e17",
|
||||
"rev": "267149c58a14d15f7f81b4d737308421de9d7152",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -35,12 +35,15 @@
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -88,11 +91,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1685012353,
|
||||
"narHash": "sha256-U3oOge4cHnav8OLGdRVhL45xoRj4Ppd+It6nPC9nNIU=",
|
||||
"lastModified": 1688221086,
|
||||
"narHash": "sha256-cdW6qUL71cNWhHCpMPOJjlw0wzSRP0pVlRn2vqX/VVg=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "aeb75dba965e790de427b73315d5addf91a54955",
|
||||
"rev": "cd99c2b3c9f160cd004318e0697f90bbd5960825",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -105,11 +108,11 @@
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1682879489,
|
||||
"narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
|
||||
"lastModified": 1688049487,
|
||||
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
|
||||
"rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -135,11 +138,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1684842236,
|
||||
"narHash": "sha256-rYWsIXHvNhVQ15RQlBUv67W3YnM+Pd+DuXGMvCBq2IE=",
|
||||
"lastModified": 1688386108,
|
||||
"narHash": "sha256-Vffto9QaVonzYAcPlAzd0soqWYpPpKk60dfNLSIXcFA=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "61e567d6497bc9556f391faebe5e410e6623217f",
|
||||
"rev": "42587d3414d1747999a5f71e92a83cf6547b62da",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -156,6 +159,21 @@
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
@ -687,8 +687,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
m_settings->reset("PastebinCustomAPIBase");
|
||||
}
|
||||
}
|
||||
// meta URL
|
||||
m_settings->registerSetting("MetaURLOverride", "");
|
||||
{
|
||||
// Meta URL
|
||||
m_settings->registerSetting("MetaURLOverride", "");
|
||||
|
||||
QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
|
||||
|
||||
// get rid of invalid meta urls
|
||||
if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https")
|
||||
m_settings->reset("MetaURLOverride");
|
||||
}
|
||||
|
||||
m_settings->registerSetting("CloseAfterLaunch", false);
|
||||
m_settings->registerSetting("QuitAfterGameStop", false);
|
||||
|
@ -36,6 +36,7 @@
|
||||
*/
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <QPair>
|
||||
|
||||
#include "BuildConfig.h"
|
||||
|
||||
@ -102,7 +103,7 @@ namespace fs = ghc::filesystem;
|
||||
#include <linux/fs.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#include <sys/attr.h>
|
||||
#include <sys/clonefile.h>
|
||||
#elif defined(Q_OS_WIN)
|
||||
@ -246,6 +247,7 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
{
|
||||
using copy_opts = fs::copy_options;
|
||||
m_copied = 0; // reset counter
|
||||
m_failedPaths.clear();
|
||||
|
||||
// NOTE always deep copy on windows. the alternatives are too messy.
|
||||
#if defined Q_OS_WIN32
|
||||
@ -277,6 +279,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
m_failedPaths.append(dst_path);
|
||||
emit copyFailed(relative_dst_path);
|
||||
return;
|
||||
}
|
||||
m_copied++;
|
||||
emit fileCopied(relative_dst_path);
|
||||
@ -1077,6 +1082,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
|
||||
}
|
||||
|
||||
m_cloned = 0; // reset counter
|
||||
m_failedClones.clear();
|
||||
|
||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||
@ -1097,6 +1103,9 @@ bool clone::operator()(const QString& offset, bool dryRun)
|
||||
qDebug() << "Failed to clone files: error" << err.value() << "message" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
m_failedClones.append(qMakePair(src_path, dst_path));
|
||||
emit cloneFailed(src_path, dst_path);
|
||||
return;
|
||||
}
|
||||
m_cloned++;
|
||||
emit fileCloned(src_path, dst_path);
|
||||
@ -1156,7 +1165,7 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
|
||||
if (!macos_bsd_clonefile(src_path, dst_path, ec)) {
|
||||
qDebug() << "failed macos_bsd_clonefile:";
|
||||
@ -1385,7 +1394,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_OPENBSD)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
|
||||
bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
|
||||
{
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include <system_error>
|
||||
|
||||
#include <QDir>
|
||||
#include <QPair>
|
||||
#include <QFlags>
|
||||
#include <QLocalServer>
|
||||
#include <QObject>
|
||||
@ -112,9 +113,12 @@ class copy : public QObject {
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
int totalCopied() { return m_copied; }
|
||||
int totalFailed() { return m_failedPaths.length(); }
|
||||
QStringList failed() { return m_failedPaths; }
|
||||
|
||||
signals:
|
||||
void fileCopied(const QString& relativeName);
|
||||
void copyFailed(const QString& relativeName);
|
||||
// TODO: maybe add a "shouldCopy" signal in the future?
|
||||
|
||||
private:
|
||||
@ -127,6 +131,7 @@ class copy : public QObject {
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
int m_copied;
|
||||
QStringList m_failedPaths;
|
||||
};
|
||||
|
||||
struct LinkPair {
|
||||
@ -471,6 +476,9 @@ class clone : public QObject {
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
int totalCloned() { return m_cloned; }
|
||||
int totalFailed() { return m_failedClones.length(); }
|
||||
|
||||
QList<QPair<QString, QString>> failed() { return m_failedClones; }
|
||||
|
||||
signals:
|
||||
void fileCloned(const QString& src, const QString& dst);
|
||||
@ -485,6 +493,7 @@ class clone : public QObject {
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
int m_cloned;
|
||||
QList<QPair<QString, QString>> m_failedClones;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -187,8 +187,8 @@ void LaunchController::login() {
|
||||
switch(m_accountToUse->accountState()) {
|
||||
case AccountState::Offline: {
|
||||
m_session->wants_online = false;
|
||||
// NOTE: fallthrough is intentional
|
||||
}
|
||||
/* fallthrough */
|
||||
case AccountState::Online: {
|
||||
if(!m_session->wants_online) {
|
||||
// we ask the user for a player name
|
||||
@ -267,8 +267,8 @@ void LaunchController::login() {
|
||||
// This means some sort of soft error that we can fix with a refresh ... so let's refresh.
|
||||
case AccountState::Unchecked: {
|
||||
m_accountToUse->refresh();
|
||||
// NOTE: fallthrough intentional
|
||||
}
|
||||
/* fallthrough */
|
||||
case AccountState::Working: {
|
||||
// refresh is in progress, we need to wait for it to finish to proceed.
|
||||
ProgressDialog progDialog(m_parentWidget);
|
||||
|
@ -193,31 +193,21 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
switch(column)
|
||||
if(column == Name && hasRecommended)
|
||||
{
|
||||
case Name:
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
if(hasRecommended)
|
||||
return tr("Recommended");
|
||||
} else if(hasLatest) {
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
return tr("Recommended");
|
||||
}
|
||||
else if(hasLatest)
|
||||
{
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
return tr("Latest");
|
||||
}
|
||||
}
|
||||
return tr("Latest");
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole);
|
||||
}
|
||||
} else {
|
||||
return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole);
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
|
@ -45,10 +45,10 @@ QVariant Index::data(const QModelIndex &index, int role) const
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case 0: return list->humanReadable();
|
||||
default: break;
|
||||
if (index.column() == 0) {
|
||||
return list->humanReadable();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
case UidRole: return list->uid();
|
||||
case NameRole: return list->name();
|
||||
|
@ -328,6 +328,9 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
default: {
|
||||
return tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,11 +357,12 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue(account);
|
||||
|
||||
case Qt::CheckStateRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
if (index.column() == ProfileNameColumn) {
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
|
@ -273,6 +273,7 @@ void Yggdrasil::processReply() {
|
||||
AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
changeState(
|
||||
|
@ -74,6 +74,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
@ -83,6 +84,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
|
||||
return { 1, type == SortType::PACK_FORMAT };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
|
@ -91,6 +91,7 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::VERSION: {
|
||||
auto this_ver = Version(version());
|
||||
@ -99,11 +100,13 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
return { 1, type == SortType::VERSION };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::VERSION };
|
||||
break;
|
||||
}
|
||||
case SortType::PROVIDER: {
|
||||
auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::PROVIDER };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
@ -166,6 +169,13 @@ auto Mod::homeurl() const -> QString
|
||||
return details().homeurl;
|
||||
}
|
||||
|
||||
auto Mod::metaurl() const -> QString
|
||||
{
|
||||
if (metadata() == nullptr)
|
||||
return homeurl();
|
||||
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
|
||||
}
|
||||
|
||||
auto Mod::description() const -> QString
|
||||
{
|
||||
return details().description;
|
||||
|
@ -70,6 +70,7 @@ public:
|
||||
auto provider() const -> std::optional<QString>;
|
||||
auto licenses() const -> const QList<ModLicense>&;
|
||||
auto issueTracker() const -> QString;
|
||||
auto metaurl() const -> QString;
|
||||
|
||||
/** Get the intneral path to the mod's icon file*/
|
||||
QString iconPath() const { return m_local_details.icon_file; };
|
||||
|
@ -71,6 +71,7 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
return { 1, type == SortType::ENABLED };
|
||||
if (!enabled() && other.enabled())
|
||||
return { -1, type == SortType::ENABLED };
|
||||
break;
|
||||
case SortType::NAME: {
|
||||
QString this_name{ name() };
|
||||
QString other_name{ other.name() };
|
||||
@ -81,12 +82,14 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::NAME };
|
||||
break;
|
||||
}
|
||||
case SortType::DATE:
|
||||
if (dateTimeChanged() > other.dateTimeChanged())
|
||||
return { 1, type == SortType::DATE };
|
||||
if (dateTimeChanged() < other.dateTimeChanged())
|
||||
return { -1, type == SortType::DATE };
|
||||
break;
|
||||
}
|
||||
|
||||
return { 0, false };
|
||||
|
@ -102,6 +102,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
@ -111,6 +112,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
|
||||
return { 1, type == SortType::PACK_FORMAT };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
|
@ -70,11 +70,17 @@ auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString t
|
||||
}
|
||||
|
||||
QCryptographicHash hash(algo);
|
||||
if(!hash.addData(device))
|
||||
if (!hash.addData(device))
|
||||
qCritical() << "Failed to read JAR to create hash!";
|
||||
|
||||
Q_ASSERT(hash.result().length() == hash.hashLength(algo));
|
||||
return { hash.result().toHex() };
|
||||
}
|
||||
|
||||
QString getMetaURL(ResourceProvider provider, QVariant projectID)
|
||||
{
|
||||
return ((provider == ModPlatform::ResourceProvider::FLAME) ? "https://www.curseforge.com/projects/" : "https://modrinth.com/mod/") +
|
||||
projectID.toString();
|
||||
}
|
||||
|
||||
} // namespace ModPlatform
|
||||
|
@ -144,6 +144,7 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
|
||||
{ "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
|
||||
{ "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
|
||||
};
|
||||
QString getMetaURL(ResourceProvider provider, QVariant projectID);
|
||||
|
||||
} // namespace ModPlatform
|
||||
|
||||
|
@ -470,8 +470,9 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
switch (result.type) {
|
||||
case Flame::File::Type::Folder: {
|
||||
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
|
||||
// fall-through intentional, we treat these as plain old mods and dump them wherever.
|
||||
// fallthrough intentional, we treat these as plain old mods and dump them wherever.
|
||||
}
|
||||
/* fallthrough */
|
||||
case Flame::File::Type::SingleFile:
|
||||
case Flame::File::Type::Mod: {
|
||||
if (!result.url.isEmpty()) {
|
||||
|
@ -454,6 +454,7 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const
|
||||
return QString("%1%").arg(lang.percentTranslated(), 3, 'f', 1);
|
||||
}
|
||||
}
|
||||
qWarning("TranslationModel::data not implemented when role is DisplayRole");
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
|
@ -927,21 +927,8 @@ void MainWindow::onCatToggled(bool state)
|
||||
|
||||
void MainWindow::setCatBackground(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
view->setStyleSheet(QString(R"(
|
||||
InstanceView
|
||||
{
|
||||
background-image: url(:/backgrounds/%1);
|
||||
background-attachment: fixed;
|
||||
background-clip: padding;
|
||||
background-position: bottom right;
|
||||
background-repeat: none;
|
||||
background-color:palette(base);
|
||||
})")
|
||||
.arg(ThemeManager::getCatImage()));
|
||||
} else {
|
||||
view->setStyleSheet(QString());
|
||||
}
|
||||
view->setPaintCat(enabled);
|
||||
view->viewport()->repaint();
|
||||
}
|
||||
|
||||
void MainWindow::runModalTask(Task *task)
|
||||
|
@ -34,6 +34,7 @@
|
||||
*/
|
||||
|
||||
#include "ProgressDialog.h"
|
||||
#include <QPoint>
|
||||
#include "ui_ProgressDialog.h"
|
||||
|
||||
#include <limits>
|
||||
@ -68,6 +69,7 @@ ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Pr
|
||||
setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true);
|
||||
setSkipButton(false);
|
||||
changeProgress(0, 100);
|
||||
updateSize();
|
||||
}
|
||||
|
||||
void ProgressDialog::setSkipButton(bool present, QString label)
|
||||
@ -96,22 +98,29 @@ ProgressDialog::~ProgressDialog()
|
||||
void ProgressDialog::updateSize()
|
||||
{
|
||||
QSize lastSize = this->size();
|
||||
QSize qSize = QSize(480, minimumSizeHint().height());
|
||||
QPoint lastPos = this->pos();
|
||||
int minHeight = ui->globalStatusDetailsLabel->minimumSize().height() + (ui->verticalLayout->spacing() * 2);
|
||||
minHeight += ui->globalProgressBar->minimumSize().height() + ui->verticalLayout->spacing();
|
||||
if (!ui->taskProgressScrollArea->isHidden())
|
||||
minHeight += ui->taskProgressScrollArea->minimumSizeHint().height() + ui->verticalLayout->spacing();
|
||||
if (ui->skipButton->isVisible())
|
||||
minHeight += ui->skipButton->height() + ui->verticalLayout->spacing();
|
||||
minHeight = std::max(minHeight, 60);
|
||||
QSize minSize = QSize(480, minHeight);
|
||||
|
||||
setMinimumSize(minSize);
|
||||
adjustSize();
|
||||
|
||||
QSize newSize = this->size();
|
||||
// if the current window is too small
|
||||
if ((lastSize != qSize) && (lastSize.height() < qSize.height()))
|
||||
if ((lastSize != newSize) && (lastSize.height() < newSize.height()))
|
||||
{
|
||||
resize(qSize);
|
||||
|
||||
// keep the dialog in the center after a resize
|
||||
this->move(
|
||||
this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2,
|
||||
this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2
|
||||
);
|
||||
QSize sizeDiff = lastSize - newSize; // last size was smaller, the results should be negative
|
||||
// center on old position after resize
|
||||
QPoint newPos(lastPos.x() + (sizeDiff.width() / 2), lastPos.y() + (sizeDiff.height() / 2));
|
||||
this->move(newPos);
|
||||
}
|
||||
|
||||
setMinimumSize(qSize);
|
||||
|
||||
}
|
||||
|
||||
int ProgressDialog::execWithTask(Task* task)
|
||||
@ -201,7 +210,9 @@ void ProgressDialog::onTaskSucceeded()
|
||||
void ProgressDialog::changeStatus(const QString& status)
|
||||
{
|
||||
ui->globalStatusLabel->setText(task->getStatus());
|
||||
ui->globalStatusLabel->adjustSize();
|
||||
ui->globalStatusDetailsLabel->setText(task->getDetails());
|
||||
ui->globalStatusDetailsLabel->adjustSize();
|
||||
|
||||
updateSize();
|
||||
}
|
||||
|
@ -248,8 +248,8 @@ bool AccessibleInstanceView::selectColumn(int column)
|
||||
if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) {
|
||||
return false;
|
||||
}
|
||||
// fallthrough intentional
|
||||
}
|
||||
/* fallthrough */
|
||||
case QAbstractItemView::ContiguousSelection: {
|
||||
if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
|
||||
view()->clearSelection();
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include <QAccessible>
|
||||
|
||||
#include "VisualGroup.h"
|
||||
#include "ui/themes/ThemeManager.h"
|
||||
#include <QDebug>
|
||||
|
||||
#include <Application.h>
|
||||
@ -73,6 +74,7 @@ InstanceView::InstanceView(QWidget *parent)
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
setAcceptDrops(true);
|
||||
setAutoScroll(true);
|
||||
setPaintCat(APPLICATION->settings()->get("TheCat").toBool());
|
||||
}
|
||||
|
||||
InstanceView::~InstanceView()
|
||||
@ -498,12 +500,34 @@ void InstanceView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceView::paintEvent(QPaintEvent *event)
|
||||
void InstanceView::setPaintCat(bool visible)
|
||||
{
|
||||
m_catVisible = visible;
|
||||
if (visible)
|
||||
m_catPixmap.load(QString(":/backgrounds/%1").arg(ThemeManager::getCatImage()));
|
||||
else
|
||||
m_catPixmap = QPixmap();
|
||||
}
|
||||
|
||||
void InstanceView::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
executeDelayedItemsLayout();
|
||||
|
||||
QPainter painter(this->viewport());
|
||||
|
||||
if (m_catVisible) {
|
||||
int widWidth = this->viewport()->width();
|
||||
int widHeight = this->viewport()->height();
|
||||
if (m_catPixmap.width() < widWidth)
|
||||
widWidth = m_catPixmap.width();
|
||||
if (m_catPixmap.height() < widHeight)
|
||||
widHeight = m_catPixmap.height();
|
||||
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio);
|
||||
QRect rectOfPixmap = pixmap.rect();
|
||||
rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight());
|
||||
painter.drawPixmap(rectOfPixmap.topLeft(), pixmap);
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QStyleOptionViewItem option;
|
||||
initViewItemOption(&option);
|
||||
|
@ -85,10 +85,8 @@ public:
|
||||
|
||||
virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override;
|
||||
|
||||
int spacing() const
|
||||
{
|
||||
return m_spacing;
|
||||
};
|
||||
int spacing() const { return m_spacing; };
|
||||
void setPaintCat(bool visible);
|
||||
|
||||
public slots:
|
||||
virtual void updateGeometries() override;
|
||||
@ -139,6 +137,8 @@ private:
|
||||
int m_currentItemsPerRow = -1;
|
||||
int m_currentCursorColumn= -1;
|
||||
mutable QCache<int, QRect> geometryCache;
|
||||
bool m_catVisible = false;
|
||||
QPixmap m_catPixmap;
|
||||
|
||||
// point where the currently active mouse action started in geometry coordinates
|
||||
QPoint m_pressedPosition;
|
||||
|
@ -81,6 +81,8 @@ APIPage::APIPage(QWidget *parent) :
|
||||
connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLPlaceholder);
|
||||
// This function needs to be called even when the ComboBox's index is still in its default state.
|
||||
updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());
|
||||
// NOTE: this allows http://, but we replace that with https later anyway
|
||||
ui->metaURL->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->metaURL));
|
||||
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
|
||||
ui->msaClientID->setValidator(new QRegularExpressionValidator(validMSAClientID, ui->msaClientID));
|
||||
ui->flameKey->setValidator(new QRegularExpressionValidator(validFlameKey, ui->flameKey));
|
||||
@ -163,7 +165,7 @@ void APIPage::applySettings()
|
||||
|
||||
QString msaClientID = ui->msaClientID->text();
|
||||
s->set("MSAClientIDOverride", msaClientID);
|
||||
QUrl metaURL = ui->metaURL->text();
|
||||
QUrl metaURL(ui->metaURL->text());
|
||||
// Add required trailing slash
|
||||
if (!metaURL.isEmpty() && !metaURL.path().endsWith('/'))
|
||||
{
|
||||
|
@ -157,6 +157,17 @@
|
||||
<string>Try to check or update all selected resources (all resources if none are selected)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionVisitItemPage">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Visit mod's page</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Go to mods home page</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <algorithm>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
@ -60,6 +61,7 @@
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
|
||||
#include "Version.h"
|
||||
@ -86,12 +88,30 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
|
||||
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
|
||||
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
|
||||
ui->actionsToolbar->insertActionAfter(ui->actionViewFolder, ui->actionVisitItemPage);
|
||||
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
|
||||
|
||||
auto check_allow_update = [this] {
|
||||
return (!m_instance || !m_instance->isRunning()) && (ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
|
||||
};
|
||||
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
[this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
|
||||
ui->actionUpdateItem->setEnabled(check_allow_update());
|
||||
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||
auto mods_list = m_model->selectedMods(selection);
|
||||
auto selected = std::count_if(mods_list.cbegin(), mods_list.cend(),
|
||||
[](Mod* v) { return v->metadata() != nullptr || v->homeurl().size() != 0; });
|
||||
if (selected <= 1) {
|
||||
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
|
||||
} else {
|
||||
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
|
||||
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
|
||||
}
|
||||
ui->actionVisitItemPage->setEnabled(selected != 0);
|
||||
});
|
||||
|
||||
connect(mods.get(), &ModFolderModel::rowsInserted, this,
|
||||
[this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); });
|
||||
@ -133,7 +153,7 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelI
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModFolderPage::removeItems(const QItemSelection &selection)
|
||||
void ModFolderPage::removeItems(const QItemSelection& selection)
|
||||
{
|
||||
m_model->deleteMods(selection.indexes());
|
||||
}
|
||||
@ -207,8 +227,7 @@ void ModFolderPage::updateMods()
|
||||
message = tr("All selected mods are up-to-date! :)");
|
||||
}
|
||||
}
|
||||
CustomMessageBox::selectable(this, tr("Update checker"), message)
|
||||
->exec();
|
||||
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -275,3 +294,13 @@ bool NilModFolderPage::shouldDisplay() const
|
||||
{
|
||||
return m_model->dir().exists();
|
||||
}
|
||||
|
||||
void ModFolderPage::visitModPages()
|
||||
{
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||
for (auto mod : m_model->selectedMods(selection)) {
|
||||
auto url = mod->metaurl();
|
||||
if (!url.isEmpty())
|
||||
DesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -60,10 +61,11 @@ class ModFolderPage : public ExternalResourcesPage {
|
||||
|
||||
private slots:
|
||||
void runningStateChanged(bool running);
|
||||
void removeItems(const QItemSelection &selection) override;
|
||||
void removeItems(const QItemSelection& selection) override;
|
||||
|
||||
void installMods();
|
||||
void updateMods();
|
||||
void visitModPages();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ModFolderModel> m_model;
|
||||
|
@ -97,37 +97,30 @@ public:
|
||||
return;
|
||||
if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0))
|
||||
return;
|
||||
int tries = 5;
|
||||
while (tries)
|
||||
{
|
||||
if (!m_cache->stale(m_path))
|
||||
return;
|
||||
QImage image(m_path);
|
||||
if (image.isNull())
|
||||
{
|
||||
QThread::msleep(500);
|
||||
tries--;
|
||||
continue;
|
||||
}
|
||||
QImage small;
|
||||
if (image.width() > image.height())
|
||||
small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
|
||||
else
|
||||
small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
|
||||
QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
|
||||
QImage square(QSize(256, 256), QImage::Format_ARGB32);
|
||||
square.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&square);
|
||||
painter.drawImage(offset, small);
|
||||
painter.end();
|
||||
|
||||
QIcon icon(QPixmap::fromImage(square));
|
||||
m_cache->add(m_path, icon);
|
||||
m_resultEmitter.emitResultsReady(m_path);
|
||||
if (!m_cache->stale(m_path))
|
||||
return;
|
||||
QImage image(m_path);
|
||||
if (image.isNull()) {
|
||||
m_resultEmitter.emitResultsFailed(m_path);
|
||||
qDebug() << "Error loading screenshot: " + m_path + ". Perhaps too large?";
|
||||
return;
|
||||
}
|
||||
m_resultEmitter.emitResultsFailed(m_path);
|
||||
QImage small;
|
||||
if (image.width() > image.height())
|
||||
small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
|
||||
else
|
||||
small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
|
||||
QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
|
||||
QImage square(QSize(256, 256), QImage::Format_ARGB32);
|
||||
square.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&square);
|
||||
painter.drawImage(offset, small);
|
||||
painter.end();
|
||||
|
||||
QIcon icon(QPixmap::fromImage(square));
|
||||
m_cache->add(m_path, icon);
|
||||
m_resultEmitter.emitResultsReady(m_path);
|
||||
}
|
||||
QString m_path;
|
||||
SharedIconCachePtr m_cache;
|
||||
@ -146,9 +139,12 @@ public:
|
||||
m_thumbnailCache = std::make_shared<SharedIconCache>();
|
||||
m_thumbnailCache->add("placeholder", APPLICATION->getThemedIcon("screenshot-placeholder"));
|
||||
connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
// FIXME: the watched file set is not updated when files are removed
|
||||
}
|
||||
virtual ~FilterModel() { m_thumbnailingPool.waitForDone(500); }
|
||||
virtual ~FilterModel() {
|
||||
m_thumbnailingPool.clear();
|
||||
if (!m_thumbnailingPool.waitForDone(500))
|
||||
qDebug() << "Thumbnail pool took longer than 500ms to finish";
|
||||
}
|
||||
virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const
|
||||
{
|
||||
auto model = sourceModel();
|
||||
@ -215,10 +211,12 @@ private slots:
|
||||
void fileChanged(QString filepath)
|
||||
{
|
||||
m_thumbnailCache->setStale(filepath);
|
||||
thumbnailImage(filepath);
|
||||
// reinsert the path...
|
||||
watcher.removePath(filepath);
|
||||
watcher.addPath(filepath);
|
||||
if (QFile::exists(filepath)) {
|
||||
watcher.addPath(filepath);
|
||||
thumbnailImage(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -339,6 +339,7 @@ void WorldListPage::mceditState(LoggedProcess::State state)
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
/* fallthrough */
|
||||
case LoggedProcess::Running:
|
||||
case LoggedProcess::Finished:
|
||||
{
|
||||
|
@ -69,6 +69,7 @@ bool JavaWizardPage::validatePage()
|
||||
case JavaSettingsWidget::ValidationStatus::AllOK:
|
||||
{
|
||||
settings->set("JavaPath", m_java_widget->javaPath());
|
||||
return true;
|
||||
}
|
||||
case JavaSettingsWidget::ValidationStatus::JavaBad:
|
||||
{
|
||||
|
@ -1,54 +1,70 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QToolTip>
|
||||
|
||||
#include "InfoFrame.h"
|
||||
#include "ui_InfoFrame.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
InfoFrame::InfoFrame(QWidget *parent) :
|
||||
QFrame(parent),
|
||||
ui(new Ui::InfoFrame)
|
||||
void setupLinkToolTip(QLabel* label)
|
||||
{
|
||||
QObject::connect(label, &QLabel::linkHovered, [label](const QString& link) {
|
||||
if (auto url = QUrl(link); !url.isValid() || (url.scheme() != "http" && url.scheme() != "https"))
|
||||
return;
|
||||
label->setToolTip(link);
|
||||
});
|
||||
}
|
||||
|
||||
InfoFrame::InfoFrame(QWidget* parent) : QFrame(parent), ui(new Ui::InfoFrame)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->descriptionLabel->setHidden(true);
|
||||
ui->nameLabel->setHidden(true);
|
||||
ui->licenseLabel->setHidden(true);
|
||||
ui->issueTrackerLabel->setHidden(true);
|
||||
|
||||
setupLinkToolTip(ui->iconLabel);
|
||||
setupLinkToolTip(ui->descriptionLabel);
|
||||
setupLinkToolTip(ui->nameLabel);
|
||||
setupLinkToolTip(ui->licenseLabel);
|
||||
setupLinkToolTip(ui->issueTrackerLabel);
|
||||
updateHiddenState();
|
||||
}
|
||||
|
||||
@ -59,45 +75,43 @@ InfoFrame::~InfoFrame()
|
||||
|
||||
void InfoFrame::updateWithMod(Mod const& m)
|
||||
{
|
||||
if (m.type() == ResourceType::FOLDER)
|
||||
{
|
||||
if (m.type() == ResourceType::FOLDER) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
QString text = "";
|
||||
QString name = "";
|
||||
QString link = m.metaurl();
|
||||
if (m.name().isEmpty())
|
||||
name = m.internal_id();
|
||||
else
|
||||
name = m.name();
|
||||
|
||||
if (m.homeurl().isEmpty())
|
||||
if (link.isEmpty())
|
||||
text = name;
|
||||
else
|
||||
text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>";
|
||||
else {
|
||||
text = "<a href=\"" + link + "\">" + name + "</a>";
|
||||
}
|
||||
if (!m.authors().isEmpty())
|
||||
text += " by " + m.authors().join(", ");
|
||||
|
||||
setName(text);
|
||||
|
||||
if (m.description().isEmpty())
|
||||
{
|
||||
if (m.description().isEmpty()) {
|
||||
setDescription(QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
setDescription(m.description());
|
||||
}
|
||||
|
||||
setImage(m.icon({64,64}));
|
||||
setImage(m.icon({ 64, 64 }));
|
||||
|
||||
auto licenses = m.licenses();
|
||||
QString licenseText = "";
|
||||
if (!licenses.empty()) {
|
||||
for (auto l : licenses) {
|
||||
if (!licenseText.isEmpty()) {
|
||||
licenseText += "\n"; // add newline between licenses
|
||||
licenseText += "\n"; // add newline between licenses
|
||||
}
|
||||
if (!l.name.isEmpty()) {
|
||||
if (l.url.isEmpty()) {
|
||||
@ -109,9 +123,9 @@ void InfoFrame::updateWithMod(Mod const& m)
|
||||
licenseText += "<a href=\"" + l.url + "\">" + l.url + "</a>";
|
||||
}
|
||||
if (!l.description.isEmpty() && l.description != l.name) {
|
||||
licenseText += " " + l.description;
|
||||
licenseText += " " + l.description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!licenseText.isEmpty()) {
|
||||
setLicense(tr("License: %1").arg(licenseText));
|
||||
@ -123,7 +137,7 @@ void InfoFrame::updateWithMod(Mod const& m)
|
||||
if (!m.issueTracker().isEmpty()) {
|
||||
issueTracker += tr("Report issues to: ");
|
||||
issueTracker += "<a href=\"" + m.issueTracker() + "\">" + m.issueTracker() + "</a>";
|
||||
}
|
||||
}
|
||||
setIssueTracker(issueTracker);
|
||||
}
|
||||
|
||||
@ -133,7 +147,8 @@ void InfoFrame::updateWithResource(const Resource& resource)
|
||||
setImage();
|
||||
}
|
||||
|
||||
QString InfoFrame::renderColorCodes(QString input) {
|
||||
QString InfoFrame::renderColorCodes(QString input)
|
||||
{
|
||||
// We have to manually set the colors for use.
|
||||
//
|
||||
// A color is set using §x, with x = a hex number from 0 to f.
|
||||
@ -144,16 +159,12 @@ QString InfoFrame::renderColorCodes(QString input) {
|
||||
// TODO: Wrap links inside <a> tags
|
||||
|
||||
// https://minecraft.fandom.com/wiki/Formatting_codes#Color_codes
|
||||
const QMap<QChar, QString> color_codes_map = {
|
||||
{'0', "#000000"}, {'1', "#0000AA"}, {'2', "#00AA00"}, {'3', "#00AAAA"}, {'4', "#AA0000"},
|
||||
{'5', "#AA00AA"}, {'6', "#FFAA00"}, {'7', "#AAAAAA"}, {'8', "#555555"}, {'9', "#5555FF"},
|
||||
{'a', "#55FF55"}, {'b', "#55FFFF"}, {'c', "#FF5555"}, {'d', "#FF55FF"}, {'e', "#FFFF55"},
|
||||
{'f', "#FFFFFF"}
|
||||
};
|
||||
const QMap<QChar, QString> color_codes_map = { { '0', "#000000" }, { '1', "#0000AA" }, { '2', "#00AA00" }, { '3', "#00AAAA" },
|
||||
{ '4', "#AA0000" }, { '5', "#AA00AA" }, { '6', "#FFAA00" }, { '7', "#AAAAAA" },
|
||||
{ '8', "#555555" }, { '9', "#5555FF" }, { 'a', "#55FF55" }, { 'b', "#55FFFF" },
|
||||
{ 'c', "#FF5555" }, { 'd', "#FF55FF" }, { 'e', "#FFFF55" }, { 'f', "#FFFFFF" } };
|
||||
// https://minecraft.fandom.com/wiki/Formatting_codes#Formatting_codes
|
||||
const QMap<QChar, QString> formatting_codes_map = {
|
||||
{'l', "b"}, {'m', "s"}, {'n', "u"}, {'o', "i"}
|
||||
};
|
||||
const QMap<QChar, QString> formatting_codes_map = { { 'l', "b" }, { 'm', "s" }, { 'n', "u" }, { 'o', "i" } };
|
||||
|
||||
QString html("<html>");
|
||||
QList<QString> tags{};
|
||||
@ -198,14 +209,14 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack)
|
||||
{
|
||||
setName(renderColorCodes(resource_pack.name()));
|
||||
setDescription(renderColorCodes(resource_pack.description()));
|
||||
setImage(resource_pack.image({64, 64}));
|
||||
setImage(resource_pack.image({ 64, 64 }));
|
||||
}
|
||||
|
||||
void InfoFrame::updateWithTexturePack(TexturePack& texture_pack)
|
||||
{
|
||||
setName(renderColorCodes(texture_pack.name()));
|
||||
setDescription(renderColorCodes(texture_pack.description()));
|
||||
setImage(texture_pack.image({64, 64}));
|
||||
setImage(texture_pack.image({ 64, 64 }));
|
||||
}
|
||||
|
||||
void InfoFrame::clear()
|
||||
@ -229,12 +240,9 @@ void InfoFrame::updateHiddenState()
|
||||
|
||||
void InfoFrame::setName(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->nameLabel->setHidden(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->nameLabel->setText(text);
|
||||
ui->nameLabel->setHidden(false);
|
||||
}
|
||||
@ -243,14 +251,11 @@ void InfoFrame::setName(QString text)
|
||||
|
||||
void InfoFrame::setDescription(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->descriptionLabel->setHidden(true);
|
||||
updateHiddenState();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->descriptionLabel->setHidden(false);
|
||||
updateHiddenState();
|
||||
}
|
||||
@ -260,9 +265,8 @@ void InfoFrame::setDescription(QString text)
|
||||
QChar rem('\n');
|
||||
QString finaltext;
|
||||
finaltext.reserve(intermediatetext.size());
|
||||
foreach(const QChar& c, intermediatetext)
|
||||
{
|
||||
if(c == rem && prev){
|
||||
foreach (const QChar& c, intermediatetext) {
|
||||
if (c == rem && prev) {
|
||||
continue;
|
||||
}
|
||||
prev = c == rem;
|
||||
@ -270,17 +274,14 @@ void InfoFrame::setDescription(QString text)
|
||||
}
|
||||
QString labeltext;
|
||||
labeltext.reserve(300);
|
||||
if(finaltext.length() > 290)
|
||||
{
|
||||
if (finaltext.length() > 290) {
|
||||
ui->descriptionLabel->setOpenExternalLinks(false);
|
||||
ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText);
|
||||
m_description = text;
|
||||
// This allows injecting HTML here.
|
||||
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
|
||||
QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
labeltext.append(finaltext);
|
||||
}
|
||||
@ -289,14 +290,11 @@ void InfoFrame::setDescription(QString text)
|
||||
|
||||
void InfoFrame::setLicense(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->licenseLabel->setHidden(true);
|
||||
updateHiddenState();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->licenseLabel->setHidden(false);
|
||||
updateHiddenState();
|
||||
}
|
||||
@ -306,9 +304,8 @@ void InfoFrame::setLicense(QString text)
|
||||
QChar rem('\n');
|
||||
QString finaltext;
|
||||
finaltext.reserve(intermediatetext.size());
|
||||
foreach(const QChar& c, intermediatetext)
|
||||
{
|
||||
if(c == rem && prev){
|
||||
foreach (const QChar& c, intermediatetext) {
|
||||
if (c == rem && prev) {
|
||||
continue;
|
||||
}
|
||||
prev = c == rem;
|
||||
@ -316,17 +313,14 @@ void InfoFrame::setLicense(QString text)
|
||||
}
|
||||
QString labeltext;
|
||||
labeltext.reserve(300);
|
||||
if(finaltext.length() > 290)
|
||||
{
|
||||
if (finaltext.length() > 290) {
|
||||
ui->licenseLabel->setOpenExternalLinks(false);
|
||||
ui->licenseLabel->setTextFormat(Qt::TextFormat::RichText);
|
||||
m_description = text;
|
||||
// This allows injecting HTML here.
|
||||
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
|
||||
QObject::connect(ui->licenseLabel, &QLabel::linkActivated, this, &InfoFrame::licenseEllipsisHandler);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->licenseLabel->setTextFormat(Qt::TextFormat::AutoText);
|
||||
labeltext.append(finaltext);
|
||||
}
|
||||
@ -335,12 +329,9 @@ void InfoFrame::setLicense(QString text)
|
||||
|
||||
void InfoFrame::setIssueTracker(QString text)
|
||||
{
|
||||
if(text.isEmpty())
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
ui->issueTrackerLabel->setHidden(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ui->issueTrackerLabel->setText(text);
|
||||
ui->issueTrackerLabel->setHidden(false);
|
||||
}
|
||||
@ -359,28 +350,22 @@ void InfoFrame::setImage(QPixmap img)
|
||||
|
||||
void InfoFrame::descriptionEllipsisHandler(QString link)
|
||||
{
|
||||
if(!m_current_box)
|
||||
{
|
||||
if (!m_current_box) {
|
||||
m_current_box = CustomMessageBox::selectable(this, "", m_description);
|
||||
connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed);
|
||||
m_current_box->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
m_current_box->setText(m_description);
|
||||
}
|
||||
}
|
||||
|
||||
void InfoFrame::licenseEllipsisHandler(QString link)
|
||||
{
|
||||
if(!m_current_box)
|
||||
{
|
||||
if (!m_current_box) {
|
||||
m_current_box = CustomMessageBox::selectable(this, "", m_license);
|
||||
connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed);
|
||||
m_current_box->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
m_current_box->setText(m_license);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,36 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@ -21,8 +41,7 @@
|
||||
#include "minecraft/mod/ResourcePack.h"
|
||||
#include "minecraft/mod/TexturePack.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
namespace Ui {
|
||||
class InfoFrame;
|
||||
}
|
||||
|
||||
|
@ -89,8 +89,10 @@ void FourBytes_MurmurHash2(const unsigned char* data, IncrementalHashInfo& prev)
|
||||
switch (prev.len) {
|
||||
case 3:
|
||||
prev.h ^= data[2] << 16;
|
||||
/* fall through */
|
||||
case 2:
|
||||
prev.h ^= data[1] << 8;
|
||||
/* fall through */
|
||||
case 1:
|
||||
prev.h ^= data[0];
|
||||
prev.h *= m;
|
||||
|
Loading…
Reference in New Issue
Block a user