aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/ui')
-rw-r--r--launcher/ui/GuiUtil.cpp17
-rw-r--r--launcher/ui/MainWindow.cpp18
-rw-r--r--launcher/ui/MainWindow.h3
-rw-r--r--launcher/ui/dialogs/AboutDialog.cpp59
-rw-r--r--launcher/ui/dialogs/AboutDialog.h9
-rw-r--r--launcher/ui/dialogs/AboutDialog.ui34
-rw-r--r--launcher/ui/dialogs/ExportInstanceDialog.cpp8
-rw-r--r--launcher/ui/dialogs/MSALoginDialog.cpp12
-rw-r--r--launcher/ui/dialogs/MSALoginDialog.ui27
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp98
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h54
-rw-r--r--launcher/ui/dialogs/OfflineLoginDialog.cpp98
-rw-r--r--launcher/ui/dialogs/OfflineLoginDialog.h43
-rw-r--r--launcher/ui/dialogs/OfflineLoginDialog.ui67
-rw-r--r--launcher/ui/dialogs/UpdateDialog.cpp8
-rw-r--r--launcher/ui/dialogs/UpdateDialog.ui2
-rw-r--r--launcher/ui/pages/global/APIPage.cpp (renamed from launcher/ui/pages/global/PasteEEPage.cpp)54
-rw-r--r--launcher/ui/pages/global/APIPage.h (renamed from launcher/ui/pages/global/PasteEEPage.h)22
-rw-r--r--launcher/ui/pages/global/APIPage.ui179
-rw-r--r--launcher/ui/pages/global/AccountListPage.cpp41
-rw-r--r--launcher/ui/pages/global/AccountListPage.h1
-rw-r--r--launcher/ui/pages/global/AccountListPage.ui6
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp56
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui73
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp5
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui20
-rw-r--r--launcher/ui/pages/global/PasteEEPage.ui128
-rw-r--r--launcher/ui/pages/instance/LegacyUpgradePage.ui9
-rw-r--r--launcher/ui/pages/instance/LogPage.ui2
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp45
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/instance/OtherLogsPage.ui2
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModModel.cpp273
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModModel.h79
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.cpp196
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.h67
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.ui90
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp11
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp276
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h79
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp180
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h67
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui90
-rw-r--r--launcher/ui/setupwizard/AnalyticsWizardPage.cpp63
-rw-r--r--launcher/ui/setupwizard/AnalyticsWizardPage.h25
-rw-r--r--launcher/ui/setupwizard/SetupWizard.cpp2
-rw-r--r--launcher/ui/widgets/LanguageSelectionWidget.cpp14
-rw-r--r--launcher/ui/widgets/LanguageSelectionWidget.h2
-rw-r--r--launcher/ui/widgets/PageContainer.cpp2
-rw-r--r--launcher/ui/widgets/WideBar.cpp14
-rw-r--r--launcher/ui/widgets/WideBar.h1
52 files changed, 2224 insertions, 511 deletions
diff --git a/launcher/ui/GuiUtil.cpp b/launcher/ui/GuiUtil.cpp
index efb1a4df..9eb658e2 100644
--- a/launcher/ui/GuiUtil.cpp
+++ b/launcher/ui/GuiUtil.cpp
@@ -16,21 +16,8 @@
QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget)
{
ProgressDialog dialog(parentWidget);
- auto APIKeySetting = APPLICATION->settings()->get("PasteEEAPIKey").toString();
- if(APIKeySetting == "multimc")
- {
- APIKeySetting = BuildConfig.PASTE_EE_KEY;
- }
- std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, APIKeySetting));
-
- if (!paste->validateText())
- {
- CustomMessageBox::selectable(
- parentWidget, QObject::tr("Upload failed"),
- QObject::tr("The log file is too big. You'll have to upload it manually."),
- QMessageBox::Warning)->exec();
- return QString();
- }
+ auto pasteUrlSetting = APPLICATION->settings()->get("PastebinURL").toString();
+ std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, pasteUrlSetting));
dialog.execWithTask(paste.get());
if (!paste->wasSuccessful())
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index c0ba8839..ad7227cc 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -201,7 +201,6 @@ public:
//TranslatedAction actionRefresh;
TranslatedAction actionCheckUpdate;
TranslatedAction actionSettings;
- TranslatedAction actionPatreon;
TranslatedAction actionMoreNews;
TranslatedAction actionManageAccounts;
TranslatedAction actionLaunchInstance;
@@ -400,14 +399,6 @@ public:
mainToolBar->addSeparator();
- actionPatreon = TranslatedAction(MainWindow);
- actionPatreon->setObjectName(QStringLiteral("actionPatreon"));
- actionPatreon->setIcon(APPLICATION->getThemedIcon("patreon"));
- actionPatreon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Support %1"));
- actionPatreon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 Patreon page."));
- all_actions.append(&actionPatreon);
- mainToolBar->addAction(actionPatreon);
-
actionCAT = TranslatedAction(MainWindow);
actionCAT->setObjectName(QStringLiteral("actionCAT"));
actionCAT->setCheckable(true);
@@ -1694,14 +1685,9 @@ void MainWindow::on_actionReportBug_triggered()
DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL));
}
-void MainWindow::on_actionPatreon_triggered()
-{
- DesktopServices::openUrl(QUrl("https://www.patreon.com/multimc"));
-}
-
void MainWindow::on_actionMoreNews_triggered()
{
- DesktopServices::openUrl(QUrl("https://multimc.org/posts.html"));
+ DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
}
void MainWindow::newsButtonClicked()
@@ -1713,7 +1699,7 @@ void MainWindow::newsButtonClicked()
}
else
{
- DesktopServices::openUrl(QUrl("https://multimc.org/posts.html"));
+ DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL));
}
}
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index e462c524..f6940ab0 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -109,8 +109,6 @@ private slots:
void on_actionReportBug_triggered();
- void on_actionPatreon_triggered();
-
void on_actionMoreNews_triggered();
void newsButtonClicked();
@@ -223,3 +221,4 @@ private:
// managed by the application object
Task *m_versionLoadTask = nullptr;
};
+
diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp
index 9795c38b..ef96cc23 100644
--- a/launcher/ui/dialogs/AboutDialog.cpp
+++ b/launcher/ui/dialogs/AboutDialog.cpp
@@ -26,15 +26,20 @@
namespace {
// Credits
// This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument...
-QString getCreditsHtml(QStringList patrons)
+QString getCreditsHtml()
{
- QString patronsHeading = QObject::tr("Patrons", "About Credits");
QString output;
QTextStream stream(&output);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream << "<center>\n";
+
+ stream << "<h3>" << QObject::tr("PolyMC Developers", "About Credits") << "</h3>\n";
+ stream << "<p>swirl &lt;<a href='mailto:swurl@swurl.xyz'>swurl@swurl.xyz </a>&gt;</p>\n";
+ stream << "<p>LennyMcLennington &lt;<a href='mailto:lenny@sneed.church'>lenny@sneed.church</a>&gt;</p>\n";
+ stream << "<br />\n";
+
// TODO: possibly retrieve from git history at build time?
- stream << "<h3>" << QObject::tr("Developers", "About Credits") << "</h3>\n";
+ stream << "<h3>" << QObject::tr("MultiMC Developers", "About Credits") << "</h3>\n";
stream << "<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>\n";
stream << "<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>\n";
stream << "<p>Sky Welch &lt;<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>&gt;</p>\n";
@@ -48,15 +53,9 @@ QString getCreditsHtml(QStringList patrons)
stream << "<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>\n";
stream << "<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt;</p>\n";
stream << "<p>Zeker Zhayard &lt;<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>&gt;</p>\n";
+ stream << "<p>Everyone else who <a href='https://github.com/PolyMC/PolyMC/graphs/contributors'>contributed</a>!</p>\n";
stream << "<br />\n";
- if(!patrons.isEmpty()) {
- stream << "<h3>" << QObject::tr("Patrons", "About Credits") << "</h3>\n";
- for (QString patron : patrons)
- {
- stream << "<p>" << patron << "</p>\n";
- }
- }
stream << "</center>\n";
return output;
}
@@ -80,7 +79,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
setWindowTitle(tr("About %1").arg(launcherName));
- QString chtml = getCreditsHtml(QStringList());
+ QString chtml = getCreditsHtml();
ui->creditsText->setHtml(chtml);
QString lhtml = getLicenseHtml();
@@ -91,8 +90,12 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
ui->icon->setPixmap(APPLICATION->getThemedIcon("logo").pixmap(64));
ui->title->setText(launcherName);
- ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString());
- ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM);
+ ui->versionLabel->setText(BuildConfig.printableVersionString());
+
+ if (!BuildConfig.BUILD_PLATFORM.isEmpty())
+ ui->platformLabel->setText(tr("Platform") +": " + BuildConfig.BUILD_PLATFORM);
+ else
+ ui->platformLabel->setVisible(false);
if (BuildConfig.VERSION_BUILD >= 0)
ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD));
@@ -104,46 +107,18 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
else
ui->channelLabel->setVisible(false);
- ui->redistributionText->setHtml(tr(
-"<p>We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</p>\n"
-"<p>Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. "
-"This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project "
-"icon and the title of windows, (no <b>MultiMC-fork</b> in the title).</p>\n"
-"<p>The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. "
-"However, it should be abundantly clear that the project is a fork <b>without</b> implying that you have our blessing.</p>"
- ));
-
QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
- QString copyText("© 2012-2021 %1");
+ QString copyText("© 2021-2022 %1");
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt);
-
- loadPatronList();
}
AboutDialog::~AboutDialog()
{
delete ui;
}
-
-void AboutDialog::loadPatronList()
-{
- netJob = new NetJob("Patreon Patron List", APPLICATION->network());
- netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://files.multimc.org/patrons.txt"), &dataSink));
- connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded);
- netJob->start();
-}
-
-void AboutDialog::patronListLoaded()
-{
- QString patronListStr(dataSink);
- dataSink.clear();
- QString html = getCreditsHtml(patronListStr.split("\n", QString::SkipEmptyParts));
- ui->creditsText->setHtml(html);
-}
-
diff --git a/launcher/ui/dialogs/AboutDialog.h b/launcher/ui/dialogs/AboutDialog.h
index cc4b8850..814fd98c 100644
--- a/launcher/ui/dialogs/AboutDialog.h
+++ b/launcher/ui/dialogs/AboutDialog.h
@@ -31,17 +31,10 @@ public:
explicit AboutDialog(QWidget *parent = 0);
~AboutDialog();
-public
-slots:
- /// Starts loading a list of Patreon patrons.
- void loadPatronList();
-
- /// Slot for when the patron list loads successfully.
- void patronListLoaded();
-
private:
Ui::AboutDialog *ui;
NetJob::Ptr netJob;
QByteArray dataSink;
};
+
diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui
index 422e877b..58275c66 100644
--- a/launcher/ui/dialogs/AboutDialog.ui
+++ b/launcher/ui/dialogs/AboutDialog.ui
@@ -80,13 +80,20 @@
</font>
</property>
<property name="text">
- <string notr="true">MultiMC 5</string>
+ <string notr="true">PolyMC</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
+ <item>
+ <widget class="QLabel" name="versionLabel">
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
@@ -152,16 +159,6 @@
</widget>
</item>
<item>
- <widget class="QLabel" name="versionLabel">
- <property name="text">
- <string>Version:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
<widget class="QLabel" name="platformLabel">
<property name="text">
<string>Platform:</string>
@@ -251,20 +248,6 @@
</item>
</layout>
</widget>
- <widget class="QWidget" name="forkingTab">
- <attribute name="title">
- <string>Forking/Redistribution</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <item>
- <widget class="QTextEdit" name="redistributionText">
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
</widget>
</item>
<item>
@@ -307,7 +290,6 @@
<tabstop>tabWidget</tabstop>
<tabstop>creditsText</tabstop>
<tabstop>licenseText</tabstop>
- <tabstop>redistributionText</tabstop>
<tabstop>aboutQt</tabstop>
<tabstop>closeButton</tabstop>
</tabstops>
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp
index 1a164875..f3bf7abe 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.cpp
+++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp
@@ -403,7 +403,13 @@ bool ExportInstanceDialog::doExport()
auto & blocked = proxyModel->blockedPaths();
using std::placeholders::_1;
- if (!JlCompress::compressDir(output, m_instance->instanceRoot(), name, std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1)))
+ auto files = QFileInfoList();
+ if (!MMCZip::collectFileListRecursively(m_instance->instanceRoot(), nullptr, &files,
+ std::bind(&SeparatorPrefixTree<'/'>::covers, blocked, _1))) {
+ QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
+ return false;
+ }
+ if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files))
{
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;
diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp
index f46aa3b9..174ad46c 100644
--- a/launcher/ui/dialogs/MSALoginDialog.cpp
+++ b/launcher/ui/dialogs/MSALoginDialog.cpp
@@ -16,15 +16,19 @@
#include "MSALoginDialog.h"
#include "ui_MSALoginDialog.h"
+#include "DesktopServices.h"
#include "minecraft/auth/AccountTask.h"
#include <QtWidgets/QPushButton>
#include <QUrl>
+#include <QApplication>
+#include <QClipboard>
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);
@@ -81,10 +85,17 @@ void MSALoginDialog::showVerificationUriAndCode(const QUrl& uri, const QString&
QString urlString = uri.toString();
QString linkString = QString("<a href=\"%1\">%2</a>").arg(urlString, urlString);
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)
@@ -110,6 +121,7 @@ void MSALoginDialog::onTaskFailed(const QString &reason)
// Re-enable user-interaction
setUserInputsEnabled(true);
ui->progressBar->setVisible(false);
+ ui->actionButton->setVisible(false);
}
void MSALoginDialog::onTaskSucceeded()
diff --git a/launcher/ui/dialogs/MSALoginDialog.ui b/launcher/ui/dialogs/MSALoginDialog.ui
index 78cbfb26..c18d01a1 100644
--- a/launcher/ui/dialogs/MSALoginDialog.ui
+++ b/launcher/ui/dialogs/MSALoginDialog.ui
@@ -49,14 +49,25 @@ aaaaa</string>
</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>
+ <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>
</item>
</layout>
</widget>
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
new file mode 100644
index 00000000..6b807b8c
--- /dev/null
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -0,0 +1,98 @@
+#include "ModDownloadDialog.h"
+
+#include <BaseVersion.h>
+#include <icons/IconList.h>
+#include <InstanceList.h>
+
+#include "ProgressDialog.h"
+
+#include <QLayout>
+#include <QPushButton>
+#include <QValidator>
+#include <QDialogButtonBox>
+
+#include "ui/widgets/PageContainer.h"
+#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
+#include "ModDownloadTask.h"
+
+
+ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods, QWidget *parent,
+ BaseInstance *instance)
+ : QDialog(parent), mods(mods), m_instance(instance)
+{
+ setObjectName(QStringLiteral("ModDownloadDialog"));
+ resize(400, 347);
+ m_verticalLayout = new QVBoxLayout(this);
+ m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
+
+ setWindowIcon(APPLICATION->getThemedIcon("new"));
+ // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below.
+ m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+ m_container = new PageContainer(this);
+ m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding);
+ m_container->layout()->setContentsMargins(0, 0, 0, 0);
+ m_verticalLayout->addWidget(m_container);
+
+ m_container->addButtons(m_buttons);
+
+ // 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
+ auto OkButton = m_buttons->button(QDialogButtonBox::Ok);
+ OkButton->setDefault(true);
+ OkButton->setAutoDefault(true);
+ connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::accept);
+
+ auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel);
+ CancelButton->setDefault(false);
+ CancelButton->setAutoDefault(false);
+ connect(CancelButton, &QPushButton::clicked, this, &ModDownloadDialog::reject);
+
+ auto HelpButton = m_buttons->button(QDialogButtonBox::Help);
+ HelpButton->setDefault(false);
+ HelpButton->setAutoDefault(false);
+ connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help);
+ QMetaObject::connectSlotsByName(this);
+ setWindowModality(Qt::WindowModal);
+ setWindowTitle("Download mods");
+}
+
+QString ModDownloadDialog::dialogTitle()
+{
+ return tr("Download mods");
+}
+
+void ModDownloadDialog::reject()
+{
+ QDialog::reject();
+}
+
+void ModDownloadDialog::accept()
+{
+ QDialog::accept();
+}
+
+QList<BasePage *> ModDownloadDialog::getPages()
+{
+ modrinthPage = new ModrinthPage(this, m_instance);
+ flameModPage = new FlameModPage(this, m_instance);
+ return
+ {
+ modrinthPage,
+ flameModPage
+ };
+}
+
+void ModDownloadDialog::setSuggestedMod(const QString& name, ModDownloadTask* task)
+{
+ modTask.reset(task);
+ m_buttons->button(QDialogButtonBox::Ok)->setEnabled(task);
+}
+
+ModDownloadDialog::~ModDownloadDialog()
+{
+}
+
+ModDownloadTask *ModDownloadDialog::getTask() {
+ return modTask.release();
+}
diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h
new file mode 100644
index 00000000..ece8e328
--- /dev/null
+++ b/launcher/ui/dialogs/ModDownloadDialog.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <QDialog>
+#include <QVBoxLayout>
+
+#include "BaseVersion.h"
+#include "ui/pages/BasePageProvider.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "ModDownloadTask.h"
+#include "ui/pages/modplatform/flame/FlameModPage.h"
+
+namespace Ui
+{
+class ModDownloadDialog;
+}
+
+class PageContainer;
+class QDialogButtonBox;
+class ModrinthPage;
+
+class ModDownloadDialog : public QDialog, public BasePageProvider
+{
+ Q_OBJECT
+
+public:
+ explicit ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods, QWidget *parent, BaseInstance *instance);
+ ~ModDownloadDialog();
+
+ QString dialogTitle() override;
+ QList<BasePage *> getPages() override;
+
+ void setSuggestedMod(const QString & name = QString(), ModDownloadTask * task = nullptr);
+
+ ModDownloadTask * getTask();
+ const std::shared_ptr<ModFolderModel> &mods;
+
+public slots:
+ void accept() override;
+ void reject() override;
+
+//private slots:
+
+private:
+ Ui::ModDownloadDialog *ui = nullptr;
+ PageContainer * m_container = nullptr;
+ QDialogButtonBox * m_buttons = nullptr;
+ QVBoxLayout *m_verticalLayout = nullptr;
+
+
+ ModrinthPage *modrinthPage = nullptr;
+ FlameModPage *flameModPage = nullptr;
+ std::unique_ptr<ModDownloadTask> modTask;
+ BaseInstance *m_instance;
+};
diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp
new file mode 100644
index 00000000..345ed40a
--- /dev/null
+++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp
@@ -0,0 +1,98 @@
+#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)
+{
+ ui->setupUi(this);
+ ui->progressBar->setVisible(false);
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+
+ connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+OfflineLoginDialog::~OfflineLoginDialog()
+{
+ delete ui;
+}
+
+// Stage 1: User interaction
+void OfflineLoginDialog::accept()
+{
+ setUserInputsEnabled(false);
+ ui->progressBar->setVisible(true);
+
+ // Setup the login task and start it
+ m_account = MinecraftAccount::createOffline(ui->userTextBox->text());
+ m_loginTask = m_account->loginOffline();
+ 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);
+ connect(m_loginTask.get(), &Task::progress, this, &OfflineLoginDialog::onTaskProgress);
+ m_loginTask->start();
+}
+
+void OfflineLoginDialog::setUserInputsEnabled(bool enable)
+{
+ ui->userTextBox->setEnabled(enable);
+ ui->buttonBox->setEnabled(enable);
+}
+
+// Enable the OK button only when the textbox contains something.
+void OfflineLoginDialog::on_userTextBox_textEdited(const QString &newText)
+{
+ ui->buttonBox->button(QDialogButtonBox::Ok)
+ ->setEnabled(!newText.isEmpty());
+}
+
+void OfflineLoginDialog::onTaskFailed(const QString &reason)
+{
+ // Set message
+ auto lines = reason.split('\n');
+ QString processed;
+ for(auto line: lines) {
+ if(line.size()) {
+ processed += "<font color='red'>" + line + "</font><br />";
+ }
+ else {
+ processed += "<br />";
+ }
+ }
+ ui->label->setText(processed);
+
+ // Re-enable user-interaction
+ setUserInputsEnabled(true);
+ ui->progressBar->setVisible(false);
+}
+
+void OfflineLoginDialog::onTaskSucceeded()
+{
+ QDialog::accept();
+}
+
+void OfflineLoginDialog::onTaskStatus(const QString &status)
+{
+ ui->label->setText(status);
+}
+
+void OfflineLoginDialog::onTaskProgress(qint64 current, qint64 total)
+{
+ ui->progressBar->setMaximum(total);
+ ui->progressBar->setValue(current);
+}
+
+// Public interface
+MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg)
+{
+ OfflineLoginDialog dlg(parent);
+ dlg.ui->label->setText(msg);
+ if (dlg.exec() == QDialog::Accepted)
+ {
+ return dlg.m_account;
+ }
+ return 0;
+}
diff --git a/launcher/ui/dialogs/OfflineLoginDialog.h b/launcher/ui/dialogs/OfflineLoginDialog.h
new file mode 100644
index 00000000..5e608379
--- /dev/null
+++ b/launcher/ui/dialogs/OfflineLoginDialog.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <QtWidgets/QDialog>
+#include <QtCore/QEventLoop>
+
+#include "minecraft/auth/MinecraftAccount.h"
+#include "tasks/Task.h"
+
+namespace Ui
+{
+class OfflineLoginDialog;
+}
+
+class OfflineLoginDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ ~OfflineLoginDialog();
+
+ static MinecraftAccountPtr newAccount(QWidget *parent, QString message);
+
+private:
+ explicit OfflineLoginDialog(QWidget *parent = 0);
+
+ void setUserInputsEnabled(bool enable);
+
+protected
+slots:
+ void accept();
+
+ void onTaskFailed(const QString &reason);
+ void onTaskSucceeded();
+ void onTaskStatus(const QString &status);
+ void onTaskProgress(qint64 current, qint64 total);
+
+ void on_userTextBox_textEdited(const QString &newText);
+
+private:
+ Ui::OfflineLoginDialog *ui;
+ MinecraftAccountPtr m_account;
+ Task::Ptr m_loginTask;
+};
diff --git a/launcher/ui/dialogs/OfflineLoginDialog.ui b/launcher/ui/dialogs/OfflineLoginDialog.ui
new file mode 100644
index 00000000..d8964a2e
--- /dev/null
+++ b/launcher/ui/dialogs/OfflineLoginDialog.ui
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OfflineLoginDialog</class>
+ <widget class="QDialog" name="OfflineLoginDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>150</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>Add Account</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string notr="true">Message label placeholder.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="userTextBox">
+ <property name="placeholderText">
+ <string>Username</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="value">
+ <number>69</number>
+ </property>
+ <property name="textVisible">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp
index c0f6074c..ec77d146 100644
--- a/launcher/ui/dialogs/UpdateDialog.cpp
+++ b/launcher/ui/dialogs/UpdateDialog.cpp
@@ -38,12 +38,12 @@ void UpdateDialog::loadChangelog()
QString url;
if(channel == "stable")
{
- url = QString("https://raw.githubusercontent.com/MultiMC/Launcher/%1/changelog.md").arg(channel);
+ url = QString("https://raw.githubusercontent.com/PolyMC/PolyMC/%1/changelog.md").arg(channel);
m_changelogType = CHANGELOG_MARKDOWN;
}
else
{
- url = QString("https://api.github.com/repos/MultiMC/Launcher/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel);
+ url = QString("https://api.github.com/repos/PolyMC/PolyMC/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel);
m_changelogType = CHANGELOG_COMMITS;
}
dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData));
@@ -58,7 +58,7 @@ QString reprocessMarkdown(QByteArray markdown)
QString output = hoedown.process(markdown);
// HACK: easier than customizing hoedown
- output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/MultiMC/Launcher/issues/\\1\">GH-\\1</a>");
+ output.replace(QRegExp("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/issues/\\1\">GH-\\1</a>");
qDebug() << output;
return output;
}
@@ -100,7 +100,7 @@ QString reprocessCommits(QByteArray json)
result += "<tr><td>";
if(issuenr.length())
{
- result += QString("<a href=\"https://github.com/MultiMC/Launcher/issues/%1\">GH-%2</a>").arg(issuenr, issuenr);
+ result += QString("<a href=\"https://github.com/PolyMC/PolyMC/issues/%1\">GH-%2</a>").arg(issuenr, issuenr);
}
else if(prefix.length())
{
diff --git a/launcher/ui/dialogs/UpdateDialog.ui b/launcher/ui/dialogs/UpdateDialog.ui
index b0b3dd83..bd94a554 100644
--- a/launcher/ui/dialogs/UpdateDialog.ui
+++ b/launcher/ui/dialogs/UpdateDialog.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
- <string>MultiMC Update</string>
+ <string>PolyMC Update</string>
</property>
<property name="windowIcon">
<iconset>
diff --git a/launcher/ui/pages/global/PasteEEPage.cpp b/launcher/ui/pages/global/APIPage.cpp
index 4b375d9a..ad79e00c 100644
--- a/launcher/ui/pages/global/PasteEEPage.cpp
+++ b/launcher/ui/pages/global/APIPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2021 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC & PolyMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,69 +13,55 @@
* limitations under the License.
*/
-#include "PasteEEPage.h"
-#include "ui_PasteEEPage.h"
+#include "APIPage.h"
+#include "ui_APIPage.h"
#include <QMessageBox>
#include <QFileDialog>
#include <QStandardPaths>
#include <QTabBar>
+#include <QVariant>
#include "settings/SettingsObject.h"
#include "tools/BaseProfiler.h"
#include "Application.h"
-PasteEEPage::PasteEEPage(QWidget *parent) :
+APIPage::APIPage(QWidget *parent) :
QWidget(parent),
- ui(new Ui::PasteEEPage)
+ ui(new Ui::APIPage)
{
+ static QRegularExpression validUrlRegExp("https?://.+");
ui->setupUi(this);
+ ui->urlChoices->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->urlChoices));
ui->tabWidget->tabBar()->hide();\
- connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited);
loadSettings();
}
-PasteEEPage::~PasteEEPage()
+APIPage::~APIPage()
{
delete ui;
}
-void PasteEEPage::loadSettings()
+void APIPage::loadSettings()
{
auto s = APPLICATION->settings();
- QString keyToUse = s->get("PasteEEAPIKey").toString();
- if(keyToUse == "multimc")
- {
- ui->multimcButton->setChecked(true);
- }
- else
- {
- ui->customButton->setChecked(true);
- ui->customAPIkeyEdit->setText(keyToUse);
- }
+ QString pastebinURL = s->get("PastebinURL").toString();
+ ui->urlChoices->setCurrentText(pastebinURL);
+ QString msaClientID = s->get("MSAClientIDOverride").toString();
+ ui->msaClientID->setText(msaClientID);
}
-void PasteEEPage::applySettings()
+void APIPage::applySettings()
{
auto s = APPLICATION->settings();
-
- QString pasteKeyToUse;
- if (ui->customButton->isChecked())
- pasteKeyToUse = ui->customAPIkeyEdit->text();
- else
- {
- pasteKeyToUse = "multimc";
- }
- s->set("PasteEEAPIKey", pasteKeyToUse);
+ QString pastebinURL = ui->urlChoices->currentText();
+ s->set("PastebinURL", pastebinURL);
+ QString msaClientID = ui->msaClientID->text();
+ s->set("MSAClientIDOverride", msaClientID);
}
-bool PasteEEPage::apply()
+bool APIPage::apply()
{
applySettings();
return true;
}
-
-void PasteEEPage::textEdited(const QString& text)
-{
- ui->customButton->setChecked(true);
-}
diff --git a/launcher/ui/pages/global/PasteEEPage.h b/launcher/ui/pages/global/APIPage.h
index a1c7d434..9474ebbb 100644
--- a/launcher/ui/pages/global/PasteEEPage.h
+++ b/launcher/ui/pages/global/APIPage.h
@@ -21,32 +21,32 @@
#include <Application.h>
namespace Ui {
-class PasteEEPage;
+class APIPage;
}
-class PasteEEPage : public QWidget, public BasePage
+class APIPage : public QWidget, public BasePage
{
Q_OBJECT
public:
- explicit PasteEEPage(QWidget *parent = 0);
- ~PasteEEPage();
+ explicit APIPage(QWidget *parent = 0);
+ ~APIPage();
QString displayName() const override
{
- return tr("Log Upload");
+ return tr("APIs");
}
QIcon icon() const override
{
- return APPLICATION->getThemedIcon("log");
+ return APPLICATION->getThemedIcon("worlds");
}
QString id() const override
{
- return "log-upload";
+ return "apis";
}
QString helpPage() const override
{
- return "Log-Upload";
+ return "APIs";
}
virtual bool apply() override;
@@ -54,9 +54,7 @@ private:
void loadSettings();
void applySettings();
-private slots:
- void textEdited(const QString &text);
-
private:
- Ui::PasteEEPage *ui;
+ Ui::APIPage *ui;
};
+
diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui
new file mode 100644
index 00000000..28c53b79
--- /dev/null
+++ b/launcher/ui/pages/global/APIPage.ui
@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>APIPage</class>
+ <widget class="QWidget" name="APIPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>491</width>
+ <height>474</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string notr="true">Tab 1</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox_paste">
+ <property name="title">
+ <string>Pastebin URL</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <pointsize>10</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: only input that starts with &lt;span style=&quot; font-weight:600;&quot;&gt;http://&lt;/span&gt; or &lt;span style=&quot; font-weight:600;&quot;&gt;https://&lt;/span&gt; will be accepted.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="scaledContents">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="urlChoices">
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ <property name="insertPolicy">
+ <enum>QComboBox::NoInsert</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>https://0x0.st</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>https://paste.polymc.org</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Here you can choose from a predefined list of paste services, or input the URL of a different paste service of your choice, provided it supports the same protocol as 0x0.st, that is POST a file parameter to the URL and return a link in the response body.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_msa">
+ <property name="title">
+ <string>Microsoft Authentication</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Note: you probably don't need to set this if logging in via Microsoft Authentication already works.</string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="msaClientID">
+ <property name="placeholderText">
+ <string>(Default)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Enter a custom client ID for Microsoft Authentication here. </string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp
index d3eb2655..eb1ee8d3 100644
--- a/launcher/ui/pages/global/AccountListPage.cpp
+++ b/launcher/ui/pages/global/AccountListPage.cpp
@@ -24,6 +24,7 @@
#include "net/NetJob.h"
#include "ui/dialogs/ProgressDialog.h"
+#include "ui/dialogs/OfflineLoginDialog.h"
#include "ui/dialogs/LoginDialog.h"
#include "ui/dialogs/MSALoginDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
@@ -37,8 +38,6 @@
#include "BuildConfig.h"
-#include "Secrets.h"
-
AccountListPage::AccountListPage(QWidget *parent)
: QMainWindow(parent), ui(new Ui::AccountListPage)
{
@@ -74,7 +73,10 @@ AccountListPage::AccountListPage(QWidget *parent)
updateButtonStates();
// Xbox authentication won't work without a client identifier, so disable the button if it is missing
- ui->actionAddMicrosoft->setVisible(Secrets::hasMSAClientID());
+ if (APPLICATION->getMSAClientID().isEmpty()) {
+ ui->actionAddMicrosoft->setVisible(false);
+ ui->actionAddMicrosoft->setToolTip(tr("No Microsoft Authentication client ID was set."));
+ }
}
AccountListPage::~AccountListPage()
@@ -134,8 +136,8 @@ void AccountListPage::on_actionAddMicrosoft_triggered()
this,
tr("Microsoft Accounts not available"),
tr(
- "Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated MultiMC.\n\n"
- "Please update both your operating system and MultiMC."
+ "Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated PolyMC.\n\n"
+ "Please update both your operating system and PolyMC."
),
QMessageBox::Warning
)->exec();
@@ -155,6 +157,35 @@ void AccountListPage::on_actionAddMicrosoft_triggered()
}
}
+void AccountListPage::on_actionAddOffline_triggered()
+{
+ if (!m_accounts->anyAccountIsValid()) {
+ QMessageBox::warning(
+ this,
+ tr("Error"),
+ tr(
+ "You must add a Microsoft or Mojang account that owns Minecraft before you can add an offline account."
+ "<br><br>"
+ "If you have lost your account you can contact Microsoft for support."
+ )
+ );
+ return;
+ }
+
+ MinecraftAccountPtr account = OfflineLoginDialog::newAccount(
+ this,
+ tr("Please enter your desired username to add your offline account.")
+ );
+
+ if (account)
+ {
+ m_accounts->addAccount(account);
+ if (m_accounts->count() == 1) {
+ m_accounts->setDefaultAccount(account);
+ }
+ }
+}
+
void AccountListPage::on_actionRemove_triggered()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h
index 1c65e708..841c3fd2 100644
--- a/launcher/ui/pages/global/AccountListPage.h
+++ b/launcher/ui/pages/global/AccountListPage.h
@@ -62,6 +62,7 @@ public:
public slots:
void on_actionAddMojang_triggered();
void on_actionAddMicrosoft_triggered();
+ void on_actionAddOffline_triggered();
void on_actionRemove_triggered();
void on_actionRefresh_triggered();
void on_actionSetDefault_triggered();
diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui
index 29738c02..d21a92e2 100644
--- a/launcher/ui/pages/global/AccountListPage.ui
+++ b/launcher/ui/pages/global/AccountListPage.ui
@@ -54,6 +54,7 @@
</attribute>
<addaction name="actionAddMicrosoft"/>
<addaction name="actionAddMojang"/>
+ <addaction name="actionAddOffline"/>
<addaction name="actionRefresh"/>
<addaction name="actionRemove"/>
<addaction name="actionSetDefault"/>
@@ -103,6 +104,11 @@
<string>Add Microsoft</string>
</property>
</action>
+ <action name="actionAddOffline">
+ <property name="text">
+ <string>Add Offline</string>
+ </property>
+ </action>
<action name="actionRefresh">
<property name="text">
<string>Refresh</string>
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 2eb73e44..0ffe8050 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -73,11 +73,6 @@ LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::Launch
{
ui->updateSettingsBox->setHidden(true);
}
- // Analytics
- if(BuildConfig.ANALYTICS_ID.isEmpty())
- {
- ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->analyticsTab));
- }
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
@@ -251,32 +246,31 @@ void LauncherPage::applySettings()
//FIXME: make generic
switch (ui->themeComboBox->currentIndex())
{
- case 1:
+ case 0:
s->set("IconTheme", "pe_dark");
break;
- case 2:
+ case 1:
s->set("IconTheme", "pe_light");
break;
- case 3:
+ case 2:
s->set("IconTheme", "pe_blue");
break;
- case 4:
+ case 3:
s->set("IconTheme", "pe_colored");
break;
- case 5:
+ case 4:
s->set("IconTheme", "OSX");
break;
- case 6:
+ case 5:
s->set("IconTheme", "iOS");
break;
- case 7:
+ case 6:
s->set("IconTheme", "flat");
break;
- case 8:
+ case 7:
s->set("IconTheme", "custom");
break;
- case 0:
- default:
+ case 8:
s->set("IconTheme", "multimc");
break;
}
@@ -321,12 +315,6 @@ void LauncherPage::applySettings()
s->set("InstSortMode", "Name");
break;
}
-
- // Analytics
- if(!BuildConfig.ANALYTICS_ID.isEmpty())
- {
- s->set("Analytics", ui->analyticsCheck->isChecked());
- }
}
void LauncherPage::loadSettings()
{
@@ -338,40 +326,40 @@ void LauncherPage::loadSettings()
auto theme = s->get("IconTheme").toString();
if (theme == "pe_dark")
{
- ui->themeComboBox->setCurrentIndex(1);
+ ui->themeComboBox->setCurrentIndex(0);
}
else if (theme == "pe_light")
{
- ui->themeComboBox->setCurrentIndex(2);
+ ui->themeComboBox->setCurrentIndex(1);
}
else if (theme == "pe_blue")
{
- ui->themeComboBox->setCurrentIndex(3);
+ ui->themeComboBox->setCurrentIndex(2);
}
else if (theme == "pe_colored")
{
- ui->themeComboBox->setCurrentIndex(4);
+ ui->themeComboBox->setCurrentIndex(3);
}
else if (theme == "OSX")
{
- ui->themeComboBox->setCurrentIndex(5);
+ ui->themeComboBox->setCurrentIndex(4);
}
else if (theme == "iOS")
{
- ui->themeComboBox->setCurrentIndex(6);
+ ui->themeComboBox->setCurrentIndex(5);
}
else if (theme == "flat")
{
+ ui->themeComboBox->setCurrentIndex(6);
+ }
+ else if (theme == "multimc")
+ {
ui->themeComboBox->setCurrentIndex(7);
}
else if (theme == "custom")
{
ui->themeComboBox->setCurrentIndex(8);
}
- else
- {
- ui->themeComboBox->setCurrentIndex(0);
- }
{
auto currentTheme = s->get("ApplicationTheme").toString();
@@ -422,12 +410,6 @@ void LauncherPage::loadSettings()
{
ui->sortByNameBtn->setChecked(true);
}
-
- // Analytics
- if(!BuildConfig.ANALYTICS_ID.isEmpty())
- {
- ui->analyticsCheck->setChecked(s->get("Analytics").toBool());
- }
}
void LauncherPage::refreshFontPreview()
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 62a66d73..47fed873 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -264,11 +264,6 @@
</property>
<item>
<property name="text">
- <string>Default</string>
- </property>
- </item>
- <item>
- <property name="text">
<string>Simple (Dark Icons)</string>
</property>
</item>
@@ -307,6 +302,11 @@
<string>Custom</string>
</property>
</item>
+ <item>
+ <property name="text">
+ <string>MultiMC</string>
+ </property>
+ </item>
</widget>
</item>
<item row="1" column="1">
@@ -485,69 +485,6 @@
</item>
</layout>
</widget>
- <widget class="QWidget" name="analyticsTab">
- <attribute name="title">
- <string>Analytics</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_8">
- <item>
- <widget class="QGroupBox" name="consoleSettingsBox_2">
- <property name="title">
- <string>Analytics Settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <item>
- <widget class="QCheckBox" name="analyticsCheck">
- <property name="text">
- <string>Send anonymous usage statistics?</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;
-&lt;body&gt;
-&lt;p&gt;The launcher sends anonymous usage statistics on every start of the application.&lt;/p&gt;&lt;p&gt;The following data is collected:&lt;/p&gt;
-&lt;ul&gt;
-&lt;li&gt;Launcher version.&lt;/li&gt;
-&lt;li&gt;Operating system name, version and architecture.&lt;/li&gt;
-&lt;li&gt;CPU architecture (kernel architecture on linux).&lt;/li&gt;
-&lt;li&gt;Size of system memory.&lt;/li&gt;
-&lt;li&gt;Java version, architecture and memory settings.&lt;/li&gt;
-&lt;/ul&gt;
-&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
</widget>
</item>
</layout>
diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index c763f8ac..5470a586 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -71,6 +71,9 @@ void MinecraftPage::applySettings()
s->set("ShowGameTime", ui->showGameTime->isChecked());
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
s->set("RecordGameTime", ui->recordGameTime->isChecked());
+
+ // Miscellaneous
+ s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
}
void MinecraftPage::loadSettings()
@@ -88,4 +91,6 @@ void MinecraftPage::loadSettings()
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
+
+ ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
}
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 857b8cfb..a28b1f59 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -165,6 +165,25 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Miscellaneous</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="closeAfterLaunchCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PolyMC will automatically reopen when the game crashes or exits.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Close PolyMC after game window opens</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacerMinecraft">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -184,7 +203,6 @@
</layout>
</widget>
<tabstops>
- <tabstop>tabWidget</tabstop>
<tabstop>maximizedCheckBox</tabstop>
<tabstop>windowWidthSpinBox</tabstop>
<tabstop>windowHeightSpinBox</tabstop>
diff --git a/launcher/ui/pages/global/PasteEEPage.ui b/launcher/ui/pages/global/PasteEEPage.ui
deleted file mode 100644
index 10883781..00000000
--- a/launcher/ui/pages/global/PasteEEPage.ui
+++ /dev/null
@@ -1,128 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>PasteEEPage</class>
- <widget class="QWidget" name="PasteEEPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>491</width>
- <height>474</height>
- </rect>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>paste.ee API key</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_10">
- <item>
- <widget class="QRadioButton" name="multimcButton">
- <property name="text">
- <string>MultiMC key - 12MB &amp;upload limit</string>
- </property>
- <attribute name="buttonGroup">
- <string notr="true">pasteButtonGroup</string>
- </attribute>
- </widget>
- </item>
- <item>
- <widget class="QRadioButton" name="customButton">
- <property name="text">
- <string>&amp;Your own key - 12MB upload limit:</string>
- </property>
- <attribute name="buttonGroup">
- <string notr="true">pasteButtonGroup</string>
- </attribute>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="customAPIkeyEdit">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
- <property name="placeholderText">
- <string>Paste your API key here!</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="Line" name="line">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://paste.ee&quot;&gt;paste.ee&lt;/a&gt; is used by MultiMC for log uploads. If you have a &lt;a href=&quot;https://paste.ee&quot;&gt;paste.ee&lt;/a&gt; account, you can add your API key here and have your uploaded logs paired with your account.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="textFormat">
- <enum>Qt::RichText</enum>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>216</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
- </widget>
- <tabstops>
- <tabstop>tabWidget</tabstop>
- <tabstop>multimcButton</tabstop>
- <tabstop>customButton</tabstop>
- <tabstop>customAPIkeyEdit</tabstop>
- </tabstops>
- <resources/>
- <connections/>
- <buttongroups>
- <buttongroup name="pasteButtonGroup"/>
- </buttongroups>
-</ui>
diff --git a/launcher/ui/pages/instance/LegacyUpgradePage.ui b/launcher/ui/pages/instance/LegacyUpgradePage.ui
index 085919e3..b22c03e5 100644
--- a/launcher/ui/pages/instance/LegacyUpgradePage.ui
+++ b/launcher/ui/pages/instance/LegacyUpgradePage.ui
@@ -26,7 +26,14 @@
<item>
<widget class="QTextBrowser" name="textBrowser">
<property name="html">
- <string>&lt;html&gt;&lt;body&gt;&lt;h1&gt;Upgrade is required&lt;/h1&gt;&lt;p&gt;MultiMC now supports old Minecraft versions and all the required features in the new (OneSix) instance format. As a consequence, the old (Legacy) format has been entirely disabled and old instances need to be upgraded.&lt;/p&gt;&lt;p&gt;The upgrade will create a new instance with the same contents as the current one, in the new format. The original instance will remain untouched, in case anything goes wrong in the process.&lt;/p&gt;&lt;p&gt;Please report any issues on our &lt;a href=&quot;https://github.com/MultiMC/Launcher/issues&quot;&gt;github issues page&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;There is also a &lt;a href=&quot;https://discord.gg/GtPmv93&quot;&gt;discord channel for testing here&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:600;&quot;&gt;Upgrade is required&lt;/span&gt;&lt;/h1&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;PolyMC now supports old Minecraft versions and all the required features in the new (OneSix) instance format. As a consequence, the old (Legacy) format has been entirely disabled and old instances need to be upgraded.&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The upgrade will create a new instance with the same contents as the current one, in the new format. The original instance will remain untouched, in case anything goes wrong in the process.&lt;/p&gt;
+&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Please report any issues on our &lt;a href=&quot;https://github.com/PolyMC/PolyMC/issues&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#3584e4;&quot;&gt;github issues page&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
diff --git a/launcher/ui/pages/instance/LogPage.ui b/launcher/ui/pages/instance/LogPage.ui
index ccfc1551..31bb368c 100644
--- a/launcher/ui/pages/instance/LogPage.ui
+++ b/launcher/ui/pages/instance/LogPage.ui
@@ -100,7 +100,7 @@
<item>
<widget class="QPushButton" name="btnPaste">
<property name="toolTip">
- <string>Upload the log to paste.ee - it will stay online for a month</string>
+ <string>Upload the log to the paste service configured in preferences</string>
</property>
<property name="text">
<string>Upload</string>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index e63b1434..494d32f0 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -26,6 +26,7 @@
#include "Application.h"
#include "ui/dialogs/CustomMessageBox.h"
+#include "ui/dialogs/ModDownloadDialog.h"
#include "ui/GuiUtil.h"
#include "DesktopServices.h"
@@ -36,6 +37,7 @@
#include "minecraft/PackProfile.h"
#include "Version.h"
+#include "ui/dialogs/ProgressDialog.h"
namespace {
// FIXME: wasteful
@@ -141,6 +143,11 @@ ModFolderPage::ModFolderPage(
ui(new Ui::ModFolderPage)
{
ui->setupUi(this);
+ if(id == "mods") {
+ auto act = new QAction(tr("Install Mods"), this);
+ ui->actionsToolbar->insertActionBefore(ui->actionView_configs,act);
+ connect(act, &QAction::triggered, this, &ModFolderPage::on_actionInstall_mods_triggered);
+ }
ui->actionsToolbar->insertSpacer(ui->actionView_configs);
m_inst = inst;
@@ -342,6 +349,44 @@ void ModFolderPage::on_actionRemove_triggered()
m_mods->deleteMods(selection.indexes());
}
+void ModFolderPage::on_actionInstall_mods_triggered()
+{
+ if(!m_controlsEnabled) {
+ return;
+ }
+ if(m_inst->typeName() != "Minecraft"){
+ return; //this is a null instance or a legacy instance
+ }
+ bool hasFabric = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty();
+ bool hasForge = !((MinecraftInstance *)m_inst)->getPackProfile()->getComponentVersion("net.minecraftforge").isEmpty();
+ if (!hasFabric && !hasForge) {
+ QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!"));
+ return;
+ }
+ ModDownloadDialog mdownload(m_mods, this, m_inst);
+ if(mdownload.exec()) {
+ ModDownloadTask *task = mdownload.getTask();
+ if (task) {
+ connect(task, &Task::failed, [this, task](QString reason) {
+ task->deleteLater();
+ CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
+ });
+ connect(task, &Task::succeeded, [this, task]() {
+ QStringList warnings = task->warnings();
+ if (warnings.count()) {
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'),
+ QMessageBox::Warning)->show();
+ }
+ task->deleteLater();
+ });
+ ProgressDialog loadDialog(this);
+ loadDialog.setSkipButton(true, tr("Abort"));
+ loadDialog.execWithTask(task);
+ m_mods->update();
+ }
+ }
+}
+
void ModFolderPage::on_actionView_configs_triggered()
{
DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true);
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index 8ef7559b..fbda3cd8 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -102,6 +102,7 @@ slots:
void on_actionRemove_triggered();
void on_actionEnable_triggered();
void on_actionDisable_triggered();
+ void on_actionInstall_mods_triggered();
void on_actionView_Folder_triggered();
void on_actionView_configs_triggered();
void ShowContextMenu(const QPoint &pos);
diff --git a/launcher/ui/pages/instance/OtherLogsPage.ui b/launcher/ui/pages/instance/OtherLogsPage.ui
index 56ff3b62..77f3e647 100644
--- a/launcher/ui/pages/instance/OtherLogsPage.ui
+++ b/launcher/ui/pages/instance/OtherLogsPage.ui
@@ -84,7 +84,7 @@
<item row="3" column="2">
<widget class="QPushButton" name="btnPaste">
<property name="toolTip">
- <string>Upload the log to paste.ee - it will stay online for a month</string>
+ <string>Upload the log to the paste service configured in preferences.</string>
</property>
<property name="text">
<string>Upload</string>
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index 6e57909b..0fa5f68d 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -395,7 +395,7 @@ void VersionPage::on_actionDownload_All_triggered()
{
CustomMessageBox::selectable(
this, tr("Error"),
- tr("MultiMC cannot download Minecraft or update instances unless you have at least "
+ tr("PolyMC cannot download Minecraft or update instances unless you have at least "
"one account added.\nPlease add your Mojang or Minecraft account."),
QMessageBox::Warning)->show();
return;
@@ -635,4 +635,3 @@ void VersionPage::onFilterTextChanged(const QString &newContents)
}
#include "VersionPage.moc"
-
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
new file mode 100644
index 00000000..2cf83261
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp
@@ -0,0 +1,273 @@
+#include "FlameModModel.h"
+#include "Application.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "FlameModPage.h"
+#include <Json.h>
+
+#include <MMCStrings.h>
+#include <Version.h>
+
+#include <QtMath>
+
+
+namespace FlameMod {
+
+ListModel::ListModel(FlameModPage *parent) : QAbstractListModel(parent)
+{
+}
+
+ListModel::~ListModel()
+{
+}
+
+int ListModel::rowCount(const QModelIndex &parent) const
+{
+ return modpacks.size();
+}
+
+int ListModel::columnCount(const QModelIndex &parent) const
+{
+ return 1;
+}
+
+QVariant ListModel::data(const QModelIndex &index, int role) const
+{
+ int pos = index.row();
+ if(pos >= modpacks.size() || pos < 0 || !index.isValid())
+ {
+ return QString("INVALID INDEX %1").arg(pos);
+ }
+
+ IndexedPack pack = modpacks.at(pos);
+ if(role == Qt::DisplayRole)
+ {
+ return pack.name;
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ if(pack.description.length() > 100)
+ {
+ //some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+
+ }
+ return pack.description;
+ }
+ else if(role == Qt::DecorationRole)
+ {
+ if(m_logoMap.contains(pack.logoName))
+ {
+ return (m_logoMap.value(pack.logoName));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl);
+ return icon;
+ }
+ else if(role == Qt::UserRole)
+ {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+
+ return QVariant();
+}
+
+void ListModel::logoLoaded(QString logo, QIcon out)
+{
+ m_loadingLogos.removeAll(logo);
+ m_logoMap.insert(logo, out);
+ for(int i = 0; i < modpacks.size(); i++) {
+ if(modpacks[i].logoName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ }
+ }
+}
+
+void ListModel::logoFailed(QString logo)
+{
+ m_failedLogos.append(logo);
+ m_loadingLogos.removeAll(logo);
+}
+
+void ListModel::requestLogo(QString logo, QString url)
+{
+ if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo))
+ {
+ return;
+ }
+
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlameMods", QString("logos/%1").arg(logo.section(".", 0, 0)));
+ auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
+ job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+
+ auto fullPath = entry->getFullPath();
+ QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job]
+ {
+ job->deleteLater();
+ emit logoLoaded(logo, QIcon(fullPath));
+ if(waitingCallbacks.contains(logo))
+ {
+ waitingCallbacks.value(logo)(fullPath);
+ }
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, logo, job]
+ {
+ job->deleteLater();
+ emit logoFailed(logo);
+ });
+
+ job->start();
+ m_loadingLogos.append(logo);
+}
+
+void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
+{
+ if(m_logoMap.contains(logo))
+ {
+ callback(APPLICATION->metacache()->resolveEntry("FlameMods", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ }
+ else
+ {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
+{
+ return QAbstractListModel::flags(index);
+}
+
+bool ListModel::canFetchMore(const QModelIndex& parent) const
+{
+ return searchState == CanPossiblyFetchMore;
+}
+
+void ListModel::fetchMore(const QModelIndex& parent)
+{
+ if (parent.isValid())
+ return;
+ if(nextSearchOffset == 0) {
+ qWarning() << "fetchMore with 0 offset is wrong...";
+ return;
+ }
+ performPaginatedSearch();
+}
+const char* sorts[6]{"Featured","Popularity","LastUpdated","Name","Author","TotalDownloads"};
+
+void ListModel::performPaginatedSearch()
+{
+
+ QString mcVersion = ((MinecraftInstance *)((FlameModPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft");
+ bool hasFabric = !((MinecraftInstance *)((FlameModPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty();
+ auto netJob = new NetJob("Flame::Search", APPLICATION->network());
+ auto searchUrl = QString(
+ "https://addons-ecs.forgesvc.net/api/v2/addon/search?"
+ "gameId=432&"
+ "categoryId=0&"
+ "sectionId=6&"
+
+ "index=%1&"
+ "pageSize=25&"
+ "searchFilter=%2&"
+ "sort=%3&"
+ "%4"
+ "gameVersion=%5"
+ )
+ .arg(nextSearchOffset)
+ .arg(currentSearchTerm)
+ .arg(sorts[currentSort])
+ .arg(hasFabric ? "modLoaderType=4&" : "")
+ .arg(mcVersion);
+
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+ QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
+}
+
+void ListModel::searchWithTerm(const QString &term, const int sort)
+{
+ if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) {
+ return;
+ }
+ currentSearchTerm = term;
+ currentSort = sort;
+ if(jobPtr) {
+ jobPtr->abort();
+ searchState = ResetRequested;
+ return;
+ }
+ else {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+ }
+ nextSearchOffset = 0;
+ performPaginatedSearch();
+}
+
+void ListModel::searchRequestFinished()
+{
+ jobPtr.reset();
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ QList<FlameMod::IndexedPack> newList;
+ auto packs = doc.array();
+ for(auto packRaw : packs) {
+ auto packObj = packRaw.toObject();
+
+ FlameMod::IndexedPack pack;
+ try
+ {
+ FlameMod::loadIndexedPack(pack, packObj);
+ newList.append(pack);
+ }
+ catch(const JSONValidationError &e)
+ {
+ qWarning() << "Error while loading mod from Flame: " << e.cause();
+ continue;
+ }
+ }
+ if(packs.size() < 25) {
+ searchState = Finished;
+ } else {
+ nextSearchOffset += 25;
+ searchState = CanPossiblyFetchMore;
+ }
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
+ modpacks.append(newList);
+ endInsertRows();
+}
+
+void ListModel::searchRequestFailed(QString reason)
+{
+ jobPtr.reset();
+
+ if(searchState == ResetRequested) {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+
+ nextSearchOffset = 0;
+ performPaginatedSearch();
+ } else {
+ searchState = Finished;
+ }
+}
+
+}
+
diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h
new file mode 100644
index 00000000..0c1cb95e
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameModModel.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <RWStorage.h>
+
+#include <QAbstractListModel>
+#include <QSortFilterProxyModel>
+#include <QThreadPool>
+#include <QIcon>
+#include <QStyledItemDelegate>
+#include <QList>
+#include <QString>
+#include <QStringList>
+#include <QMetaType>
+
+#include <functional>
+#include <net/NetJob.h>
+
+#include <modplatform/flame/FlamePackIndex.h>
+#include "modplatform/flame/FlameModIndex.h"
+#include "BaseInstance.h"
+#include "FlameModPage.h"
+
+namespace FlameMod {
+
+
+typedef QMap<QString, QIcon> LogoMap;
+typedef std::function<void(QString)> LogoCallback;
+
+class ListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ ListModel(FlameModPage *parent);
+ virtual ~ListModel();
+
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ bool canFetchMore(const QModelIndex & parent) const override;
+ void fetchMore(const QModelIndex & parent) override;
+
+ void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+ void searchWithTerm(const QString &term, const int sort);
+
+private slots:
+ void performPaginatedSearch();
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, QIcon out);
+
+ void searchRequestFinished();
+ void searchRequestFailed(QString reason);
+
+private:
+ void requestLogo(QString file, QString url);
+
+private:
+ QList<IndexedPack> modpacks;
+ QStringList m_failedLogos;
+ QStringList m_loadingLogos;
+ LogoMap m_logoMap;
+ QMap<QString, LogoCallback> waitingCallbacks;
+
+ QString currentSearchTerm;
+ int currentSort = 0;
+ int nextSearchOffset = 0;
+ enum SearchState {
+ None,
+ CanPossiblyFetchMore,
+ ResetRequested,
+ Finished
+ } searchState = None;
+ NetJob::Ptr jobPtr;
+ QByteArray response;
+};
+
+}
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
new file mode 100644
index 00000000..a816c681
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
@@ -0,0 +1,196 @@
+#include "FlameModPage.h"
+#include "ui_FlameModPage.h"
+
+#include <QKeyEvent>
+
+#include "Application.h"
+#include "Json.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+#include "InstanceImportTask.h"
+#include "FlameModModel.h"
+#include "ModDownloadTask.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+
+FlameModPage::FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance)
+ : QWidget(dialog), m_instance(instance), ui(new Ui::FlameModPage), dialog(dialog)
+{
+ ui->setupUi(this);
+ connect(ui->searchButton, &QPushButton::clicked, this, &FlameModPage::triggerSearch);
+ ui->searchEdit->installEventFilter(this);
+ listModel = new FlameMod::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ // index is used to set the sorting with the flame api
+ ui->sortByBox->addItem(tr("Sort by Featured"));
+ ui->sortByBox->addItem(tr("Sort by Popularity"));
+ ui->sortByBox->addItem(tr("Sort by last updated"));
+ ui->sortByBox->addItem(tr("Sort by Name"));
+ ui->sortByBox->addItem(tr("Sort by Author"));
+ ui->sortByBox->addItem(tr("Sort by Downloads"));
+
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged);
+}
+
+FlameModPage::~FlameModPage()
+{
+ delete ui;
+}
+
+bool FlameModPage::eventFilter(QObject* watched, QEvent* event)
+{
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QWidget::eventFilter(watched, event);
+}
+
+bool FlameModPage::shouldDisplay() const
+{
+ return true;
+}
+
+void FlameModPage::openedImpl()
+{
+ suggestCurrent();
+ triggerSearch();
+}
+
+void FlameModPage::triggerSearch()
+{
+ listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+}
+
+void FlameModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ ui->versionSelectionBox->clear();
+
+ if(!first.isValid())
+ {
+ if(isOpened)
+ {
+ dialog->setSuggestedMod();
+ }
+ return;
+ }
+
+ current = listModel->data(first, Qt::UserRole).value<FlameMod::IndexedPack>();
+ QString text = "";
+ QString name = current.name;
+
+ if (current.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
+ if (!current.authors.empty()) {
+ auto authorToStr = [](FlameMod::ModpackAuthor & author) {
+ if(author.url.isEmpty()) {
+ return author.name;
+ }
+ return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
+ };
+ QStringList authorStrs;
+ for(auto & author: current.authors) {
+ authorStrs.push_back(authorToStr(author));
+ }
+ text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ }
+ text += "<br><br>";
+
+ ui->packDescription->setHtml(text + current.description);
+
+ if (!current.versionsLoaded)
+ {
+ qDebug() << "Loading flame mod versions";
+ auto netJob = new NetJob(QString("Flame::ModVersions(%1)").arg(current.name), APPLICATION->network());
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+ int addonId = current.addonId;
+ netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get()));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, netJob]
+ {
+ netJob->deleteLater();
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Flame at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+ QJsonArray arr = doc.array();
+ try
+ {
+ FlameMod::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance);
+ }
+ catch(const JSONValidationError &e)
+ {
+ qDebug() << *response;
+ qWarning() << "Error while reading Flame mod version: " << e.cause();
+ }
+ auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
+ QString mcVersion = packProfile->getComponentVersion("net.minecraft");
+ QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge";
+ for(int i = 0; i < current.versions.size(); i++) {
+ auto version = current.versions[i];
+ if(!version.mcVersion.contains(mcVersion)){
+ continue;
+ }
+ ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ }
+ if(ui->versionSelectionBox->count() == 0){
+ ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant(-1));
+ }
+
+ suggestCurrent();
+ });
+ netJob->start();
+ }
+ else
+ {
+ for(int i = 0; i < current.versions.size(); i++) {
+ ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
+ }
+ if(ui->versionSelectionBox->count() == 0){
+ ui->versionSelectionBox->addItem(tr("No Valid Version found!"), QVariant(-1));
+ }
+ suggestCurrent();
+ }
+}
+
+void FlameModPage::suggestCurrent()
+{
+ if(!isOpened)
+ {
+ return;
+ }
+
+ if (selectedVersion == -1)
+ {
+ dialog->setSuggestedMod();
+ return;
+ }
+
+ auto version = current.versions[selectedVersion];
+ dialog->setSuggestedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName , dialog->mods));
+}
+
+void FlameModPage::onVersionSelectionChanged(QString data)
+{
+ if(data.isNull() || data.isEmpty())
+ {
+ selectedVersion = -1;
+ return;
+ }
+ selectedVersion = ui->versionSelectionBox->currentData().toInt();
+ suggestCurrent();
+}
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h
new file mode 100644
index 00000000..8fa3248a
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <QWidget>
+
+#include "ui/pages/BasePage.h"
+#include <Application.h>
+#include "tasks/Task.h"
+#include "modplatform/flame/FlameModIndex.h"
+
+namespace Ui
+{
+class FlameModPage;
+}
+
+class ModDownloadDialog;
+
+namespace FlameMod {
+ class ListModel;
+}
+
+class FlameModPage : public QWidget, public BasePage
+{
+ Q_OBJECT
+
+public:
+ explicit FlameModPage(ModDownloadDialog *dialog, BaseInstance *instance);
+ virtual ~FlameModPage();
+ virtual QString displayName() const override
+ {
+ return tr("CurseForge");
+ }
+ virtual QIcon icon() const override
+ {
+ return APPLICATION->getThemedIcon("flame");
+ }
+ virtual QString id() const override
+ {
+ return "curseforge";
+ }
+ virtual QString helpPage() const override
+ {
+ return "Flame-platform";
+ }
+ virtual bool shouldDisplay() const override;
+
+ void openedImpl() override;
+
+ bool eventFilter(QObject * watched, QEvent * event) override;
+
+ BaseInstance *m_instance;
+
+private:
+ void suggestCurrent();
+
+private slots:
+ void triggerSearch();
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+
+private:
+ Ui::FlameModPage *ui = nullptr;
+ ModDownloadDialog* dialog = nullptr;
+ FlameMod::ListModel* listModel = nullptr;
+ FlameMod::IndexedPack current;
+
+ int selectedVersion = -1;
+};
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.ui b/launcher/ui/pages/modplatform/flame/FlameModPage.ui
new file mode 100644
index 00000000..7da0bb4a
--- /dev/null
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FlameModPage</class>
+ <widget class="QWidget" name="FlameModPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0">
+ <widget class="QListView" name="packView">
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index 891676cf..fe163cae 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -6,9 +6,6 @@
#include <Version.h>
#include <QtMath>
-#include <QLabel>
-
-#include <RWStorage.h>
namespace Flame {
@@ -100,12 +97,13 @@ void ListModel::requestLogo(QString logo, QString url)
}
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
- NetJob *job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
+ auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
- QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath]
+ QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job]
{
+ job->deleteLater();
emit logoLoaded(logo, QIcon(fullPath));
if(waitingCallbacks.contains(logo))
{
@@ -113,8 +111,9 @@ void ListModel::requestLogo(QString logo, QString url)
}
});
- QObject::connect(job, &NetJob::failed, this, [this, logo]
+ QObject::connect(job, &NetJob::failed, this, [this, logo, job]
{
+ job->deleteLater();
emit logoFailed(logo);
});
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
new file mode 100644
index 00000000..5a18830a
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -0,0 +1,276 @@
+#include "ModrinthModel.h"
+#include "Application.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "ModrinthPage.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+#include <Json.h>
+
+#include <MMCStrings.h>
+#include <Version.h>
+
+#include <QtMath>
+#include <QMessageBox>
+
+
+namespace Modrinth {
+
+ListModel::ListModel(ModrinthPage *parent) : QAbstractListModel(parent)
+{
+}
+
+ListModel::~ListModel()
+{
+}
+
+int ListModel::rowCount(const QModelIndex &parent) const
+{
+ return modpacks.size();
+}
+
+int ListModel::columnCount(const QModelIndex &parent) const
+{
+ return 1;
+}
+
+QVariant ListModel::data(const QModelIndex &index, int role) const
+{
+ int pos = index.row();
+ if(pos >= modpacks.size() || pos < 0 || !index.isValid())
+ {
+ return QString("INVALID INDEX %1").arg(pos);
+ }
+
+ IndexedPack pack = modpacks.at(pos);
+ if(role == Qt::DisplayRole)
+ {
+ return pack.name;
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ if(pack.description.length() > 100)
+ {
+ //some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+
+ }
+ return pack.description;
+ }
+ else if(role == Qt::DecorationRole)
+ {
+ if(m_logoMap.contains(pack.logoName))
+ {
+ return (m_logoMap.value(pack.logoName));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl);
+ return icon;
+ }
+ else if(role == Qt::UserRole)
+ {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+
+ return QVariant();
+}
+
+void ListModel::logoLoaded(QString logo, QIcon out)
+{
+ m_loadingLogos.removeAll(logo);
+ m_logoMap.insert(logo, out);
+ for(int i = 0; i < modpacks.size(); i++) {
+ if(modpacks[i].logoName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ }
+ }
+}
+
+void ListModel::logoFailed(QString logo)
+{
+ m_failedLogos.append(logo);
+ m_loadingLogos.removeAll(logo);
+}
+
+void ListModel::requestLogo(QString logo, QString url)
+{
+ if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo))
+ {
+ return;
+ }
+
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
+ auto job = new NetJob(QString("Modrinth Icon Download %1").arg(logo), APPLICATION->network());
+ job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+
+ auto fullPath = entry->getFullPath();
+ QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job]
+ {
+ job->deleteLater();
+ emit logoLoaded(logo, QIcon(fullPath));
+ if(waitingCallbacks.contains(logo))
+ {
+ waitingCallbacks.value(logo)(fullPath);
+ }
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, logo, job]
+ {
+ job->deleteLater();
+ emit logoFailed(logo);
+ });
+
+ job->start();
+ m_loadingLogos.append(logo);
+}
+
+void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
+{
+ if(m_logoMap.contains(logo))
+ {
+ callback(APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ }
+ else
+ {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
+{
+ return QAbstractListModel::flags(index);
+}
+
+bool ListModel::canFetchMore(const QModelIndex& parent) const
+{
+ return searchState == CanPossiblyFetchMore;
+}
+
+void ListModel::fetchMore(const QModelIndex& parent)
+{
+ if (parent.isValid())
+ return;
+ if(nextSearchOffset == 0) {
+ qWarning() << "fetchMore with 0 offset is wrong...";
+ return;
+ }
+ performPaginatedSearch();
+}
+const char* sorts[5]{"relevance","downloads","follows","updated","newest"};
+
+void ListModel::performPaginatedSearch()
+{
+
+ QString mcVersion = ((MinecraftInstance *)((ModrinthPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.minecraft");
+ bool hasFabric = !((MinecraftInstance *)((ModrinthPage *)parent())->m_instance)->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty();
+ auto netJob = new NetJob("Modrinth::Search", APPLICATION->network());
+ auto searchUrl = QString(
+ "https://api.modrinth.com/v2/search?"
+ "offset=%1&"
+ "limit=25&"
+ "query=%2&"
+ "index=%3&"
+ "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]"
+ )
+ .arg(nextSearchOffset)
+ .arg(currentSearchTerm)
+ .arg(sorts[currentSort])
+ .arg(hasFabric ? "fabric" : "forge")
+ .arg(mcVersion);
+
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+ QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
+}
+
+void ListModel::searchWithTerm(const QString &term, const int sort)
+{
+ if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) {
+ return;
+ }
+ currentSearchTerm = term;
+ currentSort = sort;
+ if(jobPtr) {
+ jobPtr->abort();
+ searchState = ResetRequested;
+ return;
+ }
+ else {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+ }
+ nextSearchOffset = 0;
+ performPaginatedSearch();
+}
+
+void Modrinth::ListModel::searchRequestFinished()
+{
+ jobPtr.reset();
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ QList<Modrinth::IndexedPack> newList;
+ auto packs = doc.object().value("hits").toArray();
+ for(auto packRaw : packs) {
+ auto packObj = packRaw.toObject();
+
+ Modrinth::IndexedPack pack;
+ try
+ {
+ Modrinth::loadIndexedPack(pack, packObj);
+ newList.append(pack);
+ }
+ catch(const JSONValidationError &e)
+ {
+ qWarning() << "Error while loading mod from Modrinth: " << e.cause();
+ continue;
+ }
+ }
+ if(packs.size() < 25) {
+ searchState = Finished;
+ } else {
+ nextSearchOffset += 25;
+ searchState = CanPossiblyFetchMore;
+ }
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
+ modpacks.append(newList);
+ endInsertRows();
+}
+
+void Modrinth::ListModel::searchRequestFailed(QString reason)
+{
+ if(jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409){
+ //409 Gone, notify user to update
+ QMessageBox::critical(nullptr, tr("Error"), tr("Modrinth API version too old!\nPlease update PolyMC!"));
+ //self-destruct
+ ((ModDownloadDialog *)((ModrinthPage *)parent())->parentWidget())->reject();
+ }
+ jobPtr.reset();
+
+ if(searchState == ResetRequested) {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+
+ nextSearchOffset = 0;
+ performPaginatedSearch();
+ } else {
+ searchState = Finished;
+ }
+}
+
+}
+
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
new file mode 100644
index 00000000..53f1f134
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <RWStorage.h>
+
+#include <QAbstractListModel>
+#include <QSortFilterProxyModel>
+#include <QThreadPool>
+#include <QIcon>
+#include <QStyledItemDelegate>
+#include <QList>
+#include <QString>
+#include <QStringList>
+#include <QMetaType>
+
+#include <functional>
+#include <net/NetJob.h>
+
+#include <modplatform/flame/FlamePackIndex.h>
+#include "modplatform/modrinth/ModrinthPackIndex.h"
+#include "BaseInstance.h"
+#include "ModrinthPage.h"
+
+namespace Modrinth {
+
+
+typedef QMap<QString, QIcon> LogoMap;
+typedef std::function<void(QString)> LogoCallback;
+
+class ListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ ListModel(ModrinthPage *parent);
+ virtual ~ListModel();
+
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ bool canFetchMore(const QModelIndex & parent) const override;
+ void fetchMore(const QModelIndex & parent) override;
+
+ void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+ void searchWithTerm(const QString &term, const int sort);
+
+private slots:
+ void performPaginatedSearch();
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, QIcon out);
+
+ void searchRequestFinished();
+ void searchRequestFailed(QString reason);
+
+private:
+ void requestLogo(QString file, QString url);
+
+private:
+ QList<IndexedPack> modpacks;
+ QStringList m_failedLogos;
+ QStringList m_loadingLogos;
+ LogoMap m_logoMap;
+ QMap<QString, LogoCallback> waitingCallbacks;
+
+ QString currentSearchTerm;
+ int currentSort = 0;
+ int nextSearchOffset = 0;
+ enum SearchState {
+ None,
+ CanPossiblyFetchMore,
+ ResetRequested,
+ Finished
+ } searchState = None;
+ NetJob::Ptr jobPtr;
+ QByteArray response;
+};
+
+}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
new file mode 100644
index 00000000..c5a54c29
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -0,0 +1,180 @@
+#include "ModrinthPage.h"
+#include "ui_ModrinthPage.h"
+
+#include <QKeyEvent>
+
+#include "Application.h"
+#include "Json.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+#include "InstanceImportTask.h"
+#include "ModrinthModel.h"
+#include "ModDownloadTask.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+
+ModrinthPage::ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance)
+ : QWidget(dialog), m_instance(instance), ui(new Ui::ModrinthPage), dialog(dialog)
+{
+ ui->setupUi(this);
+ connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch);
+ ui->searchEdit->installEventFilter(this);
+ listModel = new Modrinth::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ // index is used to set the sorting with the modrinth api
+ ui->sortByBox->addItem(tr("Sort by Relevence"));
+ ui->sortByBox->addItem(tr("Sort by Downloads"));
+ ui->sortByBox->addItem(tr("Sort by Follows"));
+ ui->sortByBox->addItem(tr("Sort by last updated"));
+ ui->sortByBox->addItem(tr("Sort by newest"));
+
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
+}
+
+ModrinthPage::~ModrinthPage()
+{
+ delete ui;
+}
+
+bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
+{
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QWidget::eventFilter(watched, event);
+}
+
+bool ModrinthPage::shouldDisplay() const
+{
+ return true;
+}
+
+void ModrinthPage::openedImpl()
+{
+ suggestCurrent();
+ triggerSearch();
+}
+
+void ModrinthPage::triggerSearch()
+{
+ listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+}
+
+void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ ui->versionSelectionBox->clear();
+
+ if(!first.isValid())
+ {
+ if(isOpened)
+ {
+ dialog->setSuggestedMod();
+ }
+ return;
+ }
+
+ current = listModel->data(first, Qt::UserRole).value<Modrinth::IndexedPack>();
+ QString text = "";
+ QString name = current.name;
+
+ if (current.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
+ text += "<br>"+ tr(" by ") + "<a href=\""+current.author.url+"\">"+current.author.name+"</a><br><br>";
+ ui->packDescription->setHtml(text + current.description);
+
+ if (!current.versionsLoaded)
+ {
+ qDebug() << "Loading Modrinth mod versions";
+ auto netJob = new NetJob(QString("Modrinth::ModVersions(%1)").arg(current.name), APPLICATION->network());
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+ QString addonId = current.addonId;
+ netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.modrinth.com/v2/project/%1/version").arg(addonId), response.get()));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, netJob]
+ {
+ netJob->deleteLater();
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+ QJsonArray arr = doc.array();
+ try
+ {
+ Modrinth::loadIndexedPackVersions(current, arr, APPLICATION->network(), m_instance);
+ }
+ catch(const JSONValidationError &e)
+ {
+ qDebug() << *response;
+ qWarning() << "Error while reading Modrinth mod version: " << e.cause();
+ }
+ auto packProfile = ((MinecraftInstance *)m_instance)->getPackProfile();
+ QString mcVersion = packProfile->getComponentVersion("net.minecraft");
+ QString loaderString = (packProfile->getComponentVersion("net.minecraftforge").isEmpty()) ? "fabric" : "forge";
+ for(int i = 0; i < current.versions.size(); i++) {
+ auto version = current.versions[i];
+ if(!version.mcVersion.contains(mcVersion) || !version.loaders.contains(loaderString)){
+ continue;
+ }
+ ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ }
+ if(ui->versionSelectionBox->count() == 0){
+ ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant(-1));
+ }
+
+ suggestCurrent();
+ });
+ netJob->start();
+ }
+ else
+ {
+ for(int i = 0; i < current.versions.size(); i++) {
+ ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i));
+ }
+ if(ui->versionSelectionBox->count() == 0){
+ ui->versionSelectionBox->addItem(tr("No Valid Version found !"), QVariant(-1));
+ }
+ suggestCurrent();
+ }
+}
+
+void ModrinthPage::suggestCurrent()
+{
+ if(!isOpened)
+ {
+ return;
+ }
+
+ if (selectedVersion == -1)
+ {
+ dialog->setSuggestedMod();
+ return;
+ }
+ auto version = current.versions[selectedVersion];
+ dialog->setSuggestedMod(current.name, new ModDownloadTask(version.downloadUrl, version.fileName , dialog->mods));
+}
+
+void ModrinthPage::onVersionSelectionChanged(QString data)
+{
+ if(data.isNull() || data.isEmpty())
+ {
+ selectedVersion = -1;
+ return;
+ }
+ selectedVersion = ui->versionSelectionBox->currentData().toInt();
+ suggestCurrent();
+}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
new file mode 100644
index 00000000..3c517069
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <QWidget>
+
+#include "ui/pages/BasePage.h"
+#include <Application.h>
+#include "tasks/Task.h"
+#include "modplatform/modrinth/ModrinthPackIndex.h"
+
+namespace Ui
+{
+class ModrinthPage;
+}
+
+class ModDownloadDialog;
+
+namespace Modrinth {
+ class ListModel;
+}
+
+class ModrinthPage : public QWidget, public BasePage
+{
+ Q_OBJECT
+
+public:
+ explicit ModrinthPage(ModDownloadDialog *dialog, BaseInstance *instance);
+ virtual ~ModrinthPage();
+ virtual QString displayName() const override
+ {
+ return tr("Modrinth");
+ }
+ virtual QIcon icon() const override
+ {
+ return APPLICATION->getThemedIcon("modrinth");
+ }
+ virtual QString id() const override
+ {
+ return "modrinth";
+ }
+ virtual QString helpPage() const override
+ {
+ return "Modrinth-platform";
+ }
+ virtual bool shouldDisplay() const override;
+
+ void openedImpl() override;
+
+ bool eventFilter(QObject * watched, QEvent * event) override;
+
+ BaseInstance *m_instance;
+
+private:
+ void suggestCurrent();
+
+private slots:
+ void triggerSearch();
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+
+private:
+ Ui::ModrinthPage *ui = nullptr;
+ ModDownloadDialog* dialog = nullptr;
+ Modrinth::ListModel* listModel = nullptr;
+ Modrinth::IndexedPack current;
+
+ int selectedVersion = -1;
+};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
new file mode 100644
index 00000000..6d183de5
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ModrinthPage</class>
+ <widget class="QWidget" name="ModrinthPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0">
+ <widget class="QListView" name="packView">
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/launcher/ui/setupwizard/AnalyticsWizardPage.cpp b/launcher/ui/setupwizard/AnalyticsWizardPage.cpp
deleted file mode 100644
index 3db2f6dc..00000000
--- a/launcher/ui/setupwizard/AnalyticsWizardPage.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#include "AnalyticsWizardPage.h"
-#include <Application.h>
-
-#include <QVBoxLayout>
-#include <QTextBrowser>
-#include <QCheckBox>
-
-#include <ganalytics.h>
-#include <BuildConfig.h>
-
-AnalyticsWizardPage::AnalyticsWizardPage(QWidget *parent)
- : BaseWizardPage(parent)
-{
- setObjectName(QStringLiteral("analyticsPage"));
- verticalLayout_3 = new QVBoxLayout(this);
- verticalLayout_3->setObjectName(QStringLiteral("verticalLayout_3"));
- textBrowser = new QTextBrowser(this);
- textBrowser->setObjectName(QStringLiteral("textBrowser"));
- textBrowser->setAcceptRichText(false);
- textBrowser->setOpenExternalLinks(true);
- verticalLayout_3->addWidget(textBrowser);
-
- checkBox = new QCheckBox(this);
- checkBox->setObjectName(QStringLiteral("checkBox"));
- checkBox->setChecked(true);
- verticalLayout_3->addWidget(checkBox);
- retranslate();
-}
-
-AnalyticsWizardPage::~AnalyticsWizardPage()
-{
-}
-
-bool AnalyticsWizardPage::validatePage()
-{
- auto settings = APPLICATION->settings();
- auto analytics = APPLICATION->analytics();
- auto status = checkBox->isChecked();
- settings->set("AnalyticsSeen", analytics->version());
- settings->set("Analytics", status);
- return true;
-}
-
-void AnalyticsWizardPage::retranslate()
-{
- setTitle(tr("Analytics"));
- setSubTitle(tr("We track some anonymous statistics about users."));
- textBrowser->setHtml(tr(
- "<html><body>"
- "<p>The launcher sends anonymous usage statistics on every start of the application. This helps us decide what platforms and issues to focus on.</p>"
- "<p>The data is processed by Google Analytics, see their <a href=\"https://support.google.com/analytics/answer/6004245?hl=en\">article on the "
- "matter</a>.</p>"
- "<p>The following data is collected:</p>"
- "<ul><li>A random unique ID of the installation.<br />It is stored in the application settings file.</li>"
- "<li>Anonymized (partial) IP address.</li>"
- "<li>Launcher version.</li>"
- "<li>Operating system name, version and architecture.</li>"
- "<li>CPU architecture (kernel architecture on linux).</li>"
- "<li>Size of system memory.</li>"
- "<li>Java version, architecture and memory settings.</li></ul>"
- "<p>If we change the tracked information, you will see this page again.</p></body></html>"));
- checkBox->setText(tr("Enable Analytics"));
-}
diff --git a/launcher/ui/setupwizard/AnalyticsWizardPage.h b/launcher/ui/setupwizard/AnalyticsWizardPage.h
deleted file mode 100644
index c451db2c..00000000
--- a/launcher/ui/setupwizard/AnalyticsWizardPage.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma once
-
-#include "BaseWizardPage.h"
-
-class QVBoxLayout;
-class QTextBrowser;
-class QCheckBox;
-
-class AnalyticsWizardPage : public BaseWizardPage
-{
- Q_OBJECT
-public:
- explicit AnalyticsWizardPage(QWidget *parent = Q_NULLPTR);
- virtual ~AnalyticsWizardPage();
-
- bool validatePage() override;
-
-protected:
- void retranslate() override;
-
-private:
- QVBoxLayout *verticalLayout_3 = nullptr;
- QTextBrowser *textBrowser = nullptr;
- QCheckBox *checkBox = nullptr;
-}; \ No newline at end of file
diff --git a/launcher/ui/setupwizard/SetupWizard.cpp b/launcher/ui/setupwizard/SetupWizard.cpp
index 5af5ba91..22eab60e 100644
--- a/launcher/ui/setupwizard/SetupWizard.cpp
+++ b/launcher/ui/setupwizard/SetupWizard.cpp
@@ -2,12 +2,10 @@
#include "LanguageWizardPage.h"
#include "JavaWizardPage.h"
-#include "AnalyticsWizardPage.h"
#include "translations/TranslationsModel.h"
#include <Application.h>
#include <FileSystem.h>
-#include <ganalytics.h>
#include <QAbstractButton>
#include <BuildConfig.h>
diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp
index cf70c7b4..256b09da 100644
--- a/launcher/ui/widgets/LanguageSelectionWidget.cpp
+++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp
@@ -5,7 +5,9 @@
#include <QHeaderView>
#include <QLabel>
#include "Application.h"
+#include "BuildConfig.h"
#include "translations/TranslationsModel.h"
+#include "settings/Setting.h"
LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
QWidget(parent)
@@ -37,6 +39,9 @@ LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged);
verticalLayout->setContentsMargins(0,0,0,0);
+
+ auto language_setting = APPLICATION->settings()->getSetting("Language");
+ connect(language_setting.get(), &Setting::SettingChanged, this, &LanguageSelectionWidget::languageSettingChanged);
}
QString LanguageSelectionWidget::getSelectedLanguageKey() const
@@ -48,7 +53,7 @@ QString LanguageSelectionWidget::getSelectedLanguageKey() const
void LanguageSelectionWidget::retranslate()
{
QString text = tr("Don't see your language or the quality is poor?<br/><a href=\"%1\">Help us with translations!</a>")
- .arg("https://github.com/MultiMC/Launcher/wiki/Translating-MultiMC");
+ .arg(BuildConfig.TRANSLATIONS_URL);
helpUsLabel->setText(text);
}
@@ -64,3 +69,10 @@ void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, con
translations->selectLanguage(key);
translations->updateLanguage(key);
}
+
+void LanguageSelectionWidget::languageSettingChanged(const Setting &, const QVariant)
+{
+ auto translations = APPLICATION->translations();
+ auto index = translations->selectedIndex();
+ languageView->setCurrentIndex(index);
+}
diff --git a/launcher/ui/widgets/LanguageSelectionWidget.h b/launcher/ui/widgets/LanguageSelectionWidget.h
index e65936db..4a88924c 100644
--- a/launcher/ui/widgets/LanguageSelectionWidget.h
+++ b/launcher/ui/widgets/LanguageSelectionWidget.h
@@ -20,6 +20,7 @@
class QVBoxLayout;
class QTreeView;
class QLabel;
+class Setting;
class LanguageSelectionWidget: public QWidget
{
@@ -33,6 +34,7 @@ public:
protected slots:
void languageRowChanged(const QModelIndex &current, const QModelIndex &previous);
+ void languageSettingChanged(const Setting &, const QVariant);
private:
QVBoxLayout *verticalLayout = nullptr;
diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp
index 74a6dff3..6de49467 100644
--- a/launcher/ui/widgets/PageContainer.cpp
+++ b/launcher/ui/widgets/PageContainer.cpp
@@ -207,7 +207,7 @@ void PageContainer::help()
QString pageId = m_currentPage->helpPage();
if (pageId.isEmpty())
return;
- DesktopServices::openUrl(QUrl("https://github.com/MultiMC/Launcher/wiki/" + pageId));
+ DesktopServices::openUrl(QUrl("https://github.com/PolyMC/PolyMC/wiki/" + pageId));
}
}
diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp
index cbd6c617..8d5bd12d 100644
--- a/launcher/ui/widgets/WideBar.cpp
+++ b/launcher/ui/widgets/WideBar.cpp
@@ -76,6 +76,20 @@ void WideBar::addSeparator()
m_entries.push_back(entry);
}
+void WideBar::insertActionBefore(QAction* before, QAction* action){
+ auto iter = std::find_if(m_entries.begin(), m_entries.end(), [before](BarEntry * entry) {
+ return entry->wideAction == before;
+ });
+ if(iter == m_entries.end()) {
+ return;
+ }
+ auto entry = new BarEntry();
+ entry->qAction = insertWidget((*iter)->qAction, new ActionButton(action, this));
+ entry->wideAction = action;
+ entry->type = BarEntry::Action;
+ m_entries.insert(iter, entry);
+}
+
void WideBar::insertSpacer(QAction* action)
{
auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) {
diff --git a/launcher/ui/widgets/WideBar.h b/launcher/ui/widgets/WideBar.h
index d1b8cbe7..2b676a8c 100644
--- a/launcher/ui/widgets/WideBar.h
+++ b/launcher/ui/widgets/WideBar.h
@@ -18,6 +18,7 @@ public:
void addAction(QAction *action);
void addSeparator();
void insertSpacer(QAction *action);
+ void insertActionBefore(QAction *before, QAction *action);
QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString());
private: