Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into instance_copy_progress

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2024-02-10 11:09:45 +02:00
114 changed files with 885 additions and 618 deletions
+59 -2
View File
@@ -132,6 +132,15 @@
#include "gamemode_client.h"
#endif
#if defined(Q_OS_LINUX)
#include <sys/statvfs.h>
#endif
#if defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include <sys/mount.h>
#include <sys/types.h>
#endif
#if defined(Q_OS_MAC)
#if defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h"
@@ -485,8 +494,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
{
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << ", (c) 2022-2023 "
<< qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + ", " + QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Platform : " << BuildConfig.BUILD_PLATFORM;
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
@@ -658,6 +666,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// The cat
m_settings->registerSetting("TheCat", false);
m_settings->registerSetting("CatOpacity", 100);
m_settings->registerSetting("StatusBarVisible", true);
m_settings->registerSetting("ToolbarsLocked", false);
@@ -740,6 +751,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ModrinthToken", "");
m_settings->registerSetting("UserAgentOverride", "");
// FTBApp instances
m_settings->registerSetting("FTBAppInstancesPath", "");
// Init page provider
{
m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
@@ -988,6 +1002,37 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
}
// notify user if /tmp is mounted with `noexec` (#1693)
{
bool is_tmp_noexec = false;
#if defined(Q_OS_LINUX)
struct statvfs tmp_stat;
statvfs("/tmp", &tmp_stat);
is_tmp_noexec = tmp_stat.f_flag & ST_NOEXEC;
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
struct statfs tmp_stat;
statfs("/tmp", &tmp_stat);
is_tmp_noexec = tmp_stat.f_flags & MNT_NOEXEC;
#endif
if (is_tmp_noexec) {
auto infoMsg =
tr("Your /tmp directory is currently mounted with the 'noexec' flag enabled.\n"
"Some versions of Minecraft may not launch.\n");
auto msgBox = new QMessageBox(QMessageBox::Information, tr("Incompatible system configuration"), infoMsg, QMessageBox::Ok);
msgBox->setDefaultButton(QMessageBox::Ok);
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setMinimumWidth(460);
msgBox->adjustSize();
msgBox->open();
}
}
if (createSetupWizard()) {
return;
}
@@ -1471,6 +1516,17 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
auto& window = extras.window;
if (window) {
// If the window is minimized on macOS or Windows, activate and bring it up
#ifdef Q_OS_MACOS
if (window->isMinimized()) {
window->setWindowState(window->windowState() & ~Qt::WindowMinimized);
}
#elif defined(Q_OS_WIN)
if (window->isMinimized()) {
window->showNormal();
}
#endif
window->raise();
window->activateWindow();
} else {
@@ -1478,6 +1534,7 @@ InstanceWindow* Application::showInstanceWindow(InstancePtr instance, QString pa
m_openWindows++;
connect(window, &InstanceWindow::isClosing, this, &Application::on_windowClose);
}
if (!page.isEmpty()) {
window->selectPage(page);
}
+2
View File
@@ -64,6 +64,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
if (m_settings->get("totalTimePlayed").toLongLong() < 0)
m_settings->reset("totalTimePlayed");
m_settings->registerSetting("lastTimePlayed", 0);
m_settings->registerSetting("linkedInstances", "[]");
+9 -121
View File
@@ -37,140 +37,33 @@
#include <QDesktopServices>
#include <QDir>
#include <QProcess>
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
*/
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
template <typename T>
bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
{
auto pid = fork();
if (pid_forked) {
if (pid > 0)
*pid_forked = pid;
else
*pid_forked = 0;
}
if (pid == -1) {
qWarning() << "IndirectOpen failed to fork: " << errno;
return false;
}
// child - do the stuff
if (pid == 0) {
// unset all this garbage so it doesn't get passed to the child process
qunsetenv("LD_PRELOAD");
qunsetenv("LD_LIBRARY_PATH");
qunsetenv("LD_DEBUG");
qunsetenv("QT_PLUGIN_PATH");
qunsetenv("QT_FONTPATH");
// open the URL
auto status = callable();
// detach from the parent process group.
setsid();
// die. now. do not clean up anything, it would just hang forever.
_exit(status ? 0 : 1);
} else {
// parent - assume it worked.
int status;
while (waitpid(pid, &status, 0)) {
if (WIFEXITED(status)) {
return WEXITSTATUS(status) == 0;
}
if (WIFSIGNALED(status)) {
return false;
}
}
return true;
}
}
#endif
#include "FileSystem.h"
namespace DesktopServices {
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
bool openPath(const QFileInfo& path, bool ensureFolderPathExists)
{
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
if (ensureExists && !dir.exists()) {
parentPath.mkpath(dir.absolutePath());
qDebug() << "Opening path" << path;
if (ensureFolderPathExists) {
FS::ensureFolderPathExists(path);
}
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
}
#endif
return f();
return openUrl(QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()));
}
bool openFile(const QString& path)
bool openPath(const QString& path, bool ensureFolderPathExists)
{
qDebug() << "Opening file" << path;
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
} else {
return f();
}
#else
return f();
#endif
}
bool openFile(const QString& application, const QString& path, const QString& workingDirectory, qint64* pid)
{
qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if (!isSandbox()) {
return IndirectOpen([&]() { return QProcess::startDetached(application, QStringList() << path, workingDirectory); }, pid);
} else {
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
}
#else
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
#endif
return openPath(QFileInfo(path), ensureFolderPathExists);
}
bool run(const QString& application, const QStringList& args, const QString& workingDirectory, qint64* pid)
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() { return QProcess::startDetached(application, args, workingDirectory); }, pid);
} else {
return QProcess::startDetached(application, args, workingDirectory, pid);
}
#else
return QProcess::startDetached(application, args, workingDirectory, pid);
#endif
}
bool openUrl(const QUrl& url)
{
qDebug() << "Opening URL" << url.toString();
auto f = [&]() { return QDesktopServices::openUrl(url); };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if (!isSandbox()) {
return IndirectOpen(f);
} else {
return f();
}
#else
return f();
#endif
return QDesktopServices::openUrl(url);
}
bool isFlatpak()
@@ -191,9 +84,4 @@ bool isSnap()
#endif
}
bool isSandbox()
{
return isSnap() || isFlatpak();
}
} // namespace DesktopServices
+8 -14
View File
@@ -3,31 +3,30 @@
#include <QString>
#include <QUrl>
class QFileInfo;
/**
* This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices!
*/
namespace DesktopServices {
/**
* Open a file in whatever application is applicable
* Open a path in whatever application is applicable.
* @param ensureFolderPathExists Make sure the path exists
*/
bool openFile(const QString& path);
bool openPath(const QFileInfo& path, bool ensureFolderPathExists = false);
/**
* Open a file in the specified application
* Open a path in whatever application is applicable.
* @param ensureFolderPathExists Make sure the path exists
*/
bool openFile(const QString& application, const QString& path, const QString& workingDirectory = QString(), qint64* pid = 0);
bool openPath(const QString& path, bool ensureFolderPathExists = false);
/**
* Run an application
*/
bool run(const QString& application, const QStringList& args, const QString& workingDirectory = QString(), qint64* pid = 0);
/**
* Open a directory
*/
bool openDirectory(const QString& path, bool ensureExists = false);
/**
* Open the URL, most likely in a browser. Maybe.
*/
@@ -42,9 +41,4 @@ bool isFlatpak();
* Determine whether the launcher is running in a Snap environment
*/
bool isSnap();
/**
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
*/
bool isSandbox();
} // namespace DesktopServices
+7 -3
View File
@@ -272,15 +272,19 @@ bool ensureFilePathExists(QString filenamepath)
return success;
}
bool ensureFolderPathExists(QString foldernamepath)
bool ensureFolderPathExists(const QFileInfo folderPath)
{
QFileInfo a(foldernamepath);
QDir dir;
QString ensuredPath = a.filePath();
QString ensuredPath = folderPath.filePath();
bool success = dir.mkpath(ensuredPath);
return success;
}
bool ensureFolderPathExists(const QString folderPathName)
{
return ensureFolderPathExists(QFileInfo(folderPathName));
}
bool copyFileAttributes(QString src, QString dst)
{
#ifdef Q_OS_WIN32
+7 -1
View File
@@ -91,7 +91,13 @@ bool ensureFilePathExists(QString filenamepath);
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
bool ensureFolderPathExists(QString filenamepath);
bool ensureFolderPathExists(const QFileInfo folderPath);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
bool ensureFolderPathExists(const QString folderPathName);
/**
* @brief Copies a directory and it's contents from src to dest
+4 -5
View File
@@ -58,10 +58,10 @@ void InstanceCopyTask::executeTask()
QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
QString staging_mc_dir;
if (mcDir.exists() && !dotMCDir.exists())
staging_mc_dir = mcDir.filePath();
else
if (dotMCDir.exists() && !mcDir.exists())
staging_mc_dir = dotMCDir.filePath();
else
staging_mc_dir = mcDir.filePath();
savesCopy = std::make_unique<FS::copy>(FS::PathCombine(m_origInstance->gameRoot(), "saves"),
FS::PathCombine(staging_mc_dir, "saves"));
@@ -156,9 +156,8 @@ void InstanceCopyTask::copyFinished()
if (!m_keepPlaytime) {
inst->resetTimePlayed();
}
if (m_useLinks)
inst->addLinkedInstanceId(m_origInstance->id());
if (m_useLinks) {
inst->addLinkedInstanceId(m_origInstance->id());
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
QByteArray allowed_symlinks;
+2 -2
View File
@@ -164,8 +164,8 @@ void InstanceImportTask::processZipPack()
} else if (technicFound) {
// process as Technic pack
qDebug() << "Technic:" << technicFound;
extractDir.mkpath(".minecraft");
extractDir.cd(".minecraft");
extractDir.mkpath("minecraft");
extractDir.cd("minecraft");
m_modpackType = ModpackType::Technic;
} else {
QStringList paths_to_ignore{ "overrides/" };
+7 -6
View File
@@ -2,6 +2,8 @@
#include "ui/dialogs/CustomMessageBox.h"
#include <QPushButton>
InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name)
{
auto dialog =
@@ -27,16 +29,15 @@ ShouldUpdate askIfShouldUpdate(QWidget* parent, QString original_version_name)
"separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before "
"updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).")
.arg(original_version_name),
QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort);
info->setButtonText(QMessageBox::Ok, QObject::tr("Update existing instance"));
info->setButtonText(QMessageBox::Abort, QObject::tr("Create new instance"));
info->setButtonText(QMessageBox::Reset, QObject::tr("Cancel"));
QMessageBox::Information, QMessageBox::Cancel);
QAbstractButton* update = info->addButton(QObject::tr("Update existing instance"), QMessageBox::AcceptRole);
QAbstractButton* skip = info->addButton(QObject::tr("Create new instance"), QMessageBox::ResetRole);
info->exec();
if (info->clickedButton() == info->button(QMessageBox::Ok))
if (info->clickedButton() == update)
return ShouldUpdate::Update;
if (info->clickedButton() == info->button(QMessageBox::Abort))
if (info->clickedButton() == skip)
return ShouldUpdate::SkipUpdating;
return ShouldUpdate::Cancel;
}
+6 -7
View File
@@ -42,7 +42,6 @@
#include "ui/InstanceWindow.h"
#include "ui/MainWindow.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ProfileSelectDialog.h"
#include "ui/dialogs/ProfileSetupDialog.h"
#include "ui/dialogs/ProgressDialog.h"
@@ -144,6 +143,12 @@ void LaunchController::login()
bool tryagain = true;
unsigned int tries = 0;
if (m_accountToUse->accountType() != AccountType::Offline && m_accountToUse->accountState() == AccountState::Offline) {
// Force account refresh on the account used to launch the instance updating the AccountState
// only on first try and if it is not meant to be offline
auto accounts = APPLICATION->accounts();
accounts->requestRefresh(m_accountToUse->internalId());
}
while (tryagain) {
if (tries > 0 && tries % 3 == 0) {
auto result =
@@ -250,12 +255,6 @@ void LaunchController::login()
progDialog.execWithTask(task.get());
continue;
}
// FIXME: this is missing - the meaning is that the account is queued for refresh and we should wait for that
/*
case AccountState::Queued: {
return;
}
*/
case AccountState::Expired: {
auto errorString = tr("The account has expired and needs to be logged into manually again.");
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
+3 -3
View File
@@ -292,10 +292,10 @@ QString MinecraftInstance::gameRoot() const
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
if (mcDir.exists() && !dotMCDir.exists())
return mcDir.filePath();
else
if (dotMCDir.exists() && !mcDir.exists())
return dotMCDir.filePath();
else
return mcDir.filePath();
}
QString MinecraftInstance::binRoot() const
@@ -157,20 +157,6 @@ void MojangVersionFormat::readVersionProperties(const QJsonObject& in, VersionFi
Bits::readString(in, "id", out->minecraftVersion);
Bits::readString(in, "mainClass", out->mainClass);
Bits::readString(in, "minecraftArguments", out->minecraftArguments);
if (out->minecraftArguments.isEmpty()) {
QString processArguments;
Bits::readString(in, "processArguments", processArguments);
QString toCompare = processArguments.toLower();
if (toCompare == "legacy") {
out->minecraftArguments = " ${auth_player_name} ${auth_session}";
} else if (toCompare == "username_session") {
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}";
} else if (toCompare == "username_session_version") {
out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}";
} else if (!toCompare.isEmpty()) {
out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments));
}
}
Bits::readString(in, "type", out->type);
Bits::readString(in, "assets", out->assets);
-2
View File
@@ -52,8 +52,6 @@
#include <FileSystem.h>
#include <QSaveFile>
#include <chrono>
enum AccountListVersion { MojangMSA = 3 };
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
@@ -306,7 +306,6 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>
auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removed_it != m_resources.end());
Q_ASSERT(removed_set.contains(removed_it->get()->internal_id()));
if ((*removed_it)->isResolving()) {
auto ticket = (*removed_it)->resolutionTicket();
@@ -182,7 +182,9 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks;
callbacks.on_fail = [](QString reason, int) {
qCritical() << tr("A network error occurred. Could not load project dependencies:%1").arg(reason);
};
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
try {
QJsonArray arr;
@@ -283,4 +285,4 @@ QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
rby[addonId.toString()] = req;
}
return rby;
}
}
@@ -149,6 +149,7 @@ void EnsureMetadataTask::executeTask()
if (m_current_task)
m_current_task.reset();
});
connect(project_task.get(), &Task::failed, this, &EnsureMetadataTask::emitFailed);
m_current_task = project_task;
project_task->start();
+2
View File
@@ -122,6 +122,8 @@ struct ExtraPackData {
QString wikiUrl;
QString discordUrl;
QString status;
QString body;
};
+2
View File
@@ -96,6 +96,7 @@ class ResourceAPI {
};
struct VersionSearchCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
};
struct ProjectInfoArgs {
@@ -118,6 +119,7 @@ class ResourceAPI {
struct DependencySearchCallbacks {
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
std::function<void(QString const& reason, int network_error_code)> on_fail;
};
public:
@@ -119,7 +119,6 @@ void Flame::FileResolvingTask::netJobFinished()
connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
step_progress->state = TaskStepState::Failed;
stepProgress(*step_progress);
emitFailed(reason);
});
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
@@ -24,7 +24,7 @@ bool FlameCheckUpdate::abort()
return true;
}
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
{
ModPlatform::IndexedPack pack;
@@ -57,6 +57,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
}
});
connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
get_project_job->deleteLater();
loop.quit();
@@ -68,7 +69,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
return pack;
}
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
{
ModPlatform::IndexedVersion ver;
@@ -100,7 +101,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
qDebug() << doc;
}
});
connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
get_file_info_job->deleteLater();
loop.quit();
@@ -22,6 +22,9 @@ class FlameCheckUpdate : public CheckUpdateTask {
void executeTask() override;
private:
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
NetJob* m_net_job = nullptr;
bool m_was_aborted = false;
@@ -227,6 +227,7 @@ bool FlameCreationTask::updateInstance()
m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path));
}
});
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files: " << reason; });
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
m_process_update_file_info_job = job;
@@ -427,6 +428,9 @@ bool FlameCreationTask::createInstance()
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
else
instance.setManagedPack("flame", "", name(), "", "");
instance.setName(name());
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
@@ -323,6 +323,7 @@ void FlamePackExportTask::getProjectsInfo()
}
buildZip();
});
connect(projTask.get(), &Task::failed, this, &FlamePackExportTask::emitFailed);
task.reset(projTask);
task->start();
}
@@ -102,6 +102,13 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
callbacks.on_succeed(doc, args.pack);
});
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
});
return netJob;
}
@@ -146,6 +153,12 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
callbacks.on_succeed(doc, args.dependency);
});
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason) {
int network_error_code = -1;
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
callbacks.on_fail(reason, network_error_code);
});
return netJob;
}
@@ -37,7 +37,7 @@ void PackInstallTask::executeTask()
progress(1, 2);
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, ".minecraft"));
FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, "minecraft"));
folderCopy.followSymlinks(true);
return folderCopy();
});
@@ -137,7 +137,7 @@ void PackInstallTask::install()
QDir unzipMcDir(m_stagingPath + "/unzip/minecraft");
if (unzipMcDir.exists()) {
// ok, found minecraft dir, move contents to instance dir
if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) {
if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/minecraft")) {
emitFailed(tr("Failed to move unzipped Minecraft!"));
return;
}
@@ -155,7 +155,7 @@ void PackInstallTask::install()
bool fallback = true;
// handle different versions
QFile packJson(m_stagingPath + "/.minecraft/pack.json");
QFile packJson(m_stagingPath + "/minecraft/pack.json");
QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods");
if (packJson.exists()) {
packJson.open(QIODevice::ReadOnly | QIODevice::Text);
@@ -72,9 +72,7 @@ void ModrinthCheckUpdate::executeTask()
auto response = std::make_shared<QByteArray>();
auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response);
QEventLoop lock;
connect(job.get(), &Task::succeeded, this, [this, response, &mappings, best_hash_type, job] {
connect(job.get(), &Task::succeeded, this, [this, response, mappings, best_hash_type, job] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
@@ -82,7 +80,7 @@ void ModrinthCheckUpdate::executeTask()
<< " reason: " << parse_error.errorString();
qWarning() << *response;
failed(parse_error.errorString());
emitFailed(parse_error.errorString());
return;
}
@@ -167,19 +165,17 @@ void ModrinthCheckUpdate::executeTask()
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
}
} catch (Json::JsonException& e) {
failed(e.cause() + " : " + e.what());
emitFailed(e.cause() + " : " + e.what());
return;
}
emitSucceeded();
});
connect(job.get(), &Task::finished, &lock, &QEventLoop::quit);
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::emitFailed);
setStatus(tr("Waiting for the API response from Modrinth..."));
setProgress(1, 3);
m_net_job = qSharedPointerObjectCast<NetJob, Task>(job);
job->start();
lock.exec();
emitSucceeded();
}
@@ -173,7 +173,7 @@ bool ModrinthCreationTask::createInstance()
FS::ensureFilePathExists(new_index_place);
QFile::rename(index_path, new_index_place);
auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft");
auto mcPath = FS::PathCombine(m_stagingPath, "minecraft");
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(override_path)) {
@@ -226,12 +226,15 @@ bool ModrinthCreationTask::createInstance()
// Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty())
instance.setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
else
instance.setManagedPack("modrinth", "", name(), "", "");
instance.setName(name());
instance.saveNow();
m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network()));
auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft");
auto root_modpack_path = FS::PathCombine(m_stagingPath, "minecraft");
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
for (auto file : m_files) {
@@ -289,7 +292,7 @@ bool ModrinthCreationTask::createInstance()
// Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName())) {
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance.name()) {
if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange)
inst->setName(instance.name());
}
@@ -104,6 +104,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraData.donate.append(donate);
}
pack.extraData.status = Json::ensureString(obj, "status");
pack.extraData.body = Json::ensureString(obj, "body").remove("<br>");
pack.extraDataLoaded = true;
@@ -95,6 +95,8 @@ void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
pack.extra.donate.append(donate);
}
pack.extra.status = Json::ensureString(obj, "status");
pack.extraInfoLoaded = true;
}
@@ -77,6 +77,8 @@ struct ModpackExtra {
QString discordUrl;
QList<DonationData> donate;
QString status;
};
struct ModpackVersion {
@@ -62,7 +62,7 @@ void Technic::SingleZipPackInstallTask::downloadSucceeded()
m_abortable = false;
setStatus(tr("Extracting modpack"));
QDir extractDir(FS::PathCombine(m_stagingPath, ".minecraft"));
QDir extractDir(FS::PathCombine(m_stagingPath, "minecraft"));
qDebug() << "Attempting to create instance from" << m_archivePath;
// open the zip and find relevant files in it
@@ -140,7 +140,7 @@ void Technic::SolderPackInstallTask::downloadSucceeded()
m_filesNetJob.reset();
m_extractFuture = QtConcurrent::run([this]() {
int i = 0;
QString extractDir = FS::PathCombine(m_stagingPath, ".minecraft");
QString extractDir = FS::PathCombine(m_stagingPath, "minecraft");
FS::ensureFolderPathExists(extractDir);
while (m_modCount > i) {
@@ -33,7 +33,7 @@ void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings,
const QString& minecraftVersion,
[[maybe_unused]] const bool isSolder)
{
QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft");
QString minecraftPath = FS::PathCombine(stagingPath, "minecraft");
QString configPath = FS::PathCombine(stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
MinecraftInstance instance(globalSettings, instanceSettings, stagingPath);
+8 -10
View File
@@ -36,6 +36,7 @@
*/
#include "NetJob.h"
#include "tasks/ConcurrentTask.h"
#if defined(LAUNCHER_APPLICATION)
#include "Application.h"
#endif
@@ -56,18 +57,15 @@ auto NetJob::addNetAction(NetAction::Ptr action) -> bool
return true;
}
void NetJob::startNext()
void NetJob::executeNextSubTask()
{
if (m_queue.isEmpty() && m_doing.isEmpty()) {
// We're finished, check for failures and retry if we can (up to 3 times)
if (!m_failed.isEmpty() && m_try < 3) {
m_try += 1;
while (!m_failed.isEmpty())
m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
}
// We're finished, check for failures and retry if we can (up to 3 times)
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
m_try += 1;
while (!m_failed.isEmpty())
m_queue.enqueue(m_failed.take(*m_failed.keyBegin()));
}
ConcurrentTask::startNext();
ConcurrentTask::executeNextSubTask();
}
auto NetJob::size() const -> int
+3 -2
View File
@@ -55,8 +55,6 @@ class NetJob : public ConcurrentTask {
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network);
~NetJob() override = default;
void startNext() override;
auto size() const -> int;
auto canAbort() const -> bool override;
@@ -69,6 +67,9 @@ class NetJob : public ConcurrentTask {
// Qt can't handle auto at the start for some reason?
bool abort() override;
protected slots:
void executeNextSubTask() override;
protected:
void updateState() override;
+1
View File
@@ -5,6 +5,7 @@
qt.*.debug=false
# don't log credentials by default
launcher.auth.credentials.debug=false
katabasis.*.debug=false
# remove the debug lines, other log levels still get through
launcher.task.net.download.debug=false
# enable or disable whole catageries
+2 -2
View File
@@ -58,14 +58,14 @@ void ImgurUpload::init()
QNetworkReply* ImgurUpload::getReply(QNetworkRequest& request)
{
auto file = new QFile(m_fileInfo.absoluteFilePath());
auto file = new QFile(m_fileInfo.absoluteFilePath(), this);
if (!file->open(QFile::ReadOnly)) {
emitFailed();
return nullptr;
}
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this);
file->setParent(multipart);
QHttpPart filePart;
filePart.setBodyDevice(file);
+1
View File
@@ -54,6 +54,7 @@ bool INIFile::saveFile(QString fileName)
insert("ConfigVersion", "1.2");
QSettings _settings_obj{ fileName, QSettings::Format::IniFormat };
_settings_obj.setFallbacksEnabled(false);
_settings_obj.clear();
for (Iterator iter = begin(); iter != end(); iter++)
_settings_obj.setValue(iter.key(), iter.value());
+42 -51
View File
@@ -35,7 +35,6 @@
*/
#include "ConcurrentTask.h"
#include <QCoreApplication>
#include <QDebug>
#include "tasks/Task.h"
@@ -47,9 +46,9 @@ ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concu
ConcurrentTask::~ConcurrentTask()
{
for (auto task : m_queue) {
for (auto task : m_doing) {
if (task)
task->deleteLater();
task->disconnect(this);
}
}
@@ -65,15 +64,13 @@ void ConcurrentTask::addTask(Task::Ptr task)
void ConcurrentTask::executeTask()
{
// Start one task, startNext handles starting the up to the m_total_max_size
// while tracking the number currently being done
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
for (auto i = 0; i < m_total_max_size; i++)
QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
}
bool ConcurrentTask::abort()
{
m_queue.clear();
m_aborted = true;
if (m_doing.isEmpty()) {
// Don't call emitAborted() here, we want to bypass the 'is the task running' check
@@ -108,29 +105,36 @@ void ConcurrentTask::clear()
m_failed.clear();
m_queue.clear();
m_aborted = false;
m_progress = 0;
m_stepProgress = 0;
}
void ConcurrentTask::startNext()
void ConcurrentTask::executeNextSubTask()
{
if (m_aborted || m_doing.count() > m_total_max_size)
if (!isRunning()) {
return;
if (m_queue.isEmpty() && m_doing.isEmpty() && !wasSuccessful()) {
emitSucceeded();
}
if (m_doing.count() >= m_total_max_size) {
return;
}
if (m_queue.isEmpty()) {
if (m_doing.isEmpty()) {
if (m_failed.isEmpty())
emitSucceeded();
else
emitFailed(tr("One or more subtasks failed"));
}
return;
}
if (m_queue.isEmpty())
return;
Task::Ptr next = m_queue.dequeue();
startSubTask(m_queue.dequeue());
}
void ConcurrentTask::startSubTask(Task::Ptr next)
{
connect(next.get(), &Task::succeeded, this, [this, next]() { subTaskSucceeded(next); });
connect(next.get(), &Task::failed, this, [this, next](QString msg) { subTaskFailed(next, msg); });
// this should never happen but if it does, it's better to fail the task than get stuck
connect(next.get(), &Task::aborted, this, [this, next] { subTaskFailed(next, "Aborted"); });
connect(next.get(), &Task::status, this, [this, next](QString msg) { subTaskStatus(next, msg); });
@@ -140,55 +144,42 @@ void ConcurrentTask::startNext()
connect(next.get(), &Task::progress, this, [this, next](qint64 current, qint64 total) { subTaskProgress(next, current, total); });
m_doing.insert(next.get(), next);
qsizetype num_starts = qMin(m_queue.size(), m_total_max_size - m_doing.size());
auto task_progress = std::make_shared<TaskStepProgress>(next->getUid());
m_task_progress.insert(next->getUid(), task_progress);
updateState();
updateStepProgress(*task_progress.get(), Operation::ADDED);
QCoreApplication::processEvents();
QMetaObject::invokeMethod(next.get(), &Task::start, Qt::QueuedConnection);
}
// Allow going up the number of concurrent tasks in case of tasks being added in the middle of a running task.
for (int i = 0; i < num_starts; i++)
QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection);
void ConcurrentTask::subTaskFinished(Task::Ptr task, TaskStepState state)
{
m_done.insert(task.get(), task);
(state == TaskStepState::Succeeded ? m_succeeded : m_failed).insert(task.get(), task);
m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = state;
disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
QMetaObject::invokeMethod(this, &ConcurrentTask::executeNextSubTask, Qt::QueuedConnection);
}
void ConcurrentTask::subTaskSucceeded(Task::Ptr task)
{
m_done.insert(task.get(), task);
m_succeeded.insert(task.get(), task);
m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = TaskStepState::Succeeded;
disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
startNext();
subTaskFinished(task, TaskStepState::Succeeded);
}
void ConcurrentTask::subTaskFailed(Task::Ptr task, [[maybe_unused]] const QString& msg)
{
m_done.insert(task.get(), task);
m_failed.insert(task.get(), task);
m_doing.remove(task.get());
auto task_progress = m_task_progress.value(task->getUid());
task_progress->state = TaskStepState::Failed;
disconnect(task.get(), 0, this, 0);
emit stepProgress(*task_progress);
updateState();
updateStepProgress(*task_progress, Operation::REMOVED);
startNext();
subTaskFinished(task, TaskStepState::Failed);
}
void ConcurrentTask::subTaskStatus(Task::Ptr task, const QString& msg)
+5 -4
View File
@@ -72,10 +72,11 @@ class ConcurrentTask : public Task {
protected slots:
void executeTask() override;
virtual void startNext();
virtual void executeNextSubTask();
void subTaskSucceeded(Task::Ptr);
void subTaskFailed(Task::Ptr, const QString& msg);
virtual void subTaskFailed(Task::Ptr, const QString& msg);
void subTaskFinished(Task::Ptr, TaskStepState);
void subTaskStatus(Task::Ptr task, const QString& msg);
void subTaskDetails(Task::Ptr task, const QString& msg);
void subTaskProgress(Task::Ptr task, qint64 current, qint64 total);
@@ -90,6 +91,8 @@ class ConcurrentTask : public Task {
virtual void updateState();
void startSubTask(Task::Ptr task);
protected:
QString m_name;
QString m_step_status;
@@ -107,6 +110,4 @@ class ConcurrentTask : public Task {
qint64 m_stepProgress = 0;
qint64 m_stepTotalProgress = 100;
bool m_aborted = false;
};
+3 -3
View File
@@ -36,9 +36,9 @@
#include <QDebug>
MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : SequentialTask(parent, task_name) {}
MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name, 1) {}
void MultipleOptionsTask::startNext()
void MultipleOptionsTask::executeNextSubTask()
{
if (m_done.size() != m_failed.size()) {
emitSucceeded();
@@ -51,7 +51,7 @@ void MultipleOptionsTask::startNext()
return;
}
ConcurrentTask::startNext();
ConcurrentTask::executeNextSubTask();
}
void MultipleOptionsTask::updateState()
+3 -3
View File
@@ -34,18 +34,18 @@
*/
#pragma once
#include "SequentialTask.h"
#include "ConcurrentTask.h"
/* This task type will attempt to do run each of it's subtasks in sequence,
* until one of them succeeds. When that happens, the remaining tasks will not run.
* */
class MultipleOptionsTask : public SequentialTask {
class MultipleOptionsTask : public ConcurrentTask {
Q_OBJECT
public:
explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = "");
~MultipleOptionsTask() override = default;
private slots:
void startNext() override;
void executeNextSubTask() override;
void updateState() override;
};
+5 -8
View File
@@ -36,18 +36,15 @@
#include "SequentialTask.h"
#include <QDebug>
#include "tasks/ConcurrentTask.h"
SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {}
void SequentialTask::startNext()
void SequentialTask::subTaskFailed(Task::Ptr task, const QString& msg)
{
if (m_failed.size() > 0) {
emitFailed(tr("One of the tasks failed!"));
qWarning() << m_failed.constBegin()->get()->failReason();
return;
}
ConcurrentTask::startNext();
emitFailed(msg);
qWarning() << msg;
ConcurrentTask::subTaskFailed(task, msg);
}
void SequentialTask::updateState()
+3 -1
View File
@@ -50,7 +50,9 @@ class SequentialTask : public ConcurrentTask {
explicit SequentialTask(QObject* parent = nullptr, QString task_name = "");
~SequentialTask() override = default;
protected slots:
virtual void subTaskFailed(Task::Ptr, const QString& msg) override;
protected:
void startNext() override;
void updateState() override;
};
+24 -9
View File
@@ -186,6 +186,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->instanceToolBar->addContextMenuAction(ui->newsToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->instanceToolBar->toggleViewAction());
ui->instanceToolBar->addContextMenuAction(ui->actionToggleStatusBar);
ui->instanceToolBar->addContextMenuAction(ui->actionLockToolbars);
}
@@ -319,6 +320,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
setCatBackground(cat_enable);
}
// Togglable status bar
{
bool statusBarVisible = APPLICATION->settings()->get("StatusBarVisible").toBool();
ui->actionToggleStatusBar->setChecked(statusBarVisible);
connect(ui->actionToggleStatusBar, &QAction::toggled, this, &MainWindow::setStatusBarVisibility);
setStatusBarVisibility(statusBarVisible);
}
// Lock toolbars
{
bool toolbarsLocked = APPLICATION->settings()->get("ToolbarsLocked").toBool();
@@ -451,10 +460,16 @@ QMenu* MainWindow::createPopupMenu()
QMenu* filteredMenu = QMainWindow::createPopupMenu();
filteredMenu->removeAction(ui->mainToolBar->toggleViewAction());
filteredMenu->addAction(ui->actionToggleStatusBar);
filteredMenu->addAction(ui->actionLockToolbars);
return filteredMenu;
}
void MainWindow::setStatusBarVisibility(bool state)
{
statusBar()->setVisible(state);
APPLICATION->settings()->set("StatusBarVisible", state);
}
void MainWindow::lockToolbars(bool state)
{
ui->mainToolBar->setMovable(!state);
@@ -1182,43 +1197,43 @@ void MainWindow::undoTrashInstance()
void MainWindow::on_actionViewLauncherRootFolder_triggered()
{
DesktopServices::openDirectory(".");
DesktopServices::openPath(".");
}
void MainWindow::on_actionViewInstanceFolder_triggered()
{
QString str = APPLICATION->settings()->get("InstanceDir").toString();
DesktopServices::openDirectory(str);
DesktopServices::openPath(str);
}
void MainWindow::on_actionViewCentralModsFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->settings()->get("CentralModsDir").toString(), true);
DesktopServices::openPath(APPLICATION->settings()->get("CentralModsDir").toString(), true);
}
void MainWindow::on_actionViewIconThemeFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path(), true);
}
void MainWindow::on_actionViewWidgetThemeFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path(), true);
}
void MainWindow::on_actionViewCatPackFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path(), true);
DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path(), true);
}
void MainWindow::on_actionViewIconsFolder_triggered()
{
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
}
void MainWindow::on_actionViewLogsFolder_triggered()
{
DesktopServices::openDirectory("logs", true);
DesktopServices::openPath("logs", true);
}
void MainWindow::refreshInstances()
@@ -1437,7 +1452,7 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered()
{
if (m_selectedInstance) {
QString str = m_selectedInstance->instanceRoot();
DesktopServices::openDirectory(QDir(str).absolutePath());
DesktopServices::openPath(QFileInfo(str));
}
}
+2
View File
@@ -205,6 +205,8 @@ class MainWindow : public QMainWindow {
void globalSettingsClosed();
void setStatusBarVisibility(bool);
void lockToolbars(bool);
#ifndef Q_OS_MAC
+9
View File
@@ -176,6 +176,7 @@
<addaction name="actionChangeTheme"/>
<addaction name="separator"/>
<addaction name="actionCAT"/>
<addaction name="actionToggleStatusBar"/>
<addaction name="actionLockToolbars"/>
<addaction name="separator"/>
</widget>
@@ -257,6 +258,14 @@
<string>It's a fluffy kitty :3</string>
</property>
</action>
<action name="actionToggleStatusBar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Status Bar</string>
</property>
</action>
<action name="actionLockToolbars">
<property name="checkable">
<bool>true</bool>
+1 -2
View File
@@ -174,8 +174,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia
QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
QString copyText("© 2022-2023 %1");
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
ui->copyLabel->setText(BuildConfig.LAUNCHER_COPYRIGHT);
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
-4
View File
@@ -15,7 +15,6 @@
#pragma once
#include <net/NetJob.h>
#include <QDialog>
namespace Ui {
@@ -31,7 +30,4 @@ class AboutDialog : public QDialog {
private:
Ui::AboutDialog* ui;
NetJob::Ptr netJob;
QByteArray dataSink;
};
+1 -1
View File
@@ -159,5 +159,5 @@ IconPickerDialog::~IconPickerDialog()
void IconPickerDialog::openFolder()
{
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
}
+9
View File
@@ -328,6 +328,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH);
});
connect(modrinth_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (modrinth_task->getHashingTask())
seq.addTask(modrinth_task->getHashingTask());
@@ -341,6 +343,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool
connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME);
});
connect(flame_task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
if (flame_task->getHashingTask())
seq.addTask(flame_task->getHashingTask());
@@ -394,6 +398,8 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
auto task = makeShared<EnsureMetadataTask>(mod, index_dir, next(first_choice));
connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); });
connect(task.get(), &EnsureMetadataTask::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
m_second_try_metadata->addTask(task);
} else {
@@ -437,6 +443,9 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStri
reqItem->insertChildren(i++, { reqItem });
}
}
ui->toggleDepsButton->show();
m_deps << item_top;
}
auto changelog_item = new QTreeWidgetItem(item_top);
+10
View File
@@ -13,6 +13,7 @@ ReviewMessageBox::ReviewMessageBox(QWidget* parent, [[maybe_unused]] QString con
auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel);
back_button->setText(tr("Back"));
ui->toggleDepsButton->hide();
ui->modTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->modTreeWidget->header()->setStretchLastSection(false);
ui->modTreeWidget->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
@@ -75,6 +76,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
}
itemTop->insertChildren(childIndx++, { requiredByItem });
ui->toggleDepsButton->show();
m_deps << itemTop;
}
auto versionTypeItem = new QTreeWidgetItem(itemTop);
@@ -108,3 +111,10 @@ void ReviewMessageBox::retranslateUi(QString resources_name)
ui->explainLabel->setText(tr("You're about to download the following %1:").arg(resources_name));
ui->onlyCheckedLabel->setText(tr("Only %1 with a check will be downloaded!").arg(resources_name));
}
void ReviewMessageBox::on_toggleDepsButton_clicked()
{
m_deps_checked = !m_deps_checked;
auto state = m_deps_checked ? Qt::Checked : Qt::Unchecked;
for (auto dep : m_deps)
dep->setCheckState(0, state);
};
+7
View File
@@ -1,6 +1,7 @@
#pragma once
#include <QDialog>
#include <QTreeWidgetItem>
namespace Ui {
class ReviewMessageBox;
@@ -28,8 +29,14 @@ class ReviewMessageBox : public QDialog {
~ReviewMessageBox() override;
protected slots:
void on_toggleDepsButton_clicked();
protected:
ReviewMessageBox(QWidget* parent, const QString& title, const QString& icon);
Ui::ReviewMessageBox* ui;
QList<QTreeWidgetItem*> m_deps;
bool m_deps_checked = true;
};
+8 -3
View File
@@ -44,15 +44,20 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="explainLabel">
</widget>
<widget class="QLabel" name="explainLabel"/>
</item>
<item row="5" column="0" rowspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="onlyCheckedLabel">
<widget class="QPushButton" name="toggleDepsButton">
<property name="text">
<string>Toggle Dependencies</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="onlyCheckedLabel"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
@@ -458,6 +458,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
QPainter painter(this->viewport());
if (m_catVisible) {
painter.setOpacity(APPLICATION->settings()->get("CatOpacity").toFloat() / 100);
int widWidth = this->viewport()->width();
int widHeight = this->viewport()->height();
if (m_catPixmap.width() < widWidth)
@@ -468,6 +469,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
QRect rectOfPixmap = pixmap.rect();
rectOfPixmap.moveBottomRight(this->viewport()->rect().bottomRight());
painter.drawPixmap(rectOfPixmap.topLeft(), pixmap);
painter.setOpacity(1.0);
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+23 -5
View File
@@ -158,13 +158,14 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti
painter->setRenderHint(QPainter::Antialiasing);
// sizes and offsets, to keep things consistent below
int arrowOffsetLeft = fontMetrics.height() / 2 + 7;
int textOffsetLeft = arrowOffsetLeft * 2;
int arrowSize = 6;
int centerHeight = optRect.top() + fontMetrics.height() / 2;
const int arrowOffsetLeft = fontMetrics.height() / 2 + 7;
const int textOffsetLeft = arrowOffsetLeft * 2;
const int centerHeight = optRect.top() + fontMetrics.height() / 2;
const QString& textToDraw = text.isEmpty() ? QObject::tr("Ungrouped") : text;
// BEGIN: arrow
{
constexpr int arrowSize = 6;
QPolygon arrowPolygon;
if (collapsed) {
arrowPolygon << QPoint(arrowOffsetLeft - arrowSize / 2, centerHeight - arrowSize)
@@ -188,9 +189,26 @@ void VisualGroup::drawHeader(QPainter* painter, const QStyleOptionViewItem& opti
textRect.setHeight(fontMetrics.height());
textRect.setRight(textRect.right() - 7);
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, !text.isEmpty() ? text : QObject::tr("Ungrouped"));
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, textToDraw);
}
// END: text
// BEGIN: horizontal line
{
penColor.setAlphaF(0.05);
pen.setColor(penColor);
painter->setPen(pen);
// startPoint is left + arrow + text + space
const int startPoint =
optRect.left() + fontMetrics.height() + fontMetrics.size(Qt::AlignLeft | Qt::AlignVCenter, textToDraw).width() + 20;
painter->setRenderHint(QPainter::Antialiasing, false);
QPolygon polygon;
// for some reason the height (yPos) doesn't look centered, so we are adding 1 to the center height
const int lineHeight = centerHeight + 1;
polygon << QPoint(startPoint, lineHeight) << QPoint(optRect.right() - 3, lineHeight);
painter->drawPolyline(polygon);
}
// END: horizontal line
}
int VisualGroup::totalHeight() const
@@ -221,6 +221,9 @@ void LauncherPage::applySettings()
break;
}
// Cat
s->set("CatOpacity", ui->catOpacitySpinBox->value());
// Mods
s->set("ModMetadataDisabled", ui->metadataDisableBtn->isChecked());
s->set("ModDependenciesDisabled", ui->dependenciesDisableBtn->isChecked());
@@ -276,6 +279,9 @@ void LauncherPage::loadSettings()
ui->sortByNameBtn->setChecked(true);
}
// Cat
ui->catOpacitySpinBox->setValue(s->get("CatOpacity").toInt());
// Mods
ui->metadataDisableBtn->setChecked(s->get("ModMetadataDisabled").toBool());
ui->metadataWarningLabel->setHidden(!ui->metadataDisableBtn->isChecked());
+49 -1
View File
@@ -186,7 +186,7 @@
</property>
</widget>
</item>
<item>
<item>
<widget class="QCheckBox" name="dependenciesDisableBtn">
<property name="toolTip">
<string>Disable the automatic detection, installation, and updating of mod dependencies.</string>
@@ -300,6 +300,54 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="catBox">
<property name="title">
<string>Cat</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="catOpacityLabel">
<property name="toolTip">
<string>Set the cat's opacity. 0% is fully transparent and 100% is fully opaque.</string>
</property>
<property name="text">
<string>Opacity</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="catOpacitySpinBox">
<property name="specialValueText">
<string/>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="toolsBox">
<property name="sizePolicy">
+1 -1
View File
@@ -206,7 +206,7 @@
<item>
<widget class="QCheckBox" name="onlineFixes">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;This currently allows modern skins to be used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;Current fixes include: skin and online mode support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable online fixes (experimental)</string>
@@ -290,12 +290,12 @@ void ExternalResourcesPage::disableItem()
void ExternalResourcesPage::viewConfigs()
{
DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true);
DesktopServices::openPath(m_instance->instanceConfigFolder(), true);
}
void ExternalResourcesPage::viewFolder()
{
DesktopServices::openDirectory(m_model->dir().absolutePath(), true);
DesktopServices::openPath(m_model->dir().absolutePath(), true);
}
bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous)
@@ -346,7 +346,7 @@ void InstanceSettingsPage::loadSettings()
#ifdef Q_OS_LINUX
ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath);
#else
ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
#endif
// Performance
@@ -605,7 +605,7 @@
<item>
<widget class="QCheckBox" name="onlineFixes">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;This currently allows modern skins to be used.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;Current fixes include: skin and online mode support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable online fixes (experimental)</string>
+21 -1
View File
@@ -131,6 +131,22 @@ ManagedPackPage::~ManagedPackPage()
void ManagedPackPage::openedImpl()
{
if (m_inst->getManagedPackID().isEmpty()) {
ui->packVersion->hide();
ui->packVersionLabel->hide();
ui->packOrigin->hide();
ui->packOriginLabel->hide();
ui->versionsComboBox->hide();
ui->updateButton->hide();
ui->updateToVersionLabel->hide();
ui->updateFromFileButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
ui->packName->setText(m_inst->name());
ui->changelogTextBrowser->setText(tr("This is a local modpack.\n"
"This can be updated only using a file in %1 format\n")
.arg(displayName()));
return;
}
ui->packName->setText(m_inst->getManagedPackName());
ui->packVersion->setText(m_inst->getManagedPackVersionName());
ui->packOrigin->setText(tr("Website: <a href=%1>%2</a> | Pack ID: %3 | Version ID: %4")
@@ -355,6 +371,8 @@ void ModrinthManagedPackPage::update()
void ModrinthManagedPackPage::updateFromFile()
{
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "Modrinth pack (*.mrpack *.zip)");
if (output.isEmpty())
return;
QMap<QString, QString> extra_info;
extra_info.insert("pack_id", m_inst->getManagedPackID());
extra_info.insert("pack_version_id", QString());
@@ -472,7 +490,7 @@ void FlameManagedPackPage::parseManagedPack()
QString FlameManagedPackPage::url() const
{
// FIXME: We should display the websiteUrl field, but this requires doing the API request first :(
return {};
return "https://www.curseforge.com/projects/" + m_inst->getManagedPackID();
}
void FlameManagedPackPage::suggestVersion()
@@ -519,6 +537,8 @@ void FlameManagedPackPage::update()
void FlameManagedPackPage::updateFromFile()
{
auto output = QFileDialog::getOpenFileUrl(this, tr("Choose update file"), QDir::homePath(), "CurseForge pack (*.zip)");
if (output.isEmpty())
return;
QMap<QString, QString> extra_info;
extra_info.insert("pack_id", m_inst->getManagedPackID());
+1 -1
View File
@@ -242,7 +242,7 @@ void ModFolderPage::updateMods(bool includeDeps)
if (m_instance != nullptr && m_instance->isRunning()) {
auto response =
CustomMessageBox::selectable(this, tr("Confirm Update"),
tr("If you update mods while the game is running may cause mod duplication and game crashes.\n"
tr("Updating mods while the game is running may cause mod duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
@@ -324,8 +324,7 @@ void ScreenshotsPage::onItemActivated(QModelIndex index)
if (!index.isValid())
return;
auto info = m_model->fileInfo(index);
QString fileName = info.absoluteFilePath();
DesktopServices::openFile(info.absoluteFilePath());
DesktopServices::openPath(info);
}
void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
@@ -352,7 +351,7 @@ void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected)
void ScreenshotsPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_folder, true);
DesktopServices::openPath(m_folder, true);
}
void ScreenshotsPage::on_actionUpload_triggered()
+2 -9
View File
@@ -295,13 +295,6 @@ void VersionPage::on_actionRemove_triggered()
m_container->refreshContainer();
}
void VersionPage::on_actionInstall_mods_triggered()
{
if (m_container) {
m_container->selectPage("mods");
}
}
void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
{
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"),
@@ -454,12 +447,12 @@ void VersionPage::on_actionAdd_Empty_triggered()
void VersionPage::on_actionLibrariesFolder_triggered()
{
DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true);
DesktopServices::openPath(m_inst->getLocalLibraryPath(), true);
}
void VersionPage::on_actionMinecraftFolder_triggered()
{
DesktopServices::openDirectory(m_inst->gameRoot(), true);
DesktopServices::openPath(m_inst->gameRoot(), true);
}
void VersionPage::versionCurrent(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
-1
View File
@@ -80,7 +80,6 @@ class VersionPage : public QMainWindow, public BasePage {
void on_actionAdd_Agents_triggered();
void on_actionRevert_triggered();
void on_actionEdit_triggered();
void on_actionInstall_mods_triggered();
void on_actionCustomize_triggered();
void on_actionDownload_All_triggered();
+2 -2
View File
@@ -207,7 +207,7 @@ void WorldListPage::on_actionRemove_triggered()
void WorldListPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true);
DesktopServices::openPath(m_worlds->dir().absolutePath(), true);
}
void WorldListPage::on_actionDatapacks_triggered()
@@ -223,7 +223,7 @@ void WorldListPage::on_actionDatapacks_triggered()
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true);
DesktopServices::openPath(FS::PathCombine(fullPath, "datapacks"), true);
}
void WorldListPage::on_actionReset_Icon_triggered()
@@ -207,6 +207,10 @@ void ResourceModel::loadEntry(QModelIndex& entry)
return;
versionRequestSucceeded(doc, pack, entry);
};
if (!callbacks.on_fail)
callbacks.on_fail = [](QString reason, int) {
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions:%1").arg(reason));
};
if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job)
runInfoJob(job);
@@ -230,6 +234,12 @@ void ResourceModel::loadEntry(QModelIndex& entry)
return;
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
};
if (!callbacks.on_abort)
callbacks.on_abort = [this] {
if (!s_running_models.constFind(this).value())
return;
qCritical() << tr("The request was aborted for an unknown reason");
};
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
runInfoJob(job);
@@ -200,6 +200,11 @@ void ResourcePage::updateUi()
}
if (current_pack->extraDataLoaded) {
if (current_pack->extraData.status == "archived") {
text += "<br><br>" + tr("<b>This project has been archived. It will not receive any further updates unless the author decides "
"to unarchive the project.</b>");
}
if (!current_pack->extraData.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
@@ -404,9 +409,9 @@ void ResourcePage::openUrl(const QUrl& url)
auto jump = [url, slug, model, view] {
for (int row = 0; row < model->rowCount({}); row++) {
const QModelIndex index = model->index(row);
const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack::Ptr>();
if (pack.slug == slug) {
if (pack->slug == slug) {
view->setCurrentIndex(index);
return;
}
@@ -170,6 +170,10 @@ void ListModel::performPaginatedSearch()
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Abborted");
};
static const FlameAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job;
@@ -34,6 +34,7 @@
*/
#include "FlamePage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_FlamePage.h"
#include <QKeyEvent>
@@ -193,6 +194,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start();
} else {
for (auto version : current.versions) {
@@ -20,6 +20,7 @@
#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QFileDialog>
#include <QWidget>
#include "FileSystem.h"
#include "ListModel.h"
@@ -56,6 +57,13 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
connect(ui->browseButton, &QPushButton::clicked, this, [this] {
auto path = listModel->getPath();
QString dir = QFileDialog::getExistingDirectory(this, tr("Select FTBApp instances directory"), path, QFileDialog::ShowDirsOnly);
if (!dir.isEmpty())
listModel->setPath(dir);
});
ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}
@@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<item row="2" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@@ -21,28 +21,7 @@
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QComboBox" name="sortByBox">
@@ -69,6 +48,54 @@
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseButton">
<property name="toolTip">
<string>Select FTBApp instances directory</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="viewfolder">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Note: If your FTB instances are not in the default location, select it using the button next to search.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
@@ -17,11 +17,13 @@
*/
#include "ListModel.h"
#include <qfileinfo.h>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QIcon>
#include <QProcessEnvironment>
#include "Application.h"
#include "FileSystem.h"
#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
@@ -29,7 +31,7 @@
namespace FTBImportAPP {
QString getPath()
QString getStaticPath()
{
QString partialPath;
#if defined(Q_OS_OSX)
@@ -42,14 +44,14 @@ QString getPath()
return FS::PathCombine(partialPath, ".ftba");
}
const QString ListModel::FTB_APP_PATH = getPath();
static const QString FTB_APP_PATH = FS::PathCombine(getStaticPath(), "instances");
void ListModel::update()
{
beginResetModel();
modpacks.clear();
QString instancesPath = FS::PathCombine(FTB_APP_PATH, "instances");
QString instancesPath = getPath();
if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) {
QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden,
QDirIterator::FollowSymlinks);
@@ -168,4 +170,17 @@ FilterModel::Sorting FilterModel::getCurrentSorting()
{
return currentSorting;
}
void ListModel::setPath(QString path)
{
APPLICATION->settings()->set("FTBAppInstancesPath", path);
update();
}
QString ListModel::getPath()
{
auto path = APPLICATION->settings()->get("FTBAppInstancesPath").toString();
if (path.isEmpty() || !QFileInfo(path).exists())
path = FTB_APP_PATH;
return path;
}
} // namespace FTBImportAPP
@@ -60,7 +60,8 @@ class ListModel : public QAbstractListModel {
void update();
static const QString FTB_APP_PATH;
QString getPath();
void setPath(QString path);
private:
ModpackList modpacks;
@@ -140,6 +140,10 @@ void ModpackListModel::performPaginatedSearch()
callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
callbacks.on_abort = [this] {
qCritical() << "Search task aborted by an unknown reason!";
searchRequestFailed("Aborted");
};
static const ModrinthAPI api;
if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
jobPtr = job;
@@ -35,6 +35,7 @@
*/
#include "ModrinthPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ModrinthPage.h"
#include "ModrinthModel.h"
@@ -182,6 +183,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start();
} else
updateUI();
@@ -235,6 +238,8 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
suggestCurrent();
});
QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { netJob->deleteLater(); });
connect(netJob, &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
netJob->start();
} else {
@@ -262,6 +267,11 @@ void ModrinthPage::updateUI()
text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author));
if (current.extraInfoLoaded) {
if (current.extra.status == "archived") {
text += "<br><br>" + tr("<b>This project has been archived. It will not receive any further updates unless the author decides "
"to unarchive the project.</b>");
}
if (!current.extra.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](Modrinth::DonationData& donate) -> QString {
@@ -11,43 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>Note: Modrinth modpacks are still in alpha phase. Some things may be rough on the edges, or not working at all! Use it with caution.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter ...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<item row="2" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +41,7 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
@@ -97,6 +61,24 @@
</item>
</layout>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter ...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
@@ -34,6 +34,7 @@
*/
#include "TechnicPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h"
@@ -208,6 +209,8 @@ void TechnicPage::suggestCurrent()
metadataLoaded();
});
connect(jobPtr.get(), &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
jobPtr = netJob;
jobPtr->start();
@@ -258,6 +261,8 @@ void TechnicPage::metadataLoaded()
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
connect(jobPtr.get(), &NetJob::failed,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
jobPtr = netJob;
jobPtr->start();
+1 -1
View File
@@ -43,7 +43,7 @@
QString BasicCatPack::path()
{
const auto now = QDate::currentDate();
const auto birthday = QDate(now.year(), 11, 30);
const auto birthday = QDate(now.year(), 11, 1);
const auto xmas = QDate(now.year(), 12, 25);
const auto halloween = QDate(now.year(), 10, 31);
+1 -1
View File
@@ -88,7 +88,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
}
{ // Description painting
auto description = index.data(UserDataTypes::DESCRIPTION).toString();
auto description = index.data(UserDataTypes::DESCRIPTION).toString().simplified();
QTextLayout text_layout(description, opt.font);
@@ -34,11 +34,11 @@ ThemeCustomizationWidget::ThemeCustomizationWidget(QWidget* parent) : QWidget(pa
connect(ui->backgroundCatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ThemeCustomizationWidget::applyCatTheme);
connect(ui->iconsFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getIconThemesFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getIconThemesFolder().path()); });
connect(ui->widgetStyleFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getApplicationThemesFolder().path()); });
connect(ui->catPackFolder, &QPushButton::clicked, this,
[] { DesktopServices::openDirectory(APPLICATION->themeManager()->getCatPacksFolder().path()); });
[] { DesktopServices::openPath(APPLICATION->themeManager()->getCatPacksFolder().path()); });
}
ThemeCustomizationWidget::~ThemeCustomizationWidget()
@@ -102,14 +102,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
auto full_entry_path = entry->getFullPath();
auto source_url = source;
connect(job, &NetJob::succeeded, this, [this, doc, full_entry_path, source_url, posInDocument] {
qDebug() << "Loaded resource at" << full_entry_path;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
QImage image(full_entry_path);
auto loadImage = [this, doc, full_entry_path, source_url, posInDocument](const QImage& image) {
doc->addResource(QTextDocument::ImageResource, source_url, image);
parseImage(doc, image, posInDocument);
@@ -121,6 +114,23 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
doc->setPageSize(size);
m_fetching_images.remove(source_url);
};
connect(job, &NetJob::succeeded, this, [this, full_entry_path, source_url, loadImage] {
qDebug() << "Loaded resource at:" << full_entry_path;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
QImage image(full_entry_path);
loadImage(image);
});
connect(job, &NetJob::failed, this, [this, full_entry_path, source_url, loadImage](QString reason) {
qWarning() << "Failed resource at:" << full_entry_path << " because:" << reason;
// If we flushed, don't proceed.
if (!m_fetching_images.contains(source_url))
return;
loadImage(QImage());
});
connect(job, &NetJob::finished, job, &NetJob::deleteLater);
@@ -436,8 +436,8 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar
}
{ // log debug program info
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME) << "Updater"
<< ", (c) 2022-2023 " << qPrintable(QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << qPrintable(BuildConfig.LAUNCHER_DISPLAYNAME + " Updater, " +
QString(BuildConfig.LAUNCHER_COPYRIGHT).replace("\n", ", "));
qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;