mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-06-12 21:27:44 +02:00
Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into change_version
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
@ -230,7 +231,8 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
setInstanceActionsEnabled(false);
|
||||
|
||||
// add a close button at the end of the main toolbar when running on gamescope / steam deck
|
||||
// FIXME: detect if we don't have server side decorations instead
|
||||
// this is only needed on gamescope because it defaults to an X11/XWayland session and
|
||||
// does not implement decorations
|
||||
if (qgetenv("XDG_CURRENT_DESKTOP") == "gamescope") {
|
||||
ui->mainToolBar->addAction(ui->actionCloseWindow);
|
||||
}
|
||||
@ -319,6 +321,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 +461,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 +1198,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 +1453,7 @@ void MainWindow::on_actionViewSelectedInstFolder_triggered()
|
||||
{
|
||||
if (m_selectedInstance) {
|
||||
QString str = m_selectedInstance->instanceRoot();
|
||||
DesktopServices::openDirectory(QDir(str).absolutePath());
|
||||
DesktopServices::openPath(QFileInfo(str));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,6 +205,8 @@ class MainWindow : public QMainWindow {
|
||||
|
||||
void globalSettingsClosed();
|
||||
|
||||
void setStatusBarVisibility(bool);
|
||||
|
||||
void lockToolbars(bool);
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
|
@ -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>
|
||||
|
@ -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()));
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include <QMimeData>
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
|
||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods, QString hash_type)
|
||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods), m_hash_type(hash_type)
|
||||
@ -60,8 +61,13 @@ BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, cons
|
||||
|
||||
qDebug() << "[Blocked Mods Dialog] Mods List: " << mods;
|
||||
|
||||
setupWatch();
|
||||
scanPaths();
|
||||
// defer setup of file system watchers until after the dialog is shown
|
||||
// this allows OS (namely macOS) permission prompts to show after the relevant dialog appears
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
setupWatch();
|
||||
scanPaths();
|
||||
update();
|
||||
});
|
||||
|
||||
this->setWindowTitle(title);
|
||||
ui->labelDescription->setText(text);
|
||||
@ -158,7 +164,8 @@ void BlockedModsDialog::update()
|
||||
|
||||
QString watching;
|
||||
for (auto& dir : m_watcher.directories()) {
|
||||
watching += QString("<a href=\"%1\">%1</a><br/>").arg(dir);
|
||||
QUrl fileURL = QUrl::fromLocalFile(dir);
|
||||
watching += QString("<a href=\"%1\">%2</a><br/>").arg(fileURL.toString(), dir);
|
||||
}
|
||||
|
||||
ui->textBrowserWatched->setText(watching);
|
||||
@ -194,6 +201,10 @@ void BlockedModsDialog::setupWatch()
|
||||
void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
|
||||
{
|
||||
auto to_watch = QFileInfo(path);
|
||||
if (!to_watch.isReadable()) {
|
||||
qWarning() << "[Blocked Mods Dialog] Failed to add Watch Path (unable to read):" << path;
|
||||
return;
|
||||
}
|
||||
auto to_watch_path = to_watch.canonicalFilePath();
|
||||
if (m_watcher.directories().contains(to_watch_path))
|
||||
return; // don't watch the same path twice (no loops!)
|
||||
|
@ -146,7 +146,7 @@ void ExportInstanceDialog::doExport()
|
||||
return;
|
||||
}
|
||||
|
||||
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true);
|
||||
auto task = makeShared<MMCZip::ExportToZipTask>(output, m_instance->instanceRoot(), files, "", true, true);
|
||||
|
||||
connect(task.get(), &Task::failed, this,
|
||||
[this, output](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
|
||||
|
@ -47,11 +47,18 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
|
||||
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
setWindowTitle(tr("Export Modrinth Pack"));
|
||||
ui->summary->setText(instance->settings()->get("ExportSummary").toString());
|
||||
|
||||
ui->authorLabel->hide();
|
||||
ui->author->hide();
|
||||
|
||||
ui->summary->setPlainText(instance->settings()->get("ExportSummary").toString());
|
||||
} else {
|
||||
setWindowTitle(tr("Export CurseForge Pack"));
|
||||
ui->summaryLabel->setText(tr("&Author"));
|
||||
ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
|
||||
|
||||
ui->summaryLabel->hide();
|
||||
ui->summary->hide();
|
||||
|
||||
ui->author->setText(instance->settings()->get("ExportAuthor").toString());
|
||||
}
|
||||
|
||||
// ensure a valid pack is generated
|
||||
@ -108,9 +115,13 @@ void ExportPackDialog::done(int result)
|
||||
auto settings = instance->settings();
|
||||
settings->set("ExportName", ui->name->text());
|
||||
settings->set("ExportVersion", ui->version->text());
|
||||
settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
|
||||
settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
|
||||
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
|
||||
settings->set("ExportSummary", ui->summary->toPlainText());
|
||||
else
|
||||
settings->set("ExportAuthor", ui->author->text());
|
||||
|
||||
if (result == Accepted) {
|
||||
const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
|
||||
const QString filename = FS::RemoveInvalidFilenameChars(name);
|
||||
@ -134,10 +145,10 @@ void ExportPackDialog::done(int result)
|
||||
|
||||
Task* task;
|
||||
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
|
||||
output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->toPlainText(), ui->optionalFiles->isChecked(),
|
||||
instance, output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
} else {
|
||||
task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
|
||||
task = new FlamePackExportTask(name, ui->version->text(), ui->author->text(), ui->optionalFiles->isChecked(), instance, output,
|
||||
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>650</width>
|
||||
<height>510</height>
|
||||
<height>532</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
@ -19,21 +19,8 @@
|
||||
<property name="title">
|
||||
<string>&Description</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="summaryLabel">
|
||||
<property name="text">
|
||||
<string>&Summary</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>summary</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="summary"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>&Name</string>
|
||||
@ -43,7 +30,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="name"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="versionLabel">
|
||||
<property name="text">
|
||||
<string>&Version</string>
|
||||
@ -53,16 +43,43 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="name"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="version">
|
||||
<property name="text">
|
||||
<string>1.0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="summaryLabel">
|
||||
<property name="text">
|
||||
<string>&Summary</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>summary</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="summary">
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="authorLabel">
|
||||
<property name="text">
|
||||
<string>&Author</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>author</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="author"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -124,6 +141,7 @@
|
||||
<tabstop>name</tabstop>
|
||||
<tabstop>version</tabstop>
|
||||
<tabstop>summary</tabstop>
|
||||
<tabstop>author</tabstop>
|
||||
<tabstop>files</tabstop>
|
||||
<tabstop>optionalFiles</tabstop>
|
||||
</tabstops>
|
||||
|
@ -159,5 +159,5 @@ IconPickerDialog::~IconPickerDialog()
|
||||
|
||||
void IconPickerDialog::openFolder()
|
||||
{
|
||||
DesktopServices::openDirectory(APPLICATION->icons()->getDirectory(), true);
|
||||
DesktopServices::openPath(APPLICATION->icons()->getDirectory(), true);
|
||||
}
|
||||
|
@ -37,7 +37,7 @@
|
||||
#include "ui_MSALoginDialog.h"
|
||||
|
||||
#include "DesktopServices.h"
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
#include "minecraft/auth/AuthFlow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
@ -47,30 +47,29 @@
|
||||
MSALoginDialog::MSALoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::MSALoginDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->progressBar->setVisible(false);
|
||||
ui->actionButton->setVisible(false);
|
||||
// ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
ui->cancel->setEnabled(false);
|
||||
ui->link->setVisible(false);
|
||||
ui->copy->setVisible(false);
|
||||
ui->progressBar->setVisible(false);
|
||||
|
||||
connect(ui->cancel, &QPushButton::pressed, this, &QDialog::reject);
|
||||
connect(ui->copy, &QPushButton::pressed, this, &MSALoginDialog::copyUrl);
|
||||
}
|
||||
|
||||
int MSALoginDialog::exec()
|
||||
{
|
||||
setUserInputsEnabled(false);
|
||||
ui->progressBar->setVisible(true);
|
||||
|
||||
// Setup the login task and start it
|
||||
m_account = MinecraftAccount::createBlankMSA();
|
||||
m_loginTask = m_account->loginMSA();
|
||||
connect(m_loginTask.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
||||
connect(m_loginTask.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
||||
connect(m_loginTask.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
||||
connect(m_loginTask.get(), &Task::progress, this, &MSALoginDialog::onTaskProgress);
|
||||
connect(m_loginTask.get(), &AccountTask::showVerificationUriAndCode, this, &MSALoginDialog::showVerificationUriAndCode);
|
||||
connect(m_loginTask.get(), &AccountTask::hideVerificationUriAndCode, this, &MSALoginDialog::hideVerificationUriAndCode);
|
||||
connect(&m_externalLoginTimer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
|
||||
m_loginTask->start();
|
||||
m_task = m_account->login(m_using_device_code);
|
||||
connect(m_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
||||
connect(m_task.get(), &Task::succeeded, this, &MSALoginDialog::onTaskSucceeded);
|
||||
connect(m_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
||||
connect(m_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
|
||||
connect(m_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
|
||||
connect(ui->cancel, &QPushButton::pressed, m_task.get(), &Task::abort);
|
||||
connect(&m_external_timer, &QTimer::timeout, this, &MSALoginDialog::externalLoginTick);
|
||||
m_task->start();
|
||||
|
||||
return QDialog::exec();
|
||||
}
|
||||
@ -80,60 +79,6 @@ MSALoginDialog::~MSALoginDialog()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MSALoginDialog::externalLoginTick()
|
||||
{
|
||||
m_externalLoginElapsed++;
|
||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
||||
ui->progressBar->repaint();
|
||||
|
||||
if (m_externalLoginElapsed >= m_externalLoginTimeout) {
|
||||
m_externalLoginTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn)
|
||||
{
|
||||
m_externalLoginElapsed = 0;
|
||||
m_externalLoginTimeout = expiresIn;
|
||||
|
||||
m_externalLoginTimer.setInterval(1000);
|
||||
m_externalLoginTimer.setSingleShot(false);
|
||||
m_externalLoginTimer.start();
|
||||
|
||||
ui->progressBar->setMaximum(expiresIn);
|
||||
ui->progressBar->setValue(m_externalLoginElapsed);
|
||||
|
||||
QString urlString = uri.toString();
|
||||
QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString);
|
||||
if (urlString == "https://www.microsoft.com/link" && !code.isEmpty()) {
|
||||
urlString += QString("?otc=%1").arg(code);
|
||||
DesktopServices::openUrl(urlString);
|
||||
ui->label->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
|
||||
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
|
||||
.arg(linkString, code));
|
||||
} else {
|
||||
ui->label->setText(
|
||||
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
|
||||
}
|
||||
ui->actionButton->setVisible(true);
|
||||
connect(ui->actionButton, &QPushButton::clicked, [=]() {
|
||||
DesktopServices::openUrl(uri);
|
||||
QClipboard* cb = QApplication::clipboard();
|
||||
cb->setText(code);
|
||||
});
|
||||
}
|
||||
|
||||
void MSALoginDialog::hideVerificationUriAndCode()
|
||||
{
|
||||
m_externalLoginTimer.stop();
|
||||
ui->actionButton->setVisible(false);
|
||||
}
|
||||
|
||||
void MSALoginDialog::setUserInputsEnabled(bool enable)
|
||||
{
|
||||
ui->buttonBox->setEnabled(enable);
|
||||
}
|
||||
|
||||
void MSALoginDialog::onTaskFailed(const QString& reason)
|
||||
{
|
||||
// Set message
|
||||
@ -146,12 +91,7 @@ void MSALoginDialog::onTaskFailed(const QString& reason)
|
||||
processed += "<br />";
|
||||
}
|
||||
}
|
||||
ui->label->setText(processed);
|
||||
|
||||
// Re-enable user-interaction
|
||||
setUserInputsEnabled(true);
|
||||
ui->progressBar->setVisible(false);
|
||||
ui->actionButton->setVisible(false);
|
||||
ui->message->setText(processed);
|
||||
}
|
||||
|
||||
void MSALoginDialog::onTaskSucceeded()
|
||||
@ -161,22 +101,81 @@ void MSALoginDialog::onTaskSucceeded()
|
||||
|
||||
void MSALoginDialog::onTaskStatus(const QString& status)
|
||||
{
|
||||
ui->label->setText(status);
|
||||
}
|
||||
|
||||
void MSALoginDialog::onTaskProgress(qint64 current, qint64 total)
|
||||
{
|
||||
ui->progressBar->setMaximum(total);
|
||||
ui->progressBar->setValue(current);
|
||||
ui->message->setText(status);
|
||||
ui->cancel->setEnabled(false);
|
||||
ui->link->setVisible(false);
|
||||
ui->copy->setVisible(false);
|
||||
ui->progressBar->setVisible(false);
|
||||
}
|
||||
|
||||
// Public interface
|
||||
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg)
|
||||
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent, QString msg, bool usingDeviceCode)
|
||||
{
|
||||
MSALoginDialog dlg(parent);
|
||||
dlg.ui->label->setText(msg);
|
||||
dlg.m_using_device_code = usingDeviceCode;
|
||||
dlg.ui->message->setText(msg);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
return dlg.m_account;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
|
||||
{
|
||||
ui->cancel->setEnabled(true);
|
||||
ui->link->setVisible(true);
|
||||
ui->copy->setVisible(true);
|
||||
DesktopServices::openUrl(url);
|
||||
ui->link->setText(url.toDisplayString());
|
||||
ui->message->setText(
|
||||
tr("Browser opened to complete the login process."
|
||||
"<br /><br />"
|
||||
"If your browser hasn't opened, please manually open the below link in your browser:"));
|
||||
}
|
||||
|
||||
void MSALoginDialog::copyUrl()
|
||||
{
|
||||
QClipboard* cb = QApplication::clipboard();
|
||||
cb->setText(ui->link->text());
|
||||
}
|
||||
|
||||
void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn)
|
||||
{
|
||||
m_external_elapsed = 0;
|
||||
m_external_timeout = expiresIn;
|
||||
|
||||
m_external_timer.setInterval(1000);
|
||||
m_external_timer.setSingleShot(false);
|
||||
m_external_timer.start();
|
||||
|
||||
ui->progressBar->setMaximum(expiresIn);
|
||||
ui->progressBar->setValue(m_external_elapsed);
|
||||
|
||||
QString linkString = QString("<a href=\"%1\">%2</a>").arg(url, url);
|
||||
if (url == "https://www.microsoft.com/link" && !code.isEmpty()) {
|
||||
url += QString("?otc=%1").arg(code);
|
||||
ui->message->setText(tr("<p>Please login in the opened browser. If no browser was opened, please open up %1 in "
|
||||
"a browser and put in the code <b>%2</b> to proceed with login.</p>")
|
||||
.arg(linkString, code));
|
||||
} else {
|
||||
ui->message->setText(
|
||||
tr("<p>Please open up %1 in a browser and put in the code <b>%2</b> to proceed with login.</p>").arg(linkString, code));
|
||||
}
|
||||
ui->cancel->setEnabled(true);
|
||||
ui->link->setVisible(true);
|
||||
ui->copy->setVisible(true);
|
||||
ui->progressBar->setVisible(true);
|
||||
DesktopServices::openUrl(url);
|
||||
ui->link->setText(code);
|
||||
}
|
||||
|
||||
void MSALoginDialog::externalLoginTick()
|
||||
{
|
||||
m_external_elapsed++;
|
||||
ui->progressBar->setValue(m_external_elapsed);
|
||||
ui->progressBar->repaint();
|
||||
|
||||
if (m_external_elapsed >= m_external_timeout) {
|
||||
m_external_timer.stop();
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@
|
||||
#include <QtCore/QEventLoop>
|
||||
#include <QtWidgets/QDialog>
|
||||
|
||||
#include "minecraft/auth/AuthFlow.h"
|
||||
#include "minecraft/auth/MinecraftAccount.h"
|
||||
|
||||
namespace Ui {
|
||||
@ -31,29 +32,29 @@ class MSALoginDialog : public QDialog {
|
||||
public:
|
||||
~MSALoginDialog();
|
||||
|
||||
static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
|
||||
static MinecraftAccountPtr newAccount(QWidget* parent, QString message, bool usingDeviceCode = false);
|
||||
int exec() override;
|
||||
|
||||
private:
|
||||
explicit MSALoginDialog(QWidget* parent = 0);
|
||||
|
||||
void setUserInputsEnabled(bool enable);
|
||||
|
||||
protected slots:
|
||||
void onTaskFailed(const QString& reason);
|
||||
void onTaskSucceeded();
|
||||
void onTaskStatus(const QString& status);
|
||||
void onTaskProgress(qint64 current, qint64 total);
|
||||
void showVerificationUriAndCode(const QUrl& uri, const QString& code, int expiresIn);
|
||||
void hideVerificationUriAndCode();
|
||||
|
||||
void authorizeWithBrowser(const QUrl& url);
|
||||
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
|
||||
void copyUrl();
|
||||
void externalLoginTick();
|
||||
|
||||
private:
|
||||
Ui::MSALoginDialog* ui;
|
||||
MinecraftAccountPtr m_account;
|
||||
shared_qobject_ptr<AccountTask> m_loginTask;
|
||||
QTimer m_externalLoginTimer;
|
||||
int m_externalLoginElapsed = 0;
|
||||
int m_externalLoginTimeout = 0;
|
||||
shared_qobject_ptr<AuthFlow> m_task;
|
||||
|
||||
int m_external_elapsed;
|
||||
int m_external_timeout;
|
||||
QTimer m_external_timer;
|
||||
|
||||
bool m_using_device_code = false;
|
||||
};
|
||||
|
@ -7,11 +7,11 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>491</width>
|
||||
<height>143</height>
|
||||
<height>208</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
@ -21,15 +21,28 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<widget class="QLabel" name="message">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Message label placeholder.
|
||||
|
||||
aaaaa</string>
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@ -38,6 +51,28 @@ aaaaa</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="linkLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="link">
|
||||
<property name="readOnly">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="copy">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="copy">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
@ -49,25 +84,11 @@ aaaaa</string>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="actionButton">
|
||||
<property name="text">
|
||||
<string>Open page and copy code</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QPushButton" name="cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -214,19 +214,25 @@ void ModUpdateDialog::checkCandidates()
|
||||
}
|
||||
static FlameAPI api;
|
||||
|
||||
auto getRequiredBy = depTask->getRequiredBy();
|
||||
auto dependencyExtraInfo = depTask->getExtraInfo();
|
||||
|
||||
for (auto dep : depTask->getDependecies()) {
|
||||
auto changelog = dep->version.changelog;
|
||||
if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
|
||||
changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
|
||||
auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
|
||||
CheckUpdateTask::UpdatableMod updatable = {
|
||||
dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type,
|
||||
changelog, dep->pack->provider, download_task
|
||||
};
|
||||
auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString());
|
||||
CheckUpdateTask::UpdatableMod updatable = { dep->pack->name,
|
||||
dep->version.hash,
|
||||
"",
|
||||
dep->version.version,
|
||||
dep->version.version_type,
|
||||
changelog,
|
||||
dep->pack->provider,
|
||||
download_task,
|
||||
!extraInfo.maybe_installed };
|
||||
|
||||
appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
|
||||
appendMod(updatable, extraInfo.required_by);
|
||||
m_tasks.insert(updatable.name, updatable.download);
|
||||
}
|
||||
}
|
||||
@ -328,6 +334,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 +349,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 +404,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 {
|
||||
@ -406,7 +418,10 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
|
||||
void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
|
||||
{
|
||||
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
|
||||
item_top->setCheckState(0, Qt::CheckState::Checked);
|
||||
item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||
if (!info.enabled) {
|
||||
item_top->setToolTip(0, tr("Mod was disabled as it may be already instaled."));
|
||||
}
|
||||
item_top->setText(0, info.name);
|
||||
item_top->setExpanded(true);
|
||||
|
||||
@ -437,6 +452,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);
|
||||
|
@ -97,6 +97,9 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
|
||||
ui->verticalLayout->insertWidget(2, m_container);
|
||||
|
||||
m_container->addButtons(m_buttons);
|
||||
connect(m_container, &PageContainer::selectedPageChanged, this, [this](BasePage* previous, BasePage* selected) {
|
||||
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(creationTask && !instName().isEmpty());
|
||||
});
|
||||
|
||||
// Bonk Qt over its stupid head and make sure it understands which button is the default one...
|
||||
// See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button
|
||||
|
@ -1,8 +1,6 @@
|
||||
#include "OfflineLoginDialog.h"
|
||||
#include "ui_OfflineLoginDialog.h"
|
||||
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
OfflineLoginDialog::OfflineLoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::OfflineLoginDialog)
|
||||
@ -28,7 +26,7 @@ void OfflineLoginDialog::accept()
|
||||
|
||||
// Setup the login task and start it
|
||||
m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
|
||||
m_loginTask = m_account->loginOffline();
|
||||
m_loginTask = m_account->login();
|
||||
connect(m_loginTask.get(), &Task::failed, this, &OfflineLoginDialog::onTaskFailed);
|
||||
connect(m_loginTask.get(), &Task::succeeded, this, &OfflineLoginDialog::onTaskSucceeded);
|
||||
connect(m_loginTask.get(), &Task::status, this, &OfflineLoginDialog::onTaskStatus);
|
||||
|
@ -45,8 +45,9 @@
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
||||
#include <Application.h>
|
||||
#include "minecraft/auth/AuthRequest.h"
|
||||
#include "minecraft/auth/Parsers.h"
|
||||
#include "net/StaticHeaderProxy.h"
|
||||
#include "net/Upload.h"
|
||||
|
||||
ProfileSetupDialog::ProfileSetupDialog(MinecraftAccountPtr accountToSetup, QWidget* parent)
|
||||
: QDialog(parent), m_accountToSetup(accountToSetup), ui(new Ui::ProfileSetupDialog)
|
||||
@ -150,28 +151,27 @@ void ProfileSetupDialog::checkName(const QString& name)
|
||||
currentCheck = name;
|
||||
isChecking = true;
|
||||
|
||||
auto token = m_accountToSetup->accessToken();
|
||||
QUrl url(QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name));
|
||||
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||
{ "Accept", "application/json" },
|
||||
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
|
||||
|
||||
auto url = QString("https://api.minecraftservices.com/minecraft/profile/name/%1/available").arg(name);
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
||||
m_check_response.reset(new QByteArray());
|
||||
if (m_check_task)
|
||||
disconnect(m_check_task.get(), nullptr, this, nullptr);
|
||||
m_check_task = Net::Download::makeByteArray(url, m_check_response);
|
||||
m_check_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::checkFinished);
|
||||
requestor->get(request);
|
||||
connect(m_check_task.get(), &Task::finished, this, &ProfileSetupDialog::checkFinished);
|
||||
|
||||
m_check_task->setNetwork(APPLICATION->network());
|
||||
m_check_task->start();
|
||||
}
|
||||
|
||||
void ProfileSetupDialog::checkFinished(QNetworkReply::NetworkError error,
|
||||
QByteArray profileData,
|
||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
||||
void ProfileSetupDialog::checkFinished()
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
if (error == QNetworkReply::NoError) {
|
||||
auto doc = QJsonDocument::fromJson(profileData);
|
||||
if (m_check_task->error() == QNetworkReply::NoError) {
|
||||
auto doc = QJsonDocument::fromJson(*m_check_response);
|
||||
auto root = doc.object();
|
||||
auto statusValue = root.value("status").toString("INVALID");
|
||||
if (statusValue == "AVAILABLE") {
|
||||
@ -195,20 +195,22 @@ void ProfileSetupDialog::setupProfile(const QString& profileName)
|
||||
return;
|
||||
}
|
||||
|
||||
auto token = m_accountToSetup->accessToken();
|
||||
|
||||
auto url = QString("https://api.minecraftservices.com/minecraft/profile");
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
||||
|
||||
QString payloadTemplate("{\"profileName\":\"%1\"}");
|
||||
auto profileData = payloadTemplate.arg(profileName).toUtf8();
|
||||
|
||||
AuthRequest* requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &ProfileSetupDialog::setupProfileFinished);
|
||||
requestor->post(request, profileData);
|
||||
QUrl url("https://api.minecraftservices.com/minecraft/profile");
|
||||
auto headers = QList<Net::HeaderPair>{ { "Content-Type", "application/json" },
|
||||
{ "Accept", "application/json" },
|
||||
{ "Authorization", QString("Bearer %1").arg(m_accountToSetup->accessToken()).toUtf8() } };
|
||||
|
||||
m_profile_response.reset(new QByteArray());
|
||||
m_profile_task = Net::Upload::makeByteArray(url, m_profile_response, payloadTemplate.arg(profileName).toUtf8());
|
||||
m_profile_task->addHeaderProxy(new Net::StaticHeaderProxy(headers));
|
||||
|
||||
connect(m_profile_task.get(), &Task::finished, this, &ProfileSetupDialog::setupProfileFinished);
|
||||
|
||||
m_profile_task->setNetwork(APPLICATION->network());
|
||||
m_profile_task->start();
|
||||
|
||||
isWorking = true;
|
||||
|
||||
auto button = ui->buttonBox->button(QDialogButtonBox::Cancel);
|
||||
@ -244,22 +246,17 @@ struct MojangError {
|
||||
|
||||
} // namespace
|
||||
|
||||
void ProfileSetupDialog::setupProfileFinished(QNetworkReply::NetworkError error,
|
||||
QByteArray errorData,
|
||||
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
|
||||
void ProfileSetupDialog::setupProfileFinished()
|
||||
{
|
||||
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
|
||||
requestor->deleteLater();
|
||||
|
||||
isWorking = false;
|
||||
if (error == QNetworkReply::NoError) {
|
||||
if (m_profile_task->error() == QNetworkReply::NoError) {
|
||||
/*
|
||||
* data contains the profile in the response
|
||||
* ... we could parse it and update the account, but let's just return back to the normal login flow instead...
|
||||
*/
|
||||
accept();
|
||||
} else {
|
||||
auto parsedError = MojangError::fromJSON(errorData);
|
||||
auto parsedError = MojangError::fromJSON(*m_profile_response);
|
||||
ui->errorLabel->setVisible(true);
|
||||
ui->errorLabel->setText(tr("The server returned the following error:") + "\n\n" + parsedError.errorMessage);
|
||||
qDebug() << parsedError.rawError;
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
#include <minecraft/auth/MinecraftAccount.h>
|
||||
#include <memory>
|
||||
#include "net/Download.h"
|
||||
#include "net/Upload.h"
|
||||
|
||||
namespace Ui {
|
||||
class ProfileSetupDialog;
|
||||
@ -40,10 +42,10 @@ class ProfileSetupDialog : public QDialog {
|
||||
void on_buttonBox_rejected();
|
||||
|
||||
void nameEdited(const QString& name);
|
||||
void checkFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
||||
void startCheck();
|
||||
|
||||
void setupProfileFinished(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers);
|
||||
void checkFinished();
|
||||
void setupProfileFinished();
|
||||
|
||||
protected:
|
||||
void scheduleCheck(const QString& name);
|
||||
@ -67,4 +69,10 @@ class ProfileSetupDialog : public QDialog {
|
||||
QString currentCheck;
|
||||
|
||||
QTimer checkStartTimer;
|
||||
|
||||
std::shared_ptr<QByteArray> m_check_response;
|
||||
Net::Download::Ptr m_check_task;
|
||||
|
||||
std::shared_ptr<QByteArray> m_profile_response;
|
||||
Net::Upload::Ptr m_profile_task;
|
||||
};
|
||||
|
@ -132,7 +132,7 @@ void ResourceDownloadDialog::confirm()
|
||||
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
|
||||
confirm_dialog->retranslateUi(resourcesString());
|
||||
|
||||
QHash<QString, QStringList> getRequiredBy;
|
||||
QHash<QString, GetModDependenciesTask::PackDependencyExtraInfo> dependencyExtraInfo;
|
||||
if (auto task = getModDependenciesTask(); task) {
|
||||
connect(task.get(), &Task::failed, this,
|
||||
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
|
||||
@ -157,7 +157,7 @@ void ResourceDownloadDialog::confirm()
|
||||
} else {
|
||||
for (auto dep : task->getDependecies())
|
||||
addResource(dep->pack, dep->version);
|
||||
getRequiredBy = task->getRequiredBy();
|
||||
dependencyExtraInfo = task->getExtraInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,9 +166,10 @@ void ResourceDownloadDialog::confirm()
|
||||
return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0;
|
||||
});
|
||||
for (auto& task : selected) {
|
||||
auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString());
|
||||
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
|
||||
ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()),
|
||||
task->getVersion().version_type.toString() });
|
||||
ProviderCaps.name(task->getProvider()), extraInfo.required_by,
|
||||
task->getVersion().version_type.toString(), !extraInfo.maybe_installed });
|
||||
}
|
||||
|
||||
if (confirm_dialog->exec()) {
|
||||
|
@ -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);
|
||||
@ -34,8 +35,11 @@ auto ReviewMessageBox::create(QWidget* parent, QString&& title, QString&& icon)
|
||||
void ReviewMessageBox::appendResource(ResourceInformation&& info)
|
||||
{
|
||||
auto itemTop = new QTreeWidgetItem(ui->modTreeWidget);
|
||||
itemTop->setCheckState(0, Qt::CheckState::Checked);
|
||||
itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||
itemTop->setText(0, info.name);
|
||||
if (!info.enabled) {
|
||||
itemTop->setToolTip(0, tr("Mod was disabled as it may be already instaled."));
|
||||
}
|
||||
|
||||
auto filenameItem = new QTreeWidgetItem(itemTop);
|
||||
filenameItem->setText(0, tr("Filename: %1").arg(info.filename));
|
||||
@ -75,6 +79,8 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
|
||||
}
|
||||
|
||||
itemTop->insertChildren(childIndx++, { requiredByItem });
|
||||
ui->toggleDepsButton->show();
|
||||
m_deps << itemTop;
|
||||
}
|
||||
|
||||
auto versionTypeItem = new QTreeWidgetItem(itemTop);
|
||||
@ -108,3 +114,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);
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
namespace Ui {
|
||||
class ReviewMessageBox;
|
||||
@ -19,6 +20,7 @@ class ReviewMessageBox : public QDialog {
|
||||
QString provider;
|
||||
QStringList required_by;
|
||||
QString version_type;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
void appendResource(ResourceInformation&& info);
|
||||
@ -28,8 +30,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;
|
||||
};
|
||||
|
@ -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,16 +458,18 @@ 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)
|
||||
widWidth = m_catPixmap.width();
|
||||
if (m_catPixmap.height() < widHeight)
|
||||
widHeight = m_catPixmap.height();
|
||||
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio);
|
||||
auto pixmap = m_catPixmap.scaled(widWidth, widHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
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)
|
||||
@ -480,32 +482,42 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event)
|
||||
|
||||
if (model()->rowCount() == 0) {
|
||||
painter.save();
|
||||
const QString line1 = tr("Welcome!");
|
||||
const QString line2 = tr("Click \"Add Instance\" to get started.");
|
||||
auto rect = this->viewport()->rect();
|
||||
auto font = option.font;
|
||||
font.setPointSize(37);
|
||||
painter.setFont(font);
|
||||
auto fm = painter.fontMetrics();
|
||||
QString emptyString = tr("Welcome!") + "\n" + tr("Click \"Add Instance\" to get started.");
|
||||
|
||||
if (rect.height() <= (fm.height() * 5) || rect.width() <= fm.horizontalAdvance(line2)) {
|
||||
auto s = rect.height() / (5. * fm.height());
|
||||
auto sx = rect.width() * 1. / fm.horizontalAdvance(line2);
|
||||
if (s >= sx)
|
||||
s = sx;
|
||||
auto ps = font.pointSize() * s;
|
||||
if (ps <= 0)
|
||||
ps = 1;
|
||||
font.setPointSize(ps);
|
||||
painter.setFont(font);
|
||||
fm = painter.fontMetrics();
|
||||
// calculate the rect for the overlay
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
QFont font("sans", 20);
|
||||
font.setBold(true);
|
||||
|
||||
QRect bounds = viewport()->geometry();
|
||||
bounds.moveTop(0);
|
||||
auto innerBounds = bounds;
|
||||
innerBounds.adjust(10, 10, -10, -10);
|
||||
|
||||
QColor background = QApplication::palette().color(QPalette::WindowText);
|
||||
QColor foreground = QApplication::palette().color(QPalette::Base);
|
||||
foreground.setAlpha(190);
|
||||
painter.setFont(font);
|
||||
auto fontMetrics = painter.fontMetrics();
|
||||
auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
|
||||
textRect.moveCenter(bounds.center());
|
||||
|
||||
auto wrapRect = textRect;
|
||||
wrapRect.adjust(-10, -10, 10, 10);
|
||||
|
||||
// check if we are allowed to draw in our area
|
||||
if (!event->rect().intersects(wrapRect)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// text
|
||||
rect.setTop(rect.top() + fm.height() * 1.5);
|
||||
painter.drawText(rect, Qt::AlignHCenter, line1);
|
||||
rect.setTop(rect.top() + fm.height());
|
||||
painter.drawText(rect, Qt::AlignHCenter, line2);
|
||||
painter.setBrush(QBrush(background));
|
||||
painter.setPen(foreground);
|
||||
painter.drawRoundedRect(wrapRect, 5.0, 5.0);
|
||||
|
||||
painter.setPen(foreground);
|
||||
painter.setFont(font);
|
||||
painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
|
||||
|
||||
painter.restore();
|
||||
return;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -40,6 +40,7 @@
|
||||
|
||||
#include <QItemSelectionModel>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
@ -134,8 +135,19 @@ void AccountListPage::listChanged()
|
||||
|
||||
void AccountListPage::on_actionAddMicrosoft_triggered()
|
||||
{
|
||||
MinecraftAccountPtr account =
|
||||
MSALoginDialog::newAccount(this, tr("Please enter your Mojang account email and password to add your account."));
|
||||
QMessageBox box(this);
|
||||
box.setWindowTitle(tr("Add account"));
|
||||
box.setText(tr("How do you want to login?"));
|
||||
box.setIcon(QMessageBox::Question);
|
||||
auto deviceCode = box.addButton(tr("Legacy"), QMessageBox::ButtonRole::YesRole);
|
||||
auto authCode = box.addButton(tr("Recommended"), QMessageBox::ButtonRole::NoRole);
|
||||
auto cancel = box.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole);
|
||||
box.setDefaultButton(authCode);
|
||||
box.exec();
|
||||
if ((box.clickedButton() != deviceCode && box.clickedButton() != authCode) || box.clickedButton() == cancel)
|
||||
return;
|
||||
MinecraftAccountPtr account = MSALoginDialog::newAccount(
|
||||
this, tr("Please enter your Mojang account email and password to add your account."), box.clickedButton() == deviceCode);
|
||||
|
||||
if (account) {
|
||||
m_accounts->addAccount(account);
|
||||
|
@ -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());
|
||||
|
@ -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>
|
||||
@ -237,7 +237,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<spacer name="verticalSpacer_FeaturesTab">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
@ -251,7 +251,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="generalTab">
|
||||
<widget class="QWidget" name="userInterfaceTab">
|
||||
<attribute name="title">
|
||||
<string>User Interface</string>
|
||||
</attribute>
|
||||
@ -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">
|
||||
@ -326,7 +374,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="generalTabSpacer">
|
||||
<spacer name="verticalSpacer_UserInterfaceTab">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
|
@ -109,6 +109,7 @@ void MinecraftPage::applySettings()
|
||||
s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
|
||||
s->set("EnableMangoHud", ui->enableMangoHud->isChecked());
|
||||
s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
|
||||
s->set("UseZink", ui->useZink->isChecked());
|
||||
|
||||
// Game time
|
||||
s->set("ShowGameTime", ui->showGameTime->isChecked());
|
||||
@ -151,6 +152,7 @@ void MinecraftPage::loadSettings()
|
||||
ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());
|
||||
ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool());
|
||||
ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool());
|
||||
ui->useZink->setChecked(s->get("UseZink").toBool());
|
||||
|
||||
#if !defined(Q_OS_LINUX)
|
||||
ui->perfomanceGroupBox->setVisible(false);
|
||||
|
@ -206,7 +206,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="onlineFixes">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html></string>
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>Current fixes include: skin and online mode support.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable online fixes (experimental)</string>
|
||||
@ -309,6 +309,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useZink">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Use Zink, a Mesa OpenGL driver that implements OpenGL on top of Vulkan. Performance may vary depending on the situation. Note: If no suitable Vulkan driver is found, software rendering will be used.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Zink</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -88,6 +88,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
|
||||
};
|
||||
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
|
||||
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
|
||||
connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra);
|
||||
|
||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
|
||||
|
||||
@ -290,12 +291,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)
|
||||
|
@ -232,10 +232,13 @@ void InstanceSettingsPage::applySettings()
|
||||
m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
|
||||
m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked());
|
||||
m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
|
||||
m_settings->set("UseZink", ui->useZink->isChecked());
|
||||
|
||||
} else {
|
||||
m_settings->reset("EnableFeralGamemode");
|
||||
m_settings->reset("EnableMangoHud");
|
||||
m_settings->reset("UseDiscreteGpu");
|
||||
m_settings->reset("UseZink");
|
||||
}
|
||||
|
||||
// Game time
|
||||
@ -346,7 +349,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
|
||||
@ -354,6 +357,7 @@ void InstanceSettingsPage::loadSettings()
|
||||
ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool());
|
||||
ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool());
|
||||
ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
|
||||
ui->useZink->setChecked(m_settings->get("UseZink").toBool());
|
||||
|
||||
#if !defined(Q_OS_LINUX)
|
||||
ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false);
|
||||
|
@ -567,6 +567,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useZink">
|
||||
<property name="toolTip">
|
||||
<string>Use Zink, a Mesa OpenGL driver that implements OpenGL on top of Vulkan. Performance may vary depending on the situation. Note: If no suitable Vulkan driver is found, software rendering will be used.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use Zink</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -605,7 +615,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="onlineFixes">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>This currently allows modern skins to be used.</p></body></html></string>
|
||||
<string><html><head/><body><p>Emulates usages of old online services which are no longer operating.</p><p>Current fixes include: skin and online mode support.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable online fixes (experimental)</string>
|
||||
|
@ -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());
|
||||
|
@ -250,7 +250,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()
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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()
|
||||
|
@ -55,7 +55,6 @@ CustomPage::CustomPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(par
|
||||
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
connect(ui->betaFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &CustomPage::filterChanged);
|
||||
connect(ui->refreshBtn, &QPushButton::clicked, this, &CustomPage::refresh);
|
||||
@ -96,13 +95,11 @@ void CustomPage::filterChanged()
|
||||
{
|
||||
QStringList out;
|
||||
if (ui->alphaFilter->isChecked())
|
||||
out << "(old_alpha)";
|
||||
out << "(alpha)";
|
||||
if (ui->betaFilter->isChecked())
|
||||
out << "(old_beta)";
|
||||
out << "(beta)";
|
||||
if (ui->snapshotFilter->isChecked())
|
||||
out << "(snapshot)";
|
||||
if (ui->oldSnapshotFilter->isChecked())
|
||||
out << "(old_snapshot)";
|
||||
if (ui->releaseFilter->isChecked())
|
||||
out << "(release)";
|
||||
if (ui->experimentsFilter->isChecked())
|
||||
|
@ -93,16 +93,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="oldSnapshotFilter">
|
||||
<property name="text">
|
||||
<string>Old Snapshots</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="betaFilter">
|
||||
<property name="text">
|
||||
@ -286,7 +276,6 @@
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>releaseFilter</tabstop>
|
||||
<tabstop>snapshotFilter</tabstop>
|
||||
<tabstop>oldSnapshotFilter</tabstop>
|
||||
<tabstop>betaFilter</tabstop>
|
||||
<tabstop>alphaFilter</tabstop>
|
||||
<tabstop>experimentsFilter</tabstop>
|
||||
|
@ -123,6 +123,10 @@ void ImportPage::updateState()
|
||||
// need to find the download link for the modpack
|
||||
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
|
||||
QUrlQuery query(url);
|
||||
if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) {
|
||||
qDebug() << "Invalid curseforge link:" << url;
|
||||
return;
|
||||
}
|
||||
auto addonId = query.allQueryItemValues("addonId")[0];
|
||||
auto fileId = query.allQueryItemValues("fileId")[0];
|
||||
auto array = std::make_shared<QByteArray>();
|
||||
@ -200,7 +204,9 @@ void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info)
|
||||
|
||||
void ImportPage::on_modpackBtn_clicked()
|
||||
{
|
||||
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
|
||||
const QMimeType zip = QMimeDatabase().mimeTypeForName("application/zip");
|
||||
auto filter = tr("Supported files") + QString(" (%1 *.mrpack)").arg(zip.globPatterns().join(" "));
|
||||
filter += ";;" + zip.filterString();
|
||||
//: Option for filtering for *.mrpack files when importing
|
||||
filter += ";;" + tr("Modrinth pack") + " (*.mrpack)";
|
||||
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
|
||||
|
@ -90,6 +90,7 @@ void ModPage::filterMods()
|
||||
void ModPage::triggerSearch()
|
||||
{
|
||||
m_filter = m_filter_widget->getFilter();
|
||||
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||
m_ui->packView->clearSelection();
|
||||
m_ui->packDescription->clear();
|
||||
m_ui->versionSelectionBox->clear();
|
||||
|
@ -207,6 +207,11 @@ 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);
|
||||
@ -228,7 +233,13 @@ void ResourceModel::loadEntry(QModelIndex& entry)
|
||||
callbacks.on_fail = [this](QString reason) {
|
||||
if (!s_running_models.constFind(this).value())
|
||||
return;
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
|
||||
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)
|
||||
@ -320,7 +331,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
|
||||
auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
|
||||
|
||||
auto full_file_path = cache_entry->getFullPath();
|
||||
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
|
||||
connect(icon_fetch_action.get(), &Task::succeeded, this, [=] {
|
||||
auto icon = QIcon(full_file_path);
|
||||
QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 })));
|
||||
|
||||
@ -328,7 +339,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
|
||||
|
||||
emit dataChanged(index, index, { Qt::DecorationRole });
|
||||
});
|
||||
connect(icon_fetch_action.get(), &NetAction::failed, this, [=] {
|
||||
connect(icon_fetch_action.get(), &Task::failed, this, [=] {
|
||||
m_currently_running_icon_actions.remove(url);
|
||||
m_failed_icon_actions.insert(url);
|
||||
});
|
||||
|
@ -23,6 +23,7 @@ ResourcePackResourcePage::ResourcePackResourcePage(ResourceDownloadDialog* dialo
|
||||
|
||||
void ResourcePackResourcePage::triggerSearch()
|
||||
{
|
||||
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||
m_ui->packView->clearSelection();
|
||||
m_ui->packDescription->clear();
|
||||
m_ui->versionSelectionBox->clear();
|
||||
|
@ -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 {
|
||||
@ -407,9 +412,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;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ ShaderPackResourcePage::ShaderPackResourcePage(ShaderPackDownloadDialog* dialog,
|
||||
|
||||
void ShaderPackResourcePage::triggerSearch()
|
||||
{
|
||||
m_ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||
m_ui->packView->clearSelection();
|
||||
m_ui->packDescription->clear();
|
||||
m_ui->versionSelectionBox->clear();
|
||||
|
@ -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;
|
||||
@ -348,10 +352,10 @@ void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
|
||||
void ModpackListModel::searchRequestFailed(QString reason)
|
||||
{
|
||||
auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
|
||||
if (!failed_action->m_reply) {
|
||||
if (failed_action->replyStatusCode() == -1) {
|
||||
// Network error
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
|
||||
} else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) {
|
||||
} else if (failed_action->replyStatusCode() == 409) {
|
||||
// 409 Gone, notify user to update
|
||||
QMessageBox::critical(nullptr, tr("Error"),
|
||||
//: %1 refers to the launcher itself
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
#include "ModrinthPage.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui_ModrinthPage.h"
|
||||
|
||||
#include "ModrinthModel.h"
|
||||
@ -103,6 +104,7 @@ void ModrinthPage::retranslate()
|
||||
void ModrinthPage::openedImpl()
|
||||
{
|
||||
BasePage::openedImpl();
|
||||
suggestCurrent();
|
||||
triggerSearch();
|
||||
}
|
||||
|
||||
@ -182,6 +184,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 +239,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 +268,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();
|
||||
|
@ -36,7 +36,10 @@
|
||||
#include "ui/themes/CatPack.h"
|
||||
#include <QDate>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QImageReader>
|
||||
#include <QRandomGenerator>
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
@ -79,7 +82,7 @@ JsonCatPack::JsonCatPack(QFileInfo& manifestInfo) : BasicCatPack(manifestInfo.di
|
||||
auto doc = Json::requireDocument(manifestInfo.absoluteFilePath(), "CatPack JSON file");
|
||||
const auto root = doc.object();
|
||||
m_name = Json::requireString(root, "name", "Catpack name");
|
||||
m_defaultPath = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat"));
|
||||
m_default_path = FS::PathCombine(path, Json::requireString(root, "default", "Default Cat"));
|
||||
auto variants = Json::ensureArray(root, "variants", QJsonArray(), "Catpack Variants");
|
||||
for (auto v : variants) {
|
||||
auto variant = Json::ensureObject(v, QJsonObject(), "Cat variant");
|
||||
@ -117,5 +120,21 @@ QString JsonCatPack::path(QDate now)
|
||||
if (startDate <= now && now <= endDate)
|
||||
return var.path;
|
||||
}
|
||||
return m_defaultPath;
|
||||
auto dInfo = QFileInfo(m_default_path);
|
||||
if (!dInfo.isDir())
|
||||
return m_default_path;
|
||||
|
||||
QStringList supportedImageFormats;
|
||||
for (auto format : QImageReader::supportedImageFormats()) {
|
||||
supportedImageFormats.append("*." + format);
|
||||
}
|
||||
|
||||
auto files = QDir(m_default_path).entryInfoList(supportedImageFormats, QDir::Files, QDir::Name);
|
||||
if (files.length() == 0)
|
||||
return "";
|
||||
auto idx = (now.dayOfYear() - 1) % files.length();
|
||||
auto isRandom = dInfo.fileName().compare("random", Qt::CaseInsensitive) == 0;
|
||||
if (isRandom)
|
||||
idx = QRandomGenerator::global()->bounded(0, files.length());
|
||||
return files[idx].absoluteFilePath();
|
||||
}
|
||||
|
@ -87,6 +87,6 @@ class JsonCatPack : public BasicCatPack {
|
||||
QString path(QDate now);
|
||||
|
||||
private:
|
||||
QString m_defaultPath;
|
||||
QString m_default_path;
|
||||
QList<Variant> m_variants;
|
||||
};
|
||||
|
30
launcher/ui/themes/HintOverrideProxyStyle.cpp
Normal file
30
launcher/ui/themes/HintOverrideProxyStyle.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "HintOverrideProxyStyle.h"
|
||||
|
||||
int HintOverrideProxyStyle::styleHint(QStyle::StyleHint hint,
|
||||
const QStyleOption* option,
|
||||
const QWidget* widget,
|
||||
QStyleHintReturn* returnData) const
|
||||
{
|
||||
if (hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
|
||||
return 0;
|
||||
|
||||
return QProxyStyle::styleHint(hint, option, widget, returnData);
|
||||
}
|
34
launcher/ui/themes/HintOverrideProxyStyle.h
Normal file
34
launcher/ui/themes/HintOverrideProxyStyle.h
Normal file
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QProxyStyle>
|
||||
#include <iostream>
|
||||
|
||||
/// Used to override platform-specific behaviours which the launcher does work well with.
|
||||
class HintOverrideProxyStyle : public QProxyStyle {
|
||||
Q_OBJECT
|
||||
public:
|
||||
HintOverrideProxyStyle(QStyle* style) : QProxyStyle(style) {}
|
||||
|
||||
int styleHint(QStyle::StyleHint hint,
|
||||
const QStyleOption* option = nullptr,
|
||||
const QWidget* widget = nullptr,
|
||||
QStyleHintReturn* returnData = nullptr) const override;
|
||||
};
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -36,12 +37,13 @@
|
||||
#include <QDir>
|
||||
#include <QStyleFactory>
|
||||
#include "Application.h"
|
||||
#include "HintOverrideProxyStyle.h"
|
||||
#include "rainbow.h"
|
||||
|
||||
void ITheme::apply(bool)
|
||||
{
|
||||
APPLICATION->setStyleSheet(QString());
|
||||
QApplication::setStyle(QStyleFactory::create(qtTheme()));
|
||||
QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme())));
|
||||
if (hasColorScheme()) {
|
||||
QApplication::setPalette(colorScheme());
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Tayou <git@tayou.org>
|
||||
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* 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
|
||||
@ -37,6 +38,7 @@
|
||||
#include <QDebug>
|
||||
#include <QStyle>
|
||||
#include <QStyleFactory>
|
||||
#include "HintOverrideProxyStyle.h"
|
||||
#include "ThemeManager.h"
|
||||
|
||||
SystemTheme::SystemTheme()
|
||||
@ -64,8 +66,11 @@ void SystemTheme::apply(bool initial)
|
||||
{
|
||||
// See https://github.com/MultiMC/Launcher/issues/1790
|
||||
// or https://github.com/PrismLauncher/PrismLauncher/issues/490
|
||||
if (initial)
|
||||
if (initial) {
|
||||
QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme())));
|
||||
return;
|
||||
}
|
||||
|
||||
ITheme::apply(initial);
|
||||
}
|
||||
|
||||
|
@ -178,8 +178,8 @@ QList<ITheme*> ThemeManager::getValidApplicationThemes()
|
||||
QList<CatPack*> ThemeManager::getValidCatPacks()
|
||||
{
|
||||
QList<CatPack*> ret;
|
||||
ret.reserve(m_catPacks.size());
|
||||
for (auto&& [id, theme] : m_catPacks) {
|
||||
ret.reserve(m_cat_packs.size());
|
||||
for (auto&& [id, theme] : m_cat_packs) {
|
||||
ret.append(theme.get());
|
||||
}
|
||||
return ret;
|
||||
@ -244,8 +244,8 @@ void ThemeManager::applyCurrentlySelectedTheme(bool initial)
|
||||
|
||||
QString ThemeManager::getCatPack(QString catName)
|
||||
{
|
||||
auto catIter = m_catPacks.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
|
||||
if (catIter != m_catPacks.end()) {
|
||||
auto catIter = m_cat_packs.find(!catName.isEmpty() ? catName : APPLICATION->settings()->get("BackgroundCat").toString());
|
||||
if (catIter != m_cat_packs.end()) {
|
||||
auto& catPack = catIter->second;
|
||||
themeDebugLog() << "applying catpack" << catPack->id();
|
||||
return catPack->path();
|
||||
@ -253,14 +253,14 @@ QString ThemeManager::getCatPack(QString catName)
|
||||
themeWarningLog() << "Tried to get invalid catPack:" << catName;
|
||||
}
|
||||
|
||||
return m_catPacks.begin()->second->path();
|
||||
return m_cat_packs.begin()->second->path();
|
||||
}
|
||||
|
||||
QString ThemeManager::addCatPack(std::unique_ptr<CatPack> catPack)
|
||||
{
|
||||
QString id = catPack->id();
|
||||
if (m_catPacks.find(id) == m_catPacks.end())
|
||||
m_catPacks.emplace(id, std::move(catPack));
|
||||
if (m_cat_packs.find(id) == m_cat_packs.end())
|
||||
m_cat_packs.emplace(id, std::move(catPack));
|
||||
else
|
||||
themeWarningLog() << "CatPack(" << id << ") not added to prevent id duplication";
|
||||
return id;
|
||||
|
@ -61,7 +61,7 @@ class ThemeManager {
|
||||
QDir m_iconThemeFolder{ "iconthemes" };
|
||||
QDir m_applicationThemeFolder{ "themes" };
|
||||
QDir m_catPacksFolder{ "catpacks" };
|
||||
std::map<QString, std::unique_ptr<CatPack>> m_catPacks;
|
||||
std::map<QString, std::unique_ptr<CatPack>> m_cat_packs;
|
||||
|
||||
void initializeThemes();
|
||||
void initializeCatPacks();
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "LogView.h"
|
||||
#include <QScrollBar>
|
||||
#include <QTextBlock>
|
||||
#include <QTextDocumentFragment>
|
||||
|
||||
LogView::LogView(QWidget* parent) : QPlainTextEdit(parent)
|
||||
{
|
||||
@ -117,6 +118,9 @@ void LogView::rowsAboutToBeInserted(const QModelIndex& parent, int first, int la
|
||||
|
||||
void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
|
||||
{
|
||||
QTextDocument document;
|
||||
QTextCursor cursor(&document);
|
||||
|
||||
for (int i = first; i <= last; i++) {
|
||||
auto idx = m_model->index(i, 0, parent);
|
||||
auto text = m_model->data(idx, Qt::DisplayRole).toString();
|
||||
@ -133,11 +137,16 @@ void LogView::rowsInserted(const QModelIndex& parent, int first, int last)
|
||||
if (bg.isValid()) {
|
||||
format.setBackground(bg.value<QColor>());
|
||||
}
|
||||
auto workCursor = textCursor();
|
||||
workCursor.movePosition(QTextCursor::End);
|
||||
workCursor.insertText(text, format);
|
||||
workCursor.insertBlock();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.insertText(text, format);
|
||||
cursor.insertBlock();
|
||||
}
|
||||
|
||||
QTextDocumentFragment fragment(&document);
|
||||
QTextCursor workCursor = textCursor();
|
||||
workCursor.movePosition(QTextCursor::End);
|
||||
workCursor.insertFragment(fragment);
|
||||
|
||||
if (m_scroll && !m_scrolling) {
|
||||
m_scrolling = true;
|
||||
QMetaObject::invokeMethod(this, "scrollToBottom", Qt::QueuedConnection);
|
||||
|
@ -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()
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QTextObject>
|
||||
#include <memory>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
@ -36,6 +37,30 @@ QSizeF VariableSizedImageObject::intrinsicSize(QTextDocument* doc, int posInDocu
|
||||
|
||||
auto image = qvariant_cast<QImage>(format.property(ImageData));
|
||||
auto size = image.size();
|
||||
if (size.isEmpty()) // can't resize an empty image
|
||||
return { size };
|
||||
|
||||
// calculate the new image size based on the properties
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
auto widthVar = format.property(QTextFormat::ImageWidth);
|
||||
if (widthVar.isValid()) {
|
||||
width = widthVar.toInt();
|
||||
}
|
||||
auto heigthVar = format.property(QTextFormat::ImageHeight);
|
||||
if (heigthVar.isValid()) {
|
||||
height = heigthVar.toInt();
|
||||
}
|
||||
if (width != 0 && height != 0) {
|
||||
size.setWidth(width);
|
||||
size.setHeight(height);
|
||||
} else if (width != 0) {
|
||||
size.setHeight((width * size.height()) / size.width());
|
||||
size.setWidth(width);
|
||||
} else if (height != 0) {
|
||||
size.setWidth((height * size.width()) / size.height());
|
||||
size.setHeight(height);
|
||||
}
|
||||
|
||||
// Get the width of the text content to make the image similar sized.
|
||||
// doc->textWidth() includes the margin, so we need to remove it.
|
||||
@ -46,6 +71,7 @@ QSizeF VariableSizedImageObject::intrinsicSize(QTextDocument* doc, int posInDocu
|
||||
|
||||
return { size };
|
||||
}
|
||||
|
||||
void VariableSizedImageObject::drawObject(QPainter* painter,
|
||||
const QRectF& rect,
|
||||
QTextDocument* doc,
|
||||
@ -57,7 +83,20 @@ void VariableSizedImageObject::drawObject(QPainter* painter,
|
||||
if (m_fetching_images.contains(image_url))
|
||||
return;
|
||||
|
||||
loadImage(doc, image_url, posInDocument);
|
||||
auto meta = std::make_shared<ImageMetadata>();
|
||||
meta->posInDocument = posInDocument;
|
||||
meta->url = image_url;
|
||||
|
||||
auto widthVar = format.property(QTextFormat::ImageWidth);
|
||||
if (widthVar.isValid()) {
|
||||
meta->width = widthVar.toInt();
|
||||
}
|
||||
auto heigthVar = format.property(QTextFormat::ImageHeight);
|
||||
if (heigthVar.isValid()) {
|
||||
meta->height = heigthVar.toInt();
|
||||
}
|
||||
|
||||
loadImage(doc, meta);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -72,16 +111,19 @@ void VariableSizedImageObject::flush()
|
||||
m_fetching_images.clear();
|
||||
}
|
||||
|
||||
void VariableSizedImageObject::parseImage(QTextDocument* doc, QImage image, int posInDocument)
|
||||
void VariableSizedImageObject::parseImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta)
|
||||
{
|
||||
QTextCursor cursor(doc);
|
||||
cursor.setPosition(posInDocument);
|
||||
cursor.setPosition(meta->posInDocument);
|
||||
cursor.setKeepPositionOnInsert(true);
|
||||
|
||||
auto image_char_format = cursor.charFormat();
|
||||
|
||||
image_char_format.setObjectType(QTextFormat::ImageObject);
|
||||
image_char_format.setProperty(ImageData, image);
|
||||
image_char_format.setProperty(ImageData, meta->image);
|
||||
image_char_format.setProperty(QTextFormat::ImageName, meta->url.toDisplayString());
|
||||
image_char_format.setProperty(QTextFormat::ImageWidth, meta->width);
|
||||
image_char_format.setProperty(QTextFormat::ImageHeight, meta->height);
|
||||
|
||||
// Qt doesn't allow us to modify the properties of an existing object in the document.
|
||||
// So we remove the old one and add the new one with the ImageData property set.
|
||||
@ -89,30 +131,24 @@ void VariableSizedImageObject::parseImage(QTextDocument* doc, QImage image, int
|
||||
cursor.insertText(QString(QChar::ObjectReplacementCharacter), image_char_format);
|
||||
}
|
||||
|
||||
void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source, int posInDocument)
|
||||
void VariableSizedImageObject::loadImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta)
|
||||
{
|
||||
m_fetching_images.insert(source);
|
||||
m_fetching_images.insert(meta->url);
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(
|
||||
m_meta_entry,
|
||||
QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
|
||||
QString("images/%1").arg(QString(QCryptographicHash::hash(meta->url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
|
||||
|
||||
auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network());
|
||||
job->addNetAction(Net::ApiDownload::makeCached(source, entry));
|
||||
auto job = new NetJob(QString("Load Image: %1").arg(meta->url.fileName()), APPLICATION->network());
|
||||
job->addNetAction(Net::ApiDownload::makeCached(meta->url, entry));
|
||||
|
||||
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 source_url = meta->url;
|
||||
auto loadImage = [this, doc, full_entry_path, source_url, meta](const QImage& image) {
|
||||
doc->addResource(QTextDocument::ImageResource, source_url, image);
|
||||
|
||||
parseImage(doc, image, posInDocument);
|
||||
meta->image = image;
|
||||
parseImage(doc, meta);
|
||||
|
||||
// This size hack is needed to prevent the content from being laid out in an area smaller
|
||||
// than the total width available (weird).
|
||||
@ -121,6 +157,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);
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <QString>
|
||||
#include <QTextObjectInterface>
|
||||
#include <QUrl>
|
||||
#include <memory>
|
||||
|
||||
/** Custom image text object to be used instead of the normal one in ProjectDescriptionPage.
|
||||
*
|
||||
@ -32,6 +33,14 @@ class VariableSizedImageObject final : public QObject, public QTextObjectInterfa
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QTextObjectInterface)
|
||||
|
||||
struct ImageMetadata {
|
||||
int posInDocument;
|
||||
QUrl url;
|
||||
QImage image;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
public:
|
||||
QSizeF intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
|
||||
void drawObject(QPainter* painter, const QRectF& rect, QTextDocument* doc, int posInDocument, const QTextFormat& format) override;
|
||||
@ -49,13 +58,13 @@ class VariableSizedImageObject final : public QObject, public QTextObjectInterfa
|
||||
private:
|
||||
/** Adds the image to the document, in the given position.
|
||||
*/
|
||||
void parseImage(QTextDocument* doc, QImage image, int posInDocument);
|
||||
void parseImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta);
|
||||
|
||||
/** Loads an image from an external source, and adds it to the document.
|
||||
*
|
||||
* This uses m_meta_entry to cache the image.
|
||||
*/
|
||||
void loadImage(QTextDocument* doc, const QUrl& source, int posInDocument);
|
||||
void loadImage(QTextDocument* doc, std::shared_ptr<ImageMetadata> meta);
|
||||
|
||||
private:
|
||||
QString m_meta_entry;
|
||||
|
Reference in New Issue
Block a user