diff options
Diffstat (limited to 'application')
51 files changed, 1101 insertions, 1184 deletions
diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index ce204ae5..53c21866 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -125,20 +125,19 @@ SET(MULTIMC_SOURCES pages/global/ProxyPage.h pages/global/PasteEEPage.cpp pages/global/PasteEEPage.h - pages/global/PackagesPage.cpp - pages/global/PackagesPage.h # GUI - platform pages pages/modplatform/VanillaPage.cpp pages/modplatform/VanillaPage.h - pages/modplatform/FTBPage.cpp - pages/modplatform/FTBPage.h - pages/modplatform/FtbListModel.h - pages/modplatform/FtbListModel.cpp - pages/modplatform/TwitchPage.cpp - pages/modplatform/TwitchPage.h - pages/modplatform/TechnicPage.cpp - pages/modplatform/TechnicPage.h + pages/modplatform/legacy_ftb/Page.cpp + pages/modplatform/legacy_ftb/Page.h + pages/modplatform/legacy_ftb/ListModel.h + pages/modplatform/legacy_ftb/ListModel.cpp + pages/modplatform/twitch/TwitchData.h + pages/modplatform/twitch/TwitchModel.cpp + pages/modplatform/twitch/TwitchModel.h + pages/modplatform/twitch/TwitchPage.cpp + pages/modplatform/twitch/TwitchPage.h pages/modplatform/ImportPage.cpp pages/modplatform/ImportPage.h @@ -182,6 +181,8 @@ SET(MULTIMC_SOURCES widgets/Common.h widgets/CustomCommands.cpp widgets/CustomCommands.h + widgets/DropLabel.cpp + widgets/DropLabel.h widgets/FocusLineEdit.cpp widgets/FocusLineEdit.h widgets/IconLabel.cpp @@ -251,13 +252,11 @@ SET(MULTIMC_UIS pages/global/MultiMCPage.ui pages/global/ProxyPage.ui pages/global/PasteEEPage.ui - pages/global/PackagesPage.ui # Platform pages pages/modplatform/VanillaPage.ui - pages/modplatform/FTBPage.ui - pages/modplatform/TwitchPage.ui - pages/modplatform/TechnicPage.ui + pages/modplatform/legacy_ftb/Page.ui + pages/modplatform/twitch/TwitchPage.ui pages/modplatform/ImportPage.ui # Dialogs @@ -281,7 +280,6 @@ SET(MULTIMC_UIS ) set(MULTIMC_QRCS - resources/assets/assets.qrc resources/backgrounds/backgrounds.qrc resources/multimc/multimc.qrc resources/pe_dark/pe_dark.qrc diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index 5122e240..34ffc54b 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -578,6 +578,7 @@ public: MainWindow->resize(800, 600); MainWindow->setWindowIcon(MMC->getThemedIcon("logo")); MainWindow->setWindowTitle("MultiMC 5"); + MainWindow->setAccessibleName("MultiMC"); createMainToolbar(MainWindow); @@ -1357,7 +1358,7 @@ void MainWindow::on_actionCopyInstance_triggered() if (!copyInstDlg.exec()) return; - auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves()); + auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime()); copyTask->setName(copyInstDlg.instName()); copyTask->setGroup(copyInstDlg.instGroup()); copyTask->setIcon(copyInstDlg.iconKey()); diff --git a/application/MainWindow.h b/application/MainWindow.h index a415b5e8..00b8e043 100644 --- a/application/MainWindow.h +++ b/application/MainWindow.h @@ -57,6 +57,8 @@ public: void checkInstancePathForProblems(); void updatesAllowedChanged(bool allowed); + + void droppedURLs(QList<QUrl> urls); signals: void isClosing(); @@ -180,8 +182,6 @@ private slots: */ void downloadUpdates(GoUpdate::Status status); - void droppedURLs(QList<QUrl> urls); - void konamiTriggered(); void globalSettingsClosed(); diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index c95d85be..a8d26498 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -15,7 +15,6 @@ #include "pages/global/ExternalToolsPage.h" #include "pages/global/AccountListPage.h" #include "pages/global/PasteEEPage.h" -#include "pages/global/PackagesPage.h" #include "pages/global/CustomCommandsPage.h" #include "themes/ITheme.h" @@ -35,6 +34,7 @@ #include <QNetworkAccessManager> #include <QTranslator> #include <QLibraryInfo> +#include <QList> #include <QStringList> #include <QDebug> #include <QStyleFactory> @@ -146,6 +146,27 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) startTime = QDateTime::currentDateTime(); +#ifdef Q_OS_LINUX + { + QFile osrelease("/proc/sys/kernel/osrelease"); + if (osrelease.open(QFile::ReadOnly | QFile::Text)) { + QTextStream in(&osrelease); + auto contents = in.readAll(); + if( + contents.contains("WSL", Qt::CaseInsensitive) || + contents.contains("Microsoft", Qt::CaseInsensitive) + ) { + showFatalErrorMessage( + "Unsupported system detected!", + "Linux-on-Windows distributions are not supported.\n\n" + "Please use the Windows MultiMC binary when playing on Windows." + ); + return; + } + } + } +#endif + // Don't quit on hiding the last window this->setQuitOnLastWindowClosed(false); @@ -174,6 +195,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) // --alive parser.addSwitch("alive"); parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts"); + // --import + parser.addOption("import"); + parser.addShortOpt("import", 'I'); + parser.addDocumentation("import", "Import instance from specified zip (local path or URL)"); // parse the arguments try @@ -208,6 +233,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) } m_instanceIdToLaunch = args["launch"].toString(); m_liveCheck = args["alive"].toBool(); + m_zipToImport = args["import"].toUrl(); QString origcwdPath = QDir::currentPath(); QString binPath = applicationDirPath(); @@ -279,13 +305,20 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived); if(m_peerInstance->isClient()) { + int timeout = 2000; + if(m_instanceIdToLaunch.isEmpty()) { - m_peerInstance->sendMessage("activate", 2000); + m_peerInstance->sendMessage("activate", timeout); + + if(!m_zipToImport.isEmpty()) + { + m_peerInstance->sendMessage("import " + m_zipToImport.toString(), timeout); + } } else { - m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000); + m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout); } m_status = MultiMC::Succeeded; return; @@ -527,7 +560,6 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) m_globalSettingsProvider->addPage<LanguagePage>(); m_globalSettingsProvider->addPage<CustomCommandsPage>(); m_globalSettingsProvider->addPage<ProxyPage>(); - // m_globalSettingsProvider->addPage<PackagesPage>(); m_globalSettingsProvider->addPage<ExternalToolsPage>(); m_globalSettingsProvider->addPage<AccountListPage>(); m_globalSettingsProvider->addPage<PasteEEPage>(); @@ -535,7 +567,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Settings loaded."; } +#ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(groupViewAccessibleFactory); +#endif /* !QT_NO_ACCESSIBILITY */ // load translations { @@ -812,6 +846,11 @@ void MultiMC::performMainStartupAction() showMainWindow(false); qDebug() << "<> Main window shown."; } + if(!m_zipToImport.isEmpty()) + { + qDebug() << "<> Importing instance from zip:" << m_zipToImport; + m_mainWindow->droppedURLs({ m_zipToImport }); + } } void MultiMC::showFatalErrorMessage(const QString& title, const QString& content) @@ -848,18 +887,41 @@ void MultiMC::messageReceived(const QString& message) qDebug() << "Received message" << message << "while still initializing. It will be ignored."; return; } - if(message == "activate") + + QString command = message.section(' ', 0, 0); + + if(command == "activate") { showMainWindow(); } - else + else if(command == "import") + { + QString arg = message.section(' ', 1); + if(arg.isEmpty()) + { + qWarning() << "Received" << command << "message without a zip path/URL."; + return; + } + m_mainWindow->droppedURLs({ QUrl(arg) }); + } + else if(command == "launch") { - auto inst = instances()->getInstanceById(message); + QString arg = message.section(' ', 1); + if(arg.isEmpty()) + { + qWarning() << "Received" << command << "message without an instance ID."; + return; + } + auto inst = instances()->getInstanceById(arg); if(inst) { launch(inst, true, nullptr); } } + else + { + qWarning() << "Received invalid message" << message; + } } void MultiMC::analyticsSettingChanged(const Setting&, QVariant value) diff --git a/application/MultiMC.h b/application/MultiMC.h index d7c727e0..e6588a14 100644 --- a/application/MultiMC.h +++ b/application/MultiMC.h @@ -6,6 +6,7 @@ #include <QFlag> #include <QIcon> #include <QDateTime> +#include <QUrl> #include <updater/GoUpdate.h> #include <BaseInstance.h> @@ -221,5 +222,6 @@ private: public: QString m_instanceIdToLaunch; bool m_liveCheck = false; + QUrl m_zipToImport; std::unique_ptr<QFile> logFile; }; diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp index ca1dfd94..c4e4ee24 100644 --- a/application/dialogs/AboutDialog.cpp +++ b/application/dialogs/AboutDialog.cpp @@ -23,56 +23,44 @@ #include "HoeDown.h" +namespace { // Credits // This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument... -static QString getCreditsHtml(QStringList patrons) +QString getCreditsHtml(QStringList patrons) { - QString creditsHtml = QObject::tr( - "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/REC-html40/strict.dtd'>" - "<html>" - "" - "<head>" - "<meta name='qrichtext' content='1' />" - "<style type='text/css'>" - "p { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }" - "</style>" - "</head>" - "" - "<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>" - "" - "<h3>MultiMC Developers</h3>" - "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>" - "<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>" - "<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>" - "<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>" - "<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>" - "" - "<h3>With thanks to</h3>" - "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>" - "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>" - "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>" - "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>" - "" - "<h3>Patrons</h3>" - "%1" - "" - "</body>" - "</html>"); - if (patrons.isEmpty()) - return creditsHtml.arg(QObject::tr("<p>Loading...</p>")); - else - { - QString patronsStr; + QString patronsHeading = QObject::tr("Patrons", "About Credits"); + QString output; + QTextStream stream(&output); + stream << "<center>\n"; + // TODO: possibly retrieve from git history at build time? + stream << "<h3>" << QObject::tr("MultiMC Developers", "About Credits") << "</h3>\n"; + stream << "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>\n"; + stream << "<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>\n"; + stream << "<p>Sky Welch <<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>></p>\n"; + stream << "<p>Jan (02JanDal) <<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>></p>\n"; + stream << "<p>RoboSky <<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>></p>\n"; + stream << "<br />\n"; + + stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n"; + stream << "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>\n"; + stream << "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>\n"; + stream << "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>\n"; + stream << "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>\n"; + stream << "<p>Zeker Zhayard <<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>></p>\n"; + stream << "<br />\n"; + + if(!patrons.isEmpty()) { + stream << "<h3>" << QObject::tr("Patrons", "About Credits") << "</h3>\n"; for (QString patron : patrons) { - patronsStr.append(QString("<p>%1</p>").arg(patron)); + stream << "<p>" << patron << "</p>\n"; } - - return creditsHtml.arg(patronsStr); } + stream << "</center>\n"; + return output; } -static QString getLicenseHtml() +QString getLicenseHtml() { HoeDown hoedown; QFile dataFile(":/documents/COPYING.md"); @@ -81,6 +69,8 @@ static QString getLicenseHtml() return output; } +} + AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog) { ui->setupUi(this); @@ -109,6 +99,15 @@ 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 "MultiMC" name when redistributing the project. " +"This means people must take the time to go through the source code and remove all references to "MultiMC", 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>" + )); + connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt); diff --git a/application/dialogs/AboutDialog.ui b/application/dialogs/AboutDialog.ui index 47ec0293..c4096b32 100644 --- a/application/dialogs/AboutDialog.ui +++ b/application/dialogs/AboutDialog.ui @@ -217,16 +217,6 @@ </property> </widget> </item> - <item> - <widget class="QLineEdit" name="translationInfo"> - <property name="text"> - <string extracomment="Hey, Translator, feel free to put credit to you here">No Language file loaded.</string> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> </layout> </widget> <widget class="QWidget" name="licenseTab"> @@ -263,18 +253,7 @@ </attribute> <layout class="QVBoxLayout" name="verticalLayout_4"> <item> - <widget class="QTextEdit" name="textEdit"> - <property name="html"> - <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">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.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">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 *MultiMC-fork* in the title).</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;">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 </span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;">without</span><span style=" font-family:'Bitstream Vera Sans'; font-size:11pt;"> implying that you have our blessing.</span></p></body></html></string> - </property> + <widget class="QTextEdit" name="redistributionText"> <property name="textInteractionFlags"> <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> </property> @@ -323,14 +302,11 @@ p, li { white-space: pre-wrap; } <tabstops> <tabstop>tabWidget</tabstop> <tabstop>creditsText</tabstop> - <tabstop>translationInfo</tabstop> <tabstop>licenseText</tabstop> - <tabstop>textEdit</tabstop> + <tabstop>redistributionText</tabstop> <tabstop>aboutQt</tabstop> <tabstop>closeButton</tabstop> </tabstops> - <resources> - <include location="../../resources/multimc/multimc.qrc"/> - </resources> + <resources/> <connections/> </ui> diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp index 6100860c..ab76e737 100644 --- a/application/dialogs/CopyInstanceDialog.cpp +++ b/application/dialogs/CopyInstanceDialog.cpp @@ -53,6 +53,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent) ui->groupBox->setCurrentIndex(index); ui->groupBox->lineEdit()->setPlaceholderText(tr("No group")); ui->copySavesCheckbox->setChecked(m_copySaves); + ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime); } CopyInstanceDialog::~CopyInstanceDialog() @@ -123,3 +124,21 @@ void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state) m_copySaves = true; } } + +bool CopyInstanceDialog::shouldKeepPlaytime() const +{ + return m_keepPlaytime; +} + + +void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state) +{ + if(state == Qt::Unchecked) + { + m_keepPlaytime = false; + } + else if(state == Qt::Checked) + { + m_keepPlaytime = true; + } +} diff --git a/application/dialogs/CopyInstanceDialog.h b/application/dialogs/CopyInstanceDialog.h index d46e647c..2b8475b7 100644 --- a/application/dialogs/CopyInstanceDialog.h +++ b/application/dialogs/CopyInstanceDialog.h @@ -40,16 +40,19 @@ public: QString instGroup() const; QString iconKey() const; bool shouldCopySaves() const; + bool shouldKeepPlaytime() const; private slots: void on_iconButton_clicked(); void on_instNameTextBox_textChanged(const QString &arg1); void on_copySavesCheckbox_stateChanged(int state); + void on_keepPlaytimeCheckbox_stateChanged(int state); private: Ui::CopyInstanceDialog *ui; QString InstIconKey; InstancePtr m_original; bool m_copySaves = true; + bool m_keepPlaytime = true; }; diff --git a/application/dialogs/CopyInstanceDialog.ui b/application/dialogs/CopyInstanceDialog.ui index bbb1bbb3..fa675455 100644 --- a/application/dialogs/CopyInstanceDialog.ui +++ b/application/dialogs/CopyInstanceDialog.ui @@ -10,7 +10,7 @@ <x>0</x> <y>0</y> <width>345</width> - <height>240</height> + <height>323</height> </rect> </property> <property name="windowTitle"> @@ -117,6 +117,13 @@ </widget> </item> <item> + <widget class="QCheckBox" name="keepPlaytimeCheckbox"> + <property name="text"> + <string>Keep play time</string> + </property> + </widget> + </item> + <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -128,6 +135,13 @@ </item> </layout> </widget> + <tabstops> + <tabstop>iconButton</tabstop> + <tabstop>instNameTextBox</tabstop> + <tabstop>groupBox</tabstop> + <tabstop>copySavesCheckbox</tabstop> + <tabstop>keepPlaytimeCheckbox</tabstop> + </tabstops> <resources> <include location="../../graphics.qrc"/> </resources> diff --git a/application/dialogs/EditAccountDialog.ui b/application/dialogs/EditAccountDialog.ui index 85e235ce..e87509bc 100644 --- a/application/dialogs/EditAccountDialog.ui +++ b/application/dialogs/EditAccountDialog.ui @@ -30,7 +30,7 @@ <item> <widget class="QLineEdit" name="userTextBox"> <property name="placeholderText"> - <string>Email / Username</string> + <string>Email</string> </property> </widget> </item> diff --git a/application/dialogs/LoginDialog.ui b/application/dialogs/LoginDialog.ui index 99b98215..d92fbae3 100644 --- a/application/dialogs/LoginDialog.ui +++ b/application/dialogs/LoginDialog.ui @@ -36,7 +36,7 @@ <item> <widget class="QLineEdit" name="userTextBox"> <property name="placeholderText"> - <string>Email / Username</string> + <string>Email</string> </property> </widget> </item> diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp index 3533763d..511f991e 100644 --- a/application/dialogs/NewInstanceDialog.cpp +++ b/application/dialogs/NewInstanceDialog.cpp @@ -34,11 +34,9 @@ #include "widgets/PageContainer.h" #include <pages/modplatform/VanillaPage.h> -#include <pages/modplatform/FTBPage.h> -#include <pages/modplatform/TwitchPage.h> +#include <pages/modplatform/legacy_ftb/Page.h> +#include <pages/modplatform/twitch/TwitchPage.h> #include <pages/modplatform/ImportPage.h> -#include <pages/modplatform/TechnicPage.h> - NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent) @@ -97,14 +95,8 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString if(!url.isEmpty()) { QUrl actualUrl(url); - if(actualUrl.host() == "www.curseforge.com") { - m_container->selectPage("twitch"); - twitchPage->setUrl(url); - } - else { - m_container->selectPage("import"); - importPage->setUrl(url); - } + m_container->selectPage("import"); + importPage->setUrl(url); } updateDialogState(); @@ -133,8 +125,8 @@ QList<BasePage *> NewInstanceDialog::getPages() { new VanillaPage(this), importPage, - twitchPage, - new FTBPage(this) + new LegacyFTB::Page(this), + twitchPage }; } diff --git a/application/groupview/AccessibleGroupView.cpp b/application/groupview/AccessibleGroupView.cpp index 9a1bb821..c6541f18 100644 --- a/application/groupview/AccessibleGroupView.cpp +++ b/application/groupview/AccessibleGroupView.cpp @@ -6,6 +6,8 @@ #include <qaccessible.h> #include <qheaderview.h> +#ifndef QT_NO_ACCESSIBILITY + QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object) { QAccessibleInterface *iface = 0; @@ -772,3 +774,5 @@ QAccessibleInterface *AccessibleGroupViewItem::child(int) const { return 0; } + +#endif /* !QT_NO_ACCESSIBILITY */ diff --git a/application/groupview/AccessibleGroupView_p.h b/application/groupview/AccessibleGroupView_p.h index cdec1c0a..e74da3be 100644 --- a/application/groupview/AccessibleGroupView_p.h +++ b/application/groupview/AccessibleGroupView_p.h @@ -1,10 +1,11 @@ #pragma once -#include "GroupView.h" #include "QtCore/qpointer.h" #include <QtGui/qaccessible.h> #include <QAccessibleWidget> #include <QAbstractItemView> +#ifndef QT_NO_ACCESSIBILITY +#include "GroupView.h" // #include <QHeaderView> class QAccessibleTableCell; @@ -114,3 +115,4 @@ private: friend class AccessibleGroupView; }; +#endif /* !QT_NO_ACCESSIBILITY */ diff --git a/application/groupview/GroupView.cpp b/application/groupview/GroupView.cpp index ff0daee4..a1a28532 100644 --- a/application/groupview/GroupView.cpp +++ b/application/groupview/GroupView.cpp @@ -93,11 +93,13 @@ void GroupView::currentChanged(const QModelIndex& current, const QModelIndex& pr { QAbstractItemView::currentChanged(current, previous); // TODO: for accessibility support, implement+register a factory, steal QAccessibleTable from Qt and return an instance of it for GroupView. +#ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive() && current.isValid()) { QAccessibleEvent event(this, QAccessible::Focus); event.setChild(current.row()); QAccessible::updateAccessibility(&event); } +#endif /* !QT_NO_ACCESSIBILITY */ } @@ -392,6 +394,8 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event) updateGeometries(); viewport()->update(); event->accept(); + m_pressedCategory = nullptr; + setState(NoState); return; } else if (state() == CollapsingState) @@ -402,6 +406,8 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event) updateGeometries(); viewport()->update(); event->accept(); + m_pressedCategory = nullptr; + setState(NoState); return; } } diff --git a/application/main.cpp b/application/main.cpp index c871bf1b..b0360c7e 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -43,7 +43,6 @@ int main(int argc, char *argv[]) { Q_INIT_RESOURCE(multimc); Q_INIT_RESOURCE(backgrounds); - Q_INIT_RESOURCE(assets); Q_INIT_RESOURCE(pe_dark); Q_INIT_RESOURCE(pe_light); diff --git a/application/package/linux/multimc.desktop b/application/package/linux/multimc.desktop index 770f24f1..c25be047 100755 --- a/application/package/linux/multimc.desktop +++ b/application/package/linux/multimc.desktop @@ -1,7 +1,7 @@ [Desktop Entry] Version=1.0 Name=MultiMC -GenericName=MultiMC +GenericName=Minecraft Launcher Comment=Free, open source launcher and instance manager for Minecraft. Type=Application Terminal=false diff --git a/application/package/ubuntu/multimc/DEBIAN/control b/application/package/ubuntu/multimc/DEBIAN/control index 34c223a5..9386e945 100644 --- a/application/package/ubuntu/multimc/DEBIAN/control +++ b/application/package/ubuntu/multimc/DEBIAN/control @@ -1,11 +1,11 @@ Package: multimc -Version: 1.3-1 +Version: 1.4-1 Architecture: all Maintainer: Petr Mrázek <peterix@gmail.com> Section: games Priority: optional Installed-Size: 75 -Depends: zenity, desktop-file-utils, qt5-default +Depends: zenity, desktop-file-utils, qt5-default, wget Recommends: openjdk-8-jre Homepage: http://multimc.org Description: A local install wrapper for MultiMC diff --git a/application/pages/global/AccountListPage.cpp b/application/pages/global/AccountListPage.cpp index c14134f3..0453ae00 100644 --- a/application/pages/global/AccountListPage.cpp +++ b/application/pages/global/AccountListPage.cpp @@ -103,8 +103,7 @@ void AccountListPage::listChanged() void AccountListPage::on_actionAdd_triggered() { - addAccount(tr("Please enter your Mojang or Minecraft account username and password to add " - "your account.")); + addAccount(tr("Please enter your Minecraft account email and password to add your account.")); } void AccountListPage::on_actionRemove_triggered() diff --git a/application/pages/global/PackagesPage.cpp b/application/pages/global/PackagesPage.cpp deleted file mode 100644 index 1bbbed7a..00000000 --- a/application/pages/global/PackagesPage.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* Copyright 2015-2019 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "PackagesPage.h" -#include "ui_PackagesPage.h" - -#include <QDateTime> -#include <QSortFilterProxyModel> -#include <QRegularExpression> - -#include "dialogs/ProgressDialog.h" -#include "VersionProxyModel.h" - -#include "meta/Index.h" -#include "meta/VersionList.h" -#include "meta/Version.h" -#include "Env.h" -#include "MultiMC.h" - -using namespace Meta; - -static QString formatRequires(const VersionPtr &version) -{ - QStringList lines; - auto & reqs = version->requires(); - auto iter = reqs.begin(); - while (iter != reqs.end()) - { - auto &uid = iter->uid; - auto &version = iter->equalsVersion; - const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid; - if(!version.isEmpty()) - { - lines.append(QString("%1 (%2)").arg(readable, version)); - } - else - { - lines.append(QString("%1").arg(readable)); - } - iter++; - } - return lines.join('\n'); -} - -PackagesPage::PackagesPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::PackagesPage) -{ - ui->setupUi(this); - ui->tabWidget->tabBar()->hide(); - - m_fileProxy = new QSortFilterProxyModel(this); - m_fileProxy->setSortRole(Qt::DisplayRole); - m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_fileProxy->setFilterRole(Qt::DisplayRole); - m_fileProxy->setFilterKeyColumn(0); - m_fileProxy->sort(0); - m_fileProxy->setSourceModel(ENV.metadataIndex().get()); - ui->indexView->setModel(m_fileProxy); - - m_filterProxy = new QSortFilterProxyModel(this); - m_filterProxy->setSortRole(VersionList::SortRole); - m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive); - m_filterProxy->setFilterRole(Qt::DisplayRole); - m_filterProxy->setFilterKeyColumn(0); - m_filterProxy->sort(0, Qt::DescendingOrder); - ui->versionsView->setModel(m_filterProxy); - - m_versionProxy = new VersionProxyModel(this); - m_filterProxy->setSourceModel(m_versionProxy); - - connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList); - connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion); - connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged); - - updateCurrentVersionList(QModelIndex()); - updateVersion(); -} - -PackagesPage::~PackagesPage() -{ - delete ui; -} - -QIcon PackagesPage::icon() const -{ - return MMC->getThemedIcon("packages"); -} - -void PackagesPage::on_refreshIndexBtn_clicked() -{ - ENV.metadataIndex()->load(Net::Mode::Online); -} -void PackagesPage::on_refreshFileBtn_clicked() -{ - VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value<VersionListPtr>(); - if (!list) - { - return; - } - list->load(Net::Mode::Online); -} -void PackagesPage::on_refreshVersionBtn_clicked() -{ - VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value<VersionPtr>(); - if (!version) - { - return; - } - version->load(Net::Mode::Online); -} - -void PackagesPage::on_fileSearchEdit_textChanged(const QString &search) -{ - if (search.isEmpty()) - { - m_fileProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } -} -void PackagesPage::on_versionSearchEdit_textChanged(const QString &search) -{ - if (search.isEmpty()) - { - m_filterProxy->setFilterFixedString(QString()); - } - else - { - QStringList parts = search.split(' '); - std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape); - m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*"); - } -} - -void PackagesPage::updateCurrentVersionList(const QModelIndex &index) -{ - if (index.isValid()) - { - VersionListPtr list = index.data(Index::ListPtrRole).value<VersionListPtr>(); - ui->versionsBox->setEnabled(true); - ui->refreshFileBtn->setEnabled(true); - ui->fileUidLabel->setEnabled(true); - ui->fileUid->setText(list->uid()); - ui->fileNameLabel->setEnabled(true); - ui->fileName->setText(list->name()); - m_versionProxy->setSourceModel(list.get()); - ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable())); - list->load(Net::Mode::Offline); - } - else - { - ui->versionsBox->setEnabled(false); - ui->refreshFileBtn->setEnabled(false); - ui->fileUidLabel->setEnabled(false); - ui->fileUid->clear(); - ui->fileNameLabel->setEnabled(false); - ui->fileName->clear(); - m_versionProxy->setSourceModel(nullptr); - ui->refreshFileBtn->setText(tr("Refresh")); - } -} - -void PackagesPage::versionListDataChanged(const QModelIndex &tl, const QModelIndex &br) -{ - if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex())) - { - updateVersion(); - } -} - -void PackagesPage::updateVersion() -{ - VersionPtr version = std::dynamic_pointer_cast<Version>( - ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value<BaseVersionPtr>()); - if (version) - { - ui->refreshVersionBtn->setEnabled(true); - ui->versionVersionLabel->setEnabled(true); - ui->versionVersion->setText(version->version()); - ui->versionTimeLabel->setEnabled(true); - ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm")); - ui->versionTypeLabel->setEnabled(true); - ui->versionType->setText(version->type()); - ui->versionRequiresLabel->setEnabled(true); - ui->versionRequires->setText(formatRequires(version)); - ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version())); - } - else - { - ui->refreshVersionBtn->setEnabled(false); - ui->versionVersionLabel->setEnabled(false); - ui->versionVersion->clear(); - ui->versionTimeLabel->setEnabled(false); - ui->versionTime->clear(); - ui->versionTypeLabel->setEnabled(false); - ui->versionType->clear(); - ui->versionRequiresLabel->setEnabled(false); - ui->versionRequires->clear(); - ui->refreshVersionBtn->setText(tr("Refresh")); - } -} - -void PackagesPage::openedImpl() -{ - ENV.metadataIndex()->load(Net::Mode::Offline); -} diff --git a/application/pages/global/PackagesPage.h b/application/pages/global/PackagesPage.h deleted file mode 100644 index 872961a5..00000000 --- a/application/pages/global/PackagesPage.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright 2015-2019 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QWidget> - -#include "pages/BasePage.h" - -namespace Ui { -class PackagesPage; -} - -class QSortFilterProxyModel; -class VersionProxyModel; - -class PackagesPage : public QWidget, public BasePage -{ - Q_OBJECT -public: - explicit PackagesPage(QWidget *parent = 0); - ~PackagesPage(); - - QString id() const override { return "packages-global"; } - QString displayName() const override { return tr("Packages"); } - QIcon icon() const override; - void openedImpl() override; - -private slots: - void on_refreshIndexBtn_clicked(); - void on_refreshFileBtn_clicked(); - void on_refreshVersionBtn_clicked(); - void on_fileSearchEdit_textChanged(const QString &search); - void on_versionSearchEdit_textChanged(const QString &search); - void updateCurrentVersionList(const QModelIndex &index); - void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br); - -private: - Ui::PackagesPage *ui; - QSortFilterProxyModel *m_fileProxy; - QSortFilterProxyModel *m_filterProxy; - VersionProxyModel *m_versionProxy; - - void updateVersion(); -}; diff --git a/application/pages/global/PackagesPage.ui b/application/pages/global/PackagesPage.ui deleted file mode 100644 index 158bf1b4..00000000 --- a/application/pages/global/PackagesPage.ui +++ /dev/null @@ -1,252 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>PackagesPage</class> - <widget class="QWidget" name="PackagesPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>636</width> - <height>621</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <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>Tab 1</string> - </attribute> - <layout class="QGridLayout" name="gridLayout"> - <item row="1" column="2"> - <widget class="QGroupBox" name="versionsBox"> - <property name="title"> - <string>Versions</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QLineEdit" name="versionSearchEdit"> - <property name="placeholderText"> - <string>Search...</string> - </property> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTreeView" name="versionsView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <attribute name="headerVisible"> - <bool>false</bool> - </attribute> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QPushButton" name="refreshVersionBtn"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QFormLayout" name="formLayout_2"> - <item row="0" column="0"> - <widget class="QLabel" name="versionVersionLabel"> - <property name="text"> - <string>Version:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="versionVersion"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="versionTimeLabel"> - <property name="text"> - <string>Time:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="versionTime"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="2" column="0"> - <widget class="QLabel" name="versionTypeLabel"> - <property name="text"> - <string>Type:</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLabel" name="versionType"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="3" column="0"> - <widget class="QLabel" name="versionRequiresLabel"> - <property name="text"> - <string>Dependencies:</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QLabel" name="versionRequires"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item> - <spacer name="verticalSpacer_2"> - <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> - </item> - <item row="1" column="1"> - <widget class="QGroupBox" name="versionListsBox"> - <property name="title"> - <string>Resources</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QLineEdit" name="fileSearchEdit"> - <property name="placeholderText"> - <string>Search...</string> - </property> - <property name="clearButtonEnabled"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="QTreeView" name="indexView"> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <attribute name="headerVisible"> - <bool>false</bool> - </attribute> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <widget class="QPushButton" name="refreshFileBtn"> - <property name="text"> - <string>Refresh</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QFormLayout" name="formLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="fileUidLabel"> - <property name="text"> - <string>UID:</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <widget class="QLabel" name="fileUid"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item row="1" column="0"> - <widget class="QLabel" name="fileNameLabel"> - <property name="text"> - <string>Name:</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="fileName"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </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> - </item> - <item row="0" column="1" colspan="2"> - <widget class="QPushButton" name="refreshIndexBtn"> - <property name="text"> - <string>Refresh Index</string> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> - </widget> - <resources/> - <connections/> -</ui> diff --git a/application/pages/global/ProxyPage.cpp b/application/pages/global/ProxyPage.cpp index ee56a54e..3f0e766b 100644 --- a/application/pages/global/ProxyPage.cpp +++ b/application/pages/global/ProxyPage.cpp @@ -95,7 +95,7 @@ void ProxyPage::loadSettings() ui->proxyHTTPBtn->setChecked(true); ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString()); - ui->proxyPortEdit->setValue(s->get("ProxyPort").value<qint16>()); + ui->proxyPortEdit->setValue(s->get("ProxyPort").value<uint16_t>()); ui->proxyUserEdit->setText(s->get("ProxyUser").toString()); ui->proxyPassEdit->setText(s->get("ProxyPass").toString()); } diff --git a/application/pages/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp index d449f8bf..ec4bf3c9 100644 --- a/application/pages/instance/ModFolderPage.cpp +++ b/application/pages/instance/ModFolderPage.cpp @@ -51,6 +51,26 @@ public: } protected: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override { + ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel()); + if(!model) { + return false; + } + const auto &mod = model->at(source_row); + if(mod.name().contains(filterRegExp())) { + return true; + } + if(mod.description().contains(filterRegExp())) { + return true; + } + for(auto & author: mod.authors()) { + if (author.contains(filterRegExp())) { + return true; + } + } + return false; + } + bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override { ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel()); diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp index 8ca55934..c7a8dc30 100644 --- a/application/pages/instance/VersionPage.cpp +++ b/application/pages/instance/VersionPage.cpp @@ -43,6 +43,7 @@ #include "icons/IconList.h" #include "Exception.h" #include "Version.h" +#include "DesktopServices.h" #include <meta/Index.h> #include <meta/VersionList.h> @@ -60,7 +61,7 @@ public: virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override { - QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role); + QVariant var = QIdentityProxyModel::data(proxyIndex, role); int column = proxyIndex.column(); if(column == 0 && role == Qt::DecorationRole && m_parentWidget) { @@ -205,7 +206,7 @@ void VersionPage::updateVersionControls() bool newCraft = controlsEnabled && (minecraftVersion >= Version("1.14")); bool oldCraft = controlsEnabled && (minecraftVersion <= Version("1.12.2")); ui->actionInstall_Fabric->setEnabled(newCraft); - ui->actionInstall_Forge->setEnabled(oldCraft); + ui->actionInstall_Forge->setEnabled(true); ui->actionInstall_LiteLoader->setEnabled(oldCraft); ui->actionReload->setEnabled(true); updateButtons(); @@ -507,6 +508,16 @@ void VersionPage::on_actionInstall_LiteLoader_triggered() } } +void VersionPage::on_actionLibrariesFolder_triggered() +{ + DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true); +} + +void VersionPage::on_actionMinecraftFolder_triggered() +{ + DesktopServices::openDirectory(m_inst->gameRoot(), true); +} + void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous) { currentIdx = current.row(); diff --git a/application/pages/instance/VersionPage.h b/application/pages/instance/VersionPage.h index 5ffd32f5..769fe997 100644 --- a/application/pages/instance/VersionPage.h +++ b/application/pages/instance/VersionPage.h @@ -66,6 +66,9 @@ private slots: void on_actionCustomize_triggered(); void on_actionDownload_All_triggered(); + void on_actionMinecraftFolder_triggered(); + void on_actionLibrariesFolder_triggered(); + void updateVersionControls(); private: diff --git a/application/pages/instance/VersionPage.ui b/application/pages/instance/VersionPage.ui index 32111aa5..718ad067 100644 --- a/application/pages/instance/VersionPage.ui +++ b/application/pages/instance/VersionPage.ui @@ -95,6 +95,10 @@ <addaction name="actionAdd_to_Minecraft_jar"/> <addaction name="actionReplace_Minecraft_jar"/> <addaction name="actionAdd_Empty"/> + <addaction name="separator"/> + <addaction name="actionMinecraftFolder"/> + <addaction name="actionLibrariesFolder"/> + <addaction name="separator"/> <addaction name="actionReload"/> <addaction name="actionDownload_All"/> </widget> @@ -223,6 +227,22 @@ <string>Download the files needed to launch the instance now.</string> </property> </action> + <action name="actionMinecraftFolder"> + <property name="text"> + <string>Open .minecraft</string> + </property> + <property name="toolTip"> + <string>Open the instance's .minecraft folder.</string> + </property> + </action> + <action name="actionLibrariesFolder"> + <property name="text"> + <string>Open libraries</string> + </property> + <property name="toolTip"> + <string>Open the instance's local libraries folder.</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/application/pages/modplatform/TechnicPage.cpp b/application/pages/modplatform/TechnicPage.cpp deleted file mode 100644 index 2f95bec8..00000000 --- a/application/pages/modplatform/TechnicPage.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "TechnicPage.h" -#include "ui_TechnicPage.h" - -#include "MultiMC.h" -#include "dialogs/NewInstanceDialog.h" - -TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) -{ - ui->setupUi(this); -} - -TechnicPage::~TechnicPage() -{ - delete ui; -} - -bool TechnicPage::shouldDisplay() const -{ - return true; -} - -void TechnicPage::openedImpl() -{ - dialog->setSuggestedPack(); -} diff --git a/application/pages/modplatform/TechnicPage.h b/application/pages/modplatform/TechnicPage.h deleted file mode 100644 index cc82ddf3..00000000 --- a/application/pages/modplatform/TechnicPage.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright 2013-2019 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QWidget> - -#include "pages/BasePage.h" -#include <MultiMC.h> -#include "tasks/Task.h" - -namespace Ui -{ -class TechnicPage; -} - -class NewInstanceDialog; - -class TechnicPage : public QWidget, public BasePage -{ - Q_OBJECT - -public: - explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0); - virtual ~TechnicPage(); - virtual QString displayName() const override - { - return tr("Technic"); - } - virtual QIcon icon() const override - { - return MMC->getThemedIcon("technic"); - } - virtual QString id() const override - { - return "technic"; - } - virtual QString helpPage() const override - { - return "Technic-platform"; - } - virtual bool shouldDisplay() const override; - - void openedImpl() override; - -private: - Ui::TechnicPage *ui = nullptr; - NewInstanceDialog* dialog = nullptr; -}; diff --git a/application/pages/modplatform/TechnicPage.ui b/application/pages/modplatform/TechnicPage.ui deleted file mode 100644 index 702427b5..00000000 --- a/application/pages/modplatform/TechnicPage.ui +++ /dev/null @@ -1,113 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>TechnicPage</class> - <widget class="QWidget" name="TechnicPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>546</width> - <height>405</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <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> - <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> - <item> - <widget class="QLabel" name="label"> - <property name="font"> - <font> - <pointsize>40</pointsize> - </font> - </property> - <property name="styleSheet"> - <string notr="true">color:#ffc000</string> - </property> - <property name="text"> - <string notr="true">UNDER</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string notr="true"/> - </property> - <property name="pixmap"> - <pixmap resource="../../resources/assets/assets.qrc">:/assets/underconstruction</pixmap> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="label_2"> - <property name="font"> - <font> - <pointsize>40</pointsize> - </font> - </property> - <property name="styleSheet"> - <string notr="true">color:#7ca32b</string> - </property> - <property name="text"> - <string notr="true">CONSTRUCTION</string> - </property> - <property name="textFormat"> - <enum>Qt::PlainText</enum> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item> - <spacer name="verticalSpacer_2"> - <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> - <resources> - <include location="../../resources/assets/assets.qrc"/> - </resources> - <connections/> -</ui> diff --git a/application/pages/modplatform/TwitchPage.cpp b/application/pages/modplatform/TwitchPage.cpp deleted file mode 100644 index ea0f9267..00000000 --- a/application/pages/modplatform/TwitchPage.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "TwitchPage.h" -#include "ui_TwitchPage.h" - -#include "MultiMC.h" -#include "dialogs/NewInstanceDialog.h" -#include <InstanceImportTask.h> - -TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) -{ - ui->setupUi(this); - connect(ui->checkButton, &QPushButton::clicked, this, &TwitchPage::triggerCheck); -} - -TwitchPage::~TwitchPage() -{ - delete ui; -} - -bool TwitchPage::shouldDisplay() const -{ - return true; -} - -void TwitchPage::openedImpl() -{ - dialog->setSuggestedPack(); -} - -void TwitchPage::triggerCheck(bool) -{ - if(m_modIdResolver) { - return; - } - auto task = new Flame::UrlResolvingTask(ui->lineEdit->text()); - connect(task, &Task::finished, this, &TwitchPage::checkDone); - m_modIdResolver.reset(task); - task->start(); -} - -void TwitchPage::setUrl(const QString& url) -{ - ui->lineEdit->setText(url); - triggerCheck(true); -} - -void TwitchPage::checkDone() -{ - auto result = m_modIdResolver->getResults(); - auto formatted = QString("Project %1, File %2").arg(result.projectId).arg(result.fileId); - if(result.resolved && result.type == Flame::File::Type::Modpack) { - ui->twitchLabel->setText(formatted); - QFileInfo fi(result.fileName); - dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(result.url)); - } else { - ui->twitchLabel->setPixmap(QPixmap(QString::fromUtf8(":/assets/deadglitch"))); - dialog->setSuggestedPack(); - } - m_modIdResolver.reset(); -} diff --git a/application/pages/modplatform/TwitchPage.ui b/application/pages/modplatform/TwitchPage.ui deleted file mode 100644 index 0db2484d..00000000 --- a/application/pages/modplatform/TwitchPage.ui +++ /dev/null @@ -1,62 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>TwitchPage</class> - <widget class="QWidget" name="TwitchPage"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>546</width> - <height>405</height> - </rect> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="1"> - <widget class="QLineEdit" name="lineEdit"/> - </item> - <item row="0" column="0"> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Twitch URL:</string> - </property> - </widget> - </item> - <item row="1" column="0" colspan="3"> - <widget class="QLabel" name="twitchLabel"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="font"> - <font> - <pointsize>40</pointsize> - </font> - </property> - <property name="pixmap"> - <pixmap resource="../../resources/assets/assets.qrc">:/assets/deadglitch</pixmap> - </property> - <property name="alignment"> - <set>Qt::AlignCenter</set> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QPushButton" name="checkButton"> - <property name="text"> - <string>Check</string> - </property> - </widget> - </item> - </layout> - </widget> - <tabstops> - <tabstop>lineEdit</tabstop> - <tabstop>checkButton</tabstop> - </tabstops> - <resources> - <include location="../../resources/assets/assets.qrc"/> - </resources> - <connections/> -</ui> diff --git a/application/pages/modplatform/FtbListModel.cpp b/application/pages/modplatform/legacy_ftb/ListModel.cpp index 51aec890..105db25a 100644 --- a/application/pages/modplatform/FtbListModel.cpp +++ b/application/pages/modplatform/legacy_ftb/ListModel.cpp @@ -1,4 +1,4 @@ -#include "FtbListModel.h" +#include "ListModel.h" #include "MultiMC.h" #include <MMCStrings.h> @@ -12,17 +12,19 @@ #include "net/URLConstants.h" -FtbFilterModel::FtbFilterModel(QObject *parent) : QSortFilterProxyModel(parent) +namespace LegacyFTB { + +FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent) { currentSorting = Sorting::ByGameVersion; sortings.insert(tr("Sort by name"), Sorting::ByName); sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion); } -bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - FtbModpack leftPack = sourceModel()->data(left, Qt::UserRole).value<FtbModpack>(); - FtbModpack rightPack = sourceModel()->data(right, Qt::UserRole).value<FtbModpack>(); + Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>(); + Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>(); if(currentSorting == Sorting::ByGameVersion) { Version lv(leftPack.mcVersion); @@ -38,66 +40,66 @@ bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) return true; } -bool FtbFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { return true; } -const QMap<QString, FtbFilterModel::Sorting> FtbFilterModel::getAvailableSortings() +const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings() { return sortings; } -QString FtbFilterModel::translateCurrentSorting() +QString FilterModel::translateCurrentSorting() { return sortings.key(currentSorting); } -void FtbFilterModel::setSorting(Sorting s) +void FilterModel::setSorting(Sorting s) { currentSorting = s; invalidate(); } -FtbFilterModel::Sorting FtbFilterModel::getCurrentSorting() +FilterModel::Sorting FilterModel::getCurrentSorting() { return currentSorting; } -FtbListModel::FtbListModel(QObject *parent) : QAbstractListModel(parent) +ListModel::ListModel(QObject *parent) : QAbstractListModel(parent) { } -FtbListModel::~FtbListModel() +ListModel::~ListModel() { } -QString FtbListModel::translatePackType(FtbPackType type) const +QString ListModel::translatePackType(PackType type) const { switch(type) { - case FtbPackType::Public: + case PackType::Public: return tr("Public Modpack"); - case FtbPackType::ThirdParty: + case PackType::ThirdParty: return tr("Third Party Modpack"); - case FtbPackType::Private: + case PackType::Private: return tr("Private Modpack"); } qWarning() << "Unknown FTB modpack type:" << int(type); return QString(); } -int FtbListModel::rowCount(const QModelIndex &parent) const +int ListModel::rowCount(const QModelIndex &parent) const { return modpacks.size(); } -int FtbListModel::columnCount(const QModelIndex &parent) const +int ListModel::columnCount(const QModelIndex &parent) const { return 1; } -QVariant FtbListModel::data(const QModelIndex &index, int role) const +QVariant ListModel::data(const QModelIndex &index, int role) const { int pos = index.row(); if(pos >= modpacks.size() || pos < 0 || !index.isValid()) @@ -105,7 +107,7 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const return QString("INVALID INDEX %1").arg(pos); } - FtbModpack pack = modpacks.at(pos); + Modpack pack = modpacks.at(pos); if(role == Qt::DisplayRole) { return pack.name + "\n" + translatePackType(pack.type); @@ -129,7 +131,7 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const return (m_logoMap.value(pack.logo)); } QIcon icon = MMC->getThemedIcon("screenshot-placeholder"); - ((FtbListModel *)this)->requestLogo(pack.logo); + ((ListModel *)this)->requestLogo(pack.logo); return icon; } else if(role == Qt::TextColorRole) @@ -156,33 +158,33 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const return QVariant(); } -void FtbListModel::fill(FtbModpackList modpacks) +void ListModel::fill(ModpackList modpacks) { beginResetModel(); this->modpacks = modpacks; endResetModel(); } -void FtbListModel::addPack(FtbModpack modpack) +void ListModel::addPack(Modpack modpack) { beginResetModel(); this->modpacks.append(modpack); endResetModel(); } -void FtbListModel::clear() +void ListModel::clear() { beginResetModel(); modpacks.clear(); endResetModel(); } -FtbModpack FtbListModel::at(int row) +Modpack ListModel::at(int row) { return modpacks.at(row); } -void FtbListModel::remove(int row) +void ListModel::remove(int row) { if(row < 0 || row >= modpacks.size()) { @@ -194,20 +196,20 @@ void FtbListModel::remove(int row) endRemoveRows(); } -void FtbListModel::logoLoaded(QString logo, QIcon out) +void ListModel::logoLoaded(QString logo, QIcon out) { m_loadingLogos.removeAll(logo); m_logoMap.insert(logo, out); emit dataChanged(createIndex(0, 0), createIndex(1, 0)); } -void FtbListModel::logoFailed(QString logo) +void ListModel::logoFailed(QString logo) { m_failedLogos.append(logo); m_loadingLogos.removeAll(logo); } -void FtbListModel::requestLogo(QString file) +void ListModel::requestLogo(QString file) { if(m_loadingLogos.contains(file) || m_failedLogos.contains(file)) { @@ -216,7 +218,7 @@ void FtbListModel::requestLogo(QString file) MetaEntryPtr entry = ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0))); NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file)); - job->addNetAction(Net::Download::makeCached(QUrl(QString(URLConstants::FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); + job->addNetAction(Net::Download::makeCached(QUrl(QString(URLConstants::LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); auto fullPath = entry->getFullPath(); QObject::connect(job, &NetJob::finished, this, [this, file, fullPath] @@ -238,7 +240,7 @@ void FtbListModel::requestLogo(QString file) m_loadingLogos.append(file); } -void FtbListModel::getLogo(const QString &logo, LogoCallback callback) +void ListModel::getLogo(const QString &logo, LogoCallback callback) { if(m_logoMap.contains(logo)) { @@ -250,7 +252,9 @@ void FtbListModel::getLogo(const QString &logo, LogoCallback callback) } } -Qt::ItemFlags FtbListModel::flags(const QModelIndex &index) const +Qt::ItemFlags ListModel::flags(const QModelIndex &index) const { return QAbstractListModel::flags(index); } + +} diff --git a/application/pages/modplatform/FtbListModel.h b/application/pages/modplatform/legacy_ftb/ListModel.h index 34749b24..c55df000 100644 --- a/application/pages/modplatform/FtbListModel.h +++ b/application/pages/modplatform/legacy_ftb/ListModel.h @@ -1,6 +1,6 @@ #pragma once -#include <modplatform/ftb/PackHelpers.h> +#include <modplatform/legacy_ftb/PackHelpers.h> #include <RWStorage.h> #include <QAbstractListModel> @@ -11,14 +11,16 @@ #include <functional> -typedef QMap<QString, QIcon> FtbLogoMap; +namespace LegacyFTB { + +typedef QMap<QString, QIcon> FTBLogoMap; typedef std::function<void(QString)> LogoCallback; -class FtbFilterModel : public QSortFilterProxyModel +class FilterModel : public QSortFilterProxyModel { Q_OBJECT public: - FtbFilterModel(QObject* parent = Q_NULLPTR); + FilterModel(QObject* parent = Q_NULLPTR); enum Sorting { ByName, ByGameVersion @@ -38,18 +40,18 @@ private: }; -class FtbListModel : public QAbstractListModel +class ListModel : public QAbstractListModel { Q_OBJECT private: - FtbModpackList modpacks; + ModpackList modpacks; QStringList m_failedLogos; QStringList m_loadingLogos; - FtbLogoMap m_logoMap; + FTBLogoMap m_logoMap; QMap<QString, LogoCallback> waitingCallbacks; void requestLogo(QString file); - QString translatePackType(FtbPackType type) const; + QString translatePackType(PackType type) const; private slots: @@ -57,18 +59,20 @@ private slots: void logoLoaded(QString logo, QIcon out); public: - FtbListModel(QObject *parent); - ~FtbListModel(); + ListModel(QObject *parent); + ~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; - void fill(FtbModpackList modpacks); - void addPack(FtbModpack modpack); + void fill(ModpackList modpacks); + void addPack(Modpack modpack); void clear(); void remove(int row); - FtbModpack at(int row); + Modpack at(int row); void getLogo(const QString &logo, LogoCallback callback); }; + +} diff --git a/application/pages/modplatform/FTBPage.cpp b/application/pages/modplatform/legacy_ftb/Page.cpp index dca86efd..8e40ba9e 100644 --- a/application/pages/modplatform/FTBPage.cpp +++ b/application/pages/modplatform/legacy_ftb/Page.cpp @@ -1,27 +1,29 @@ -#include "FTBPage.h" -#include "ui_FTBPage.h" +#include "Page.h" +#include "ui_Page.h" #include <QInputDialog> #include "MultiMC.h" #include "dialogs/CustomMessageBox.h" #include "dialogs/NewInstanceDialog.h" -#include "modplatform/ftb/FtbPackFetchTask.h" -#include "modplatform/ftb/FtbPackInstallTask.h" -#include "modplatform/ftb/FtbPrivatePackManager.h" -#include "FtbListModel.h" +#include "modplatform/legacy_ftb/PackFetchTask.h" +#include "modplatform/legacy_ftb/PackInstallTask.h" +#include "modplatform/legacy_ftb/PrivatePackManager.h" +#include "ListModel.h" -FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) - : QWidget(parent), dialog(dialog), ui(new Ui::FTBPage) +namespace LegacyFTB { + +Page::Page(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), dialog(dialog), ui(new Ui::Page) { - ftbFetchTask.reset(new FtbPackFetchTask()); - ftbPrivatePacks.reset(new FtbPrivatePackManager()); + ftbFetchTask.reset(new PackFetchTask()); + ftbPrivatePacks.reset(new PrivatePackManager()); ui->setupUi(this); { - publicFilterModel = new FtbFilterModel(this); - publicListModel = new FtbListModel(this); + publicFilterModel = new FilterModel(this); + publicListModel = new ListModel(this); publicFilterModel->setSourceModel(publicListModel); ui->publicPackList->setModel(publicFilterModel); @@ -39,8 +41,8 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) } { - thirdPartyFilterModel = new FtbFilterModel(this); - thirdPartyModel = new FtbListModel(this); + thirdPartyFilterModel = new FilterModel(this); + thirdPartyModel = new ListModel(this); thirdPartyFilterModel->setSourceModel(thirdPartyModel); ui->thirdPartyPackList->setModel(thirdPartyFilterModel); @@ -53,8 +55,8 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) } { - privateFilterModel = new FtbFilterModel(this); - privateListModel = new FtbListModel(this); + privateFilterModel = new FilterModel(this); + privateListModel = new ListModel(this); privateFilterModel->setSourceModel(privateListModel); ui->privatePackList->setModel(privateFilterModel); @@ -69,17 +71,17 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FTBPage::onSortingSelectionChanged); - connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FTBPage::onVersionSelectionItemChanged); + connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged); - connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPublicPackSelectionChanged); - connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onThirdPartyPackSelectionChanged); - connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPrivatePackSelectionChanged); + connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged); + connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged); + connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged); - connect(ui->addPackBtn, &QPushButton::pressed, this, &FTBPage::onAddPackClicked); - connect(ui->removePackBtn, &QPushButton::pressed, this, &FTBPage::onRemovePackClicked); + connect(ui->addPackBtn, &QPushButton::pressed, this, &Page::onAddPackClicked); + connect(ui->removePackBtn, &QPushButton::pressed, this, &Page::onRemovePackClicked); - connect(ui->tabWidget, &QTabWidget::currentChanged, this, &FTBPage::onTabChanged); + connect(ui->tabWidget, &QTabWidget::currentChanged, this, &Page::onTabChanged); // ui->modpackInfo->setOpenExternalLinks(true); @@ -90,25 +92,25 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent) onTabChanged(ui->tabWidget->currentIndex()); } -FTBPage::~FTBPage() +Page::~Page() { delete ui; } -bool FTBPage::shouldDisplay() const +bool Page::shouldDisplay() const { return true; } -void FTBPage::openedImpl() +void Page::openedImpl() { if(!initialized) { - connect(ftbFetchTask.get(), &FtbPackFetchTask::finished, this, &FTBPage::ftbPackDataDownloadSuccessfully); - connect(ftbFetchTask.get(), &FtbPackFetchTask::failed, this, &FTBPage::ftbPackDataDownloadFailed); + connect(ftbFetchTask.get(), &PackFetchTask::finished, this, &Page::ftbPackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &PackFetchTask::failed, this, &Page::ftbPackDataDownloadFailed); - connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFinished, this, &FTBPage::ftbPrivatePackDataDownloadSuccessfully); - connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFailed, this, &FTBPage::ftbPrivatePackDataDownloadFailed); + connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFinished, this, &Page::ftbPrivatePackDataDownloadSuccessfully); + connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFailed, this, &Page::ftbPrivatePackDataDownloadFailed); ftbFetchTask->fetch(); ftbPrivatePacks->load(); @@ -118,13 +120,13 @@ void FTBPage::openedImpl() suggestCurrent(); } -void FTBPage::suggestCurrent() +void Page::suggestCurrent() { if(isOpened) { if(!selected.broken) { - dialog->setSuggestedPack(selected.name, new FtbPackInstallTask(selected, selectedVersion)); + dialog->setSuggestedPack(selected.name, new PackInstallTask(selected, selectedVersion)); QString editedLogoName; if(selected.logo.toLower().startsWith("ftb")) { @@ -137,21 +139,21 @@ void FTBPage::suggestCurrent() editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png")); - if(selected.type == FtbPackType::Public) + if(selected.type == PackType::Public) { publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); } - else if (selected.type == FtbPackType::ThirdParty) + else if (selected.type == PackType::ThirdParty) { thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); } - else if (selected.type == FtbPackType::Private) + else if (selected.type == PackType::Private) { privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo) { @@ -166,23 +168,23 @@ void FTBPage::suggestCurrent() } } -void FTBPage::ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks) +void Page::ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks) { publicListModel->fill(publicPacks); thirdPartyModel->fill(thirdPartyPacks); } -void FTBPage::ftbPackDataDownloadFailed(QString reason) +void Page::ftbPackDataDownloadFailed(QString reason) { //TODO: Display the error } -void FTBPage::ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack) +void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack) { privateListModel->addPack(pack); } -void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) +void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) { auto reply = QMessageBox::question( this, @@ -195,40 +197,40 @@ void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode) } } -void FTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev) { if(!now.isValid()) { onPackSelectionChanged(); return; } - FtbModpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + Modpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&selectedPack); } -void FTBPage::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev) { if(!now.isValid()) { onPackSelectionChanged(); return; } - FtbModpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + Modpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&selectedPack); } -void FTBPage::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev) +void Page::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev) { if(!now.isValid()) { onPackSelectionChanged(); return; } - FtbModpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<FtbModpack>(); + Modpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&selectedPack); } -void FTBPage::onPackSelectionChanged(FtbModpack* pack) +void Page::onPackSelectionChanged(Modpack* pack) { ui->versionSelectionBox->clear(); if(pack) @@ -266,7 +268,7 @@ void FTBPage::onPackSelectionChanged(FtbModpack* pack) suggestCurrent(); } -void FTBPage::onVersionSelectionItemChanged(QString data) +void Page::onVersionSelectionItemChanged(QString data) { if(data.isNull() || data.isEmpty()) { @@ -278,15 +280,15 @@ void FTBPage::onVersionSelectionItemChanged(QString data) suggestCurrent(); } -void FTBPage::onSortingSelectionChanged(QString data) +void Page::onSortingSelectionChanged(QString data) { - FtbFilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); + FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data); publicFilterModel->setSorting(toSet); thirdPartyFilterModel->setSorting(toSet); privateFilterModel->setSorting(toSet); } -void FTBPage::onTabChanged(int tab) +void Page::onTabChanged(int tab) { if(tab == 1) { @@ -311,7 +313,7 @@ void FTBPage::onTabChanged(int tab) QModelIndex idx = currentList->currentIndex(); if(idx.isValid()) { - auto pack = currentModel->data(idx, Qt::UserRole).value<FtbModpack>(); + auto pack = currentModel->data(idx, Qt::UserRole).value<Modpack>(); onPackSelectionChanged(&pack); } else @@ -320,7 +322,7 @@ void FTBPage::onTabChanged(int tab) } } -void FTBPage::onAddPackClicked() +void Page::onAddPackClicked() { bool ok; QString text = QInputDialog::getText( @@ -338,7 +340,7 @@ void FTBPage::onAddPackClicked() } } -void FTBPage::onRemovePackClicked() +void Page::onRemovePackClicked() { auto index = ui->privatePackList->currentIndex(); if(!index.isValid()) @@ -346,7 +348,7 @@ void FTBPage::onRemovePackClicked() return; } auto row = index.row(); - FtbModpack pack = privateListModel->at(row); + Modpack pack = privateListModel->at(row); auto answer = QMessageBox::question( this, tr("Remove pack"), @@ -362,3 +364,5 @@ void FTBPage::onRemovePackClicked() privateListModel->remove(row); onPackSelectionChanged(); } + +} diff --git a/application/pages/modplatform/FTBPage.h b/application/pages/modplatform/legacy_ftb/Page.h index 215252ee..ed6d1657 100644 --- a/application/pages/modplatform/FTBPage.h +++ b/application/pages/modplatform/legacy_ftb/Page.h @@ -22,29 +22,32 @@ #include "pages/BasePage.h" #include <MultiMC.h> #include "tasks/Task.h" -#include "modplatform/ftb/PackHelpers.h" -#include "modplatform/ftb/FtbPackFetchTask.h" +#include "modplatform/legacy_ftb/PackHelpers.h" +#include "modplatform/legacy_ftb/PackFetchTask.h" #include "QObjectPtr.h" +class NewInstanceDialog; + +namespace LegacyFTB { + namespace Ui { -class FTBPage; +class Page; } -class FtbListModel; -class FtbFilterModel; -class NewInstanceDialog; -class FtbPrivatePackListModel; -class FtbPrivatePackFilterModel; -class FtbPrivatePackManager; +class ListModel; +class FilterModel; +class PrivatePackListModel; +class PrivatePackFilterModel; +class PrivatePackManager; -class FTBPage : public QWidget, public BasePage +class Page : public QWidget, public BasePage { Q_OBJECT public: - explicit FTBPage(NewInstanceDialog * dialog, QWidget *parent = 0); - virtual ~FTBPage(); + explicit Page(NewInstanceDialog * dialog, QWidget *parent = 0); + virtual ~Page(); QString displayName() const override { return tr("FTB Legacy"); @@ -55,7 +58,7 @@ public: } QString id() const override { - return "ftb"; + return "legacy_ftb"; } QString helpPage() const override { @@ -66,13 +69,13 @@ public: private: void suggestCurrent(); - void onPackSelectionChanged(FtbModpack *pack = nullptr); + void onPackSelectionChanged(Modpack *pack = nullptr); private slots: - void ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); + void ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks); void ftbPackDataDownloadFailed(QString reason); - void ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack); + void ftbPrivatePackDataDownloadSuccessfully(Modpack pack); void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); void onSortingSelectionChanged(QString data); @@ -88,27 +91,29 @@ private slots: void onRemovePackClicked(); private: - FtbFilterModel* currentModel = nullptr; + FilterModel* currentModel = nullptr; QTreeView* currentList = nullptr; QTextBrowser* currentModpackInfo = nullptr; bool initialized = false; - FtbModpack selected; + Modpack selected; QString selectedVersion; - FtbListModel* publicListModel = nullptr; - FtbFilterModel* publicFilterModel = nullptr; + ListModel* publicListModel = nullptr; + FilterModel* publicFilterModel = nullptr; - FtbListModel *thirdPartyModel = nullptr; - FtbFilterModel *thirdPartyFilterModel = nullptr; + ListModel *thirdPartyModel = nullptr; + FilterModel *thirdPartyFilterModel = nullptr; - FtbListModel *privateListModel = nullptr; - FtbFilterModel *privateFilterModel = nullptr; + ListModel *privateListModel = nullptr; + FilterModel *privateFilterModel = nullptr; - unique_qobject_ptr<FtbPackFetchTask> ftbFetchTask; - std::unique_ptr<FtbPrivatePackManager> ftbPrivatePacks; + unique_qobject_ptr<PackFetchTask> ftbFetchTask; + std::unique_ptr<PrivatePackManager> ftbPrivatePacks; NewInstanceDialog* dialog = nullptr; - Ui::FTBPage *ui = nullptr; + Ui::Page *ui = nullptr; }; + +} diff --git a/application/pages/modplatform/FTBPage.ui b/application/pages/modplatform/legacy_ftb/Page.ui index e5ed78cb..36fb2359 100644 --- a/application/pages/modplatform/FTBPage.ui +++ b/application/pages/modplatform/legacy_ftb/Page.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> - <class>FTBPage</class> - <widget class="QWidget" name="FTBPage"> + <class>LegacyFTB::Page</class> + <widget class="QWidget" name="LegacyFTB::Page"> <property name="geometry"> <rect> <x>0</x> diff --git a/application/pages/modplatform/twitch/TwitchData.h b/application/pages/modplatform/twitch/TwitchData.h new file mode 100644 index 00000000..dd000b84 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchData.h @@ -0,0 +1,38 @@ +#pragma once + +#include <QString> +#include <QList> + +namespace Twitch { + +struct ModpackAuthor { + QString name; + QString url; +}; + +struct ModpackFile { + int addonId; + int fileId; + QString version; + QString mcVersion; + QString downloadUrl; +}; + +struct Modpack +{ + bool broken = true; + int addonId = 0; + + QString name; + QString description; + QList<ModpackAuthor> authors; + QString mcVersion; + QString logoName; + QString logoUrl; + QString websiteUrl; + + ModpackFile latestFile; +}; +} + +Q_DECLARE_METATYPE(Twitch::Modpack) diff --git a/application/pages/modplatform/twitch/TwitchModel.cpp b/application/pages/modplatform/twitch/TwitchModel.cpp new file mode 100644 index 00000000..d9358941 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchModel.cpp @@ -0,0 +1,314 @@ +#include "TwitchModel.h" +#include "MultiMC.h" + +#include <MMCStrings.h> +#include <Version.h> + +#include <QtMath> +#include <QLabel> + +#include <RWStorage.h> +#include <Env.h> + +#include "net/URLConstants.h" + +namespace Twitch { + +ListModel::ListModel(QObject *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); + } + + Modpack 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 = MMC->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 = ENV.metacache()->resolveEntry("TwitchPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); + NetJob *job = new NetJob(QString("Twitch Icon Download %1").arg(logo)); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath] + { + emit logoLoaded(logo, QIcon(fullPath)); + if(waitingCallbacks.contains(logo)) + { + waitingCallbacks.value(logo)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo] + { + 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(ENV.metacache()->resolveEntry("TwitchPacks", 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(); +} + +void ListModel::performPaginatedSearch() +{ + NetJob *netJob = new NetJob("Twitch::Search"); + auto searchUrl = QString( + "https://addons-ecs.forgesvc.net/api/v2/addon/search?" + "categoryId=0&" + "gameId=432&" + //"gameVersion=1.12.2&" + "index=%1&" + "pageSize=25&" + "searchFilter=%2&" + "sectionId=4471&" + "sort=0" + ).arg(nextSearchOffset).arg(currentSearchTerm); + 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) +{ + if(currentSearchTerm == term) { + return; + } + currentSearchTerm = term; + if(jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } + else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); +} + +void Twitch::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 Twitch at " << parse_error.offset << " reason: " << parse_error.errorString(); + qWarning() << response; + return; + } + + QList<Modpack> newList; + auto objs = doc.array(); + for(auto projectIter: objs) { + Modpack pack; + auto project = projectIter.toObject(); + pack.addonId = project.value("id").toInt(0); + if (pack.addonId == 0) { + qWarning() << "Pack without an ID, skipping: " << pack.name; + continue; + } + pack.name = project.value("name").toString(); + pack.websiteUrl = project.value("websiteUrl").toString(); + pack.description = project.value("summary").toString(); + bool thumbnailFound = false; + auto attachments = project.value("attachments").toArray(); + for(auto attachmentIter: attachments) { + auto attachment = attachmentIter.toObject(); + bool isDefault = attachment.value("isDefault").toBool(false); + if(isDefault) { + thumbnailFound = true; + pack.logoName = attachment.value("title").toString(); + pack.logoUrl = attachment.value("thumbnailUrl").toString(); + break; + } + } + if(!thumbnailFound) { + qWarning() << "Pack without an icon, skipping: " << pack.name; + continue; + } + auto authors = project.value("authors").toArray(); + for(auto authorIter: authors) { + auto author = authorIter.toObject(); + ModpackAuthor packAuthor; + packAuthor.name = author.value("name").toString(); + packAuthor.url = author.value("url").toString(); + pack.authors.append(packAuthor); + } + int defaultFileId = project.value("defaultFileId").toInt(0); + if(defaultFileId == 0) { + qWarning() << "Pack without default file, skipping: " << pack.name; + continue; + } + bool found = false; + auto files = project.value("latestFiles").toArray(); + for(auto fileIter: files) { + auto file = fileIter.toObject(); + int id = file.value("id").toInt(0); + // NOTE: for now, ignore everything that's not the default... + if(id != defaultFileId) { + continue; + } + pack.latestFile.addonId = pack.addonId; + pack.latestFile.fileId = id; + // FIXME: what to do when there's more than one, or there's no version? + auto versionArray = file.value("gameVersion").toArray(); + if(versionArray.size() != 1) { + continue; + } + pack.latestFile.mcVersion = versionArray[0].toString(); + pack.latestFile.version = file.value("displayName").toString(); + pack.latestFile.downloadUrl = file.value("downloadUrl").toString(); + found = true; + break; + } + if(!found) { + qWarning() << "Pack with no good file, skipping: " << pack.name; + continue; + } + pack.broken = false; + newList.append(pack); + } + if(objs.size() < 25) { + searchState = Finished; + } else { + nextSearchOffset += 25; + searchState = CanPossiblyFetchMore; + } + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void Twitch::ListModel::searchRequestFailed(QString reason) +{ + jobPtr.reset(); + + if(searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } +} + +} + diff --git a/application/pages/modplatform/twitch/TwitchModel.h b/application/pages/modplatform/twitch/TwitchModel.h new file mode 100644 index 00000000..ad355c64 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchModel.h @@ -0,0 +1,76 @@ +#pragma once + +#include <modplatform/legacy_ftb/PackHelpers.h> +#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 "TwitchData.h" + +namespace Twitch { + + +typedef QMap<QString, QIcon> LogoMap; +typedef std::function<void(QString)> LogoCallback; + +class ListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + ListModel(QObject *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); + +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<Modpack> modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + LogoMap m_logoMap; + QMap<QString, LogoCallback> waitingCallbacks; + + QString currentSearchTerm; + int nextSearchOffset = 0; + enum SearchState { + None, + CanPossiblyFetchMore, + ResetRequested, + Finished + } searchState = None; + NetJobPtr jobPtr; + QByteArray response; +}; + +} diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp new file mode 100644 index 00000000..1e9f9dbb --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.cpp @@ -0,0 +1,111 @@ +#include "TwitchPage.h" +#include "ui_TwitchPage.h" + +#include "MultiMC.h" +#include "dialogs/NewInstanceDialog.h" +#include <InstanceImportTask.h> +#include "TwitchModel.h" +#include <QKeyEvent> + +TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent) + : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog) +{ + ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &TwitchPage::triggerSearch); + ui->searchEdit->installEventFilter(this); + model = new Twitch::ListModel(this); + ui->packView->setModel(model); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TwitchPage::onSelectionChanged); +} + +TwitchPage::~TwitchPage() +{ + delete ui; +} + +bool TwitchPage::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 TwitchPage::shouldDisplay() const +{ + return true; +} + +void TwitchPage::openedImpl() +{ + suggestCurrent(); +} + +void TwitchPage::triggerSearch() +{ + model->searchWithTerm(ui->searchEdit->text()); +} + +void TwitchPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + if(!first.isValid()) + { + if(isOpened) + { + dialog->setSuggestedPack(); + } + ui->frame->clear(); + return; + } + + current = model->data(first, Qt::UserRole).value<Twitch::Modpack>(); + 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 = [](Twitch::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 += tr(" by ") + authorStrs.join(", "); + } + + ui->frame->setModText(text); + ui->frame->setModDescription(current.description); + suggestCurrent(); +} + +void TwitchPage::suggestCurrent() +{ + if(!isOpened) + { + return; + } + if(current.broken) + { + dialog->setSuggestedPack(); + } + + dialog->setSuggestedPack(current.name, new InstanceImportTask(current.latestFile.downloadUrl)); + QString editedLogoName; + editedLogoName = "twitch_" + current.logoName.section(".", 0, 0); + model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) + { + dialog->setSuggestedIconFromFile(logo, editedLogoName); + }); +} diff --git a/application/pages/modplatform/TwitchPage.h b/application/pages/modplatform/twitch/TwitchPage.h index 600913cd..04e3a1c6 100644 --- a/application/pages/modplatform/TwitchPage.h +++ b/application/pages/modplatform/twitch/TwitchPage.h @@ -20,7 +20,7 @@ #include "pages/BasePage.h" #include <MultiMC.h> #include "tasks/Task.h" -#include "modplatform/flame/UrlResolvingTask.h" +#include "TwitchData.h" namespace Ui { @@ -29,6 +29,10 @@ class TwitchPage; class NewInstanceDialog; +namespace Twitch { + class ListModel; +} + class TwitchPage : public QWidget, public BasePage { Q_OBJECT @@ -38,7 +42,7 @@ public: virtual ~TwitchPage(); virtual QString displayName() const override { - return tr("Twitch URL"); + return tr("Twitch"); } virtual QIcon icon() const override { @@ -56,14 +60,18 @@ public: void openedImpl() override; - void setUrl(const QString & url); + bool eventFilter(QObject * watched, QEvent * event) override; + +private: + void suggestCurrent(); private slots: - void triggerCheck(bool checked); - void checkDone(); + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); private: Ui::TwitchPage *ui = nullptr; NewInstanceDialog* dialog = nullptr; - shared_qobject_ptr<Flame::UrlResolvingTask> m_modIdResolver; + Twitch::ListModel* model = nullptr; + Twitch::Modpack current; }; diff --git a/application/pages/modplatform/twitch/TwitchPage.ui b/application/pages/modplatform/twitch/TwitchPage.ui new file mode 100644 index 00000000..29bdc727 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.ui @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>TwitchPage</class> + <widget class="QWidget" name="TwitchPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>875</width> + <height>745</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLineEdit" name="searchEdit"/> + </item> + <item row="0" column="1"> + <widget class="QPushButton" name="searchButton"> + <property name="text"> + <string>Search</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="2"> + <widget class="QTreeView" name="packView"> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="iconSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="MCModInfoFrame" name="frame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>MCModInfoFrame</class> + <extends>QFrame</extends> + <header>widgets/MCModInfoFrame.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>searchEdit</tabstop> + <tabstop>searchButton</tabstop> + <tabstop>packView</tabstop> + </tabstops> + <resources/> + <connections/> +</ui> diff --git a/application/resources/assets/assets.qrc b/application/resources/assets/assets.qrc deleted file mode 100644 index 38638e7f..00000000 --- a/application/resources/assets/assets.qrc +++ /dev/null @@ -1,7 +0,0 @@ -<!DOCTYPE RCC> -<RCC version="1.0"> - <qresource prefix="/assets"> - <file alias="underconstruction">underconstruction.png</file> - <file alias="deadglitch">deadglitch.svg</file> - </qresource> -</RCC> diff --git a/application/resources/assets/deadglitch.svg b/application/resources/assets/deadglitch.svg deleted file mode 100644 index 5682b8a3..00000000 --- a/application/resources/assets/deadglitch.svg +++ /dev/null @@ -1,66 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - class="tw-svg__asset tw-svg__asset--deadglitch tw-svg__asset--inherit" - width="92px" - height="96px" - version="1.1" - viewBox="0 0 30 30" - x="0px" - y="0px" - id="svg8" - sodipodi:docname="deadglitch.svg" - inkscape:version="0.92.2 2405546, 2018-03-11"> - <metadata - id="metadata14"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <defs - id="defs12" /> - <sodipodi:namedview - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1" - objecttolerance="10" - gridtolerance="10" - guidetolerance="10" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:window-width="1353" - inkscape:window-height="828" - id="namedview10" - showgrid="false" - inkscape:zoom="4.9166667" - inkscape:cx="44.285787" - inkscape:cy="52.833458" - inkscape:window-x="2958" - inkscape:window-y="702" - inkscape:window-maximized="0" - inkscape:current-layer="svg8" /> - <g - id="g6" - style="fill:#898395;fill-opacity:1"> - <path - d="M26,17.4589613 L26,3 L4,3 L4,22.0601057 L10.0032868,22.0601057 L10.0032868,26 L14.0004537,22.0601057 L21.3322933,22.0601057 L26,17.4589613 L26,17.4589613 Z M21.0896458,26.0850335 L15.1583403,26.0850335 L11.2051771,30 L7.24798611,30 L7.24798611,26.0850335 L0,26.0850335 L0,5.21746493 L1.97773958,0 L29,0 L29,18.2620736 L21.0896458,26.0850335 L21.0896458,26.0850335 Z" - id="path2" - style="fill:#898395;fill-opacity:1" /> - <path - d="M20.8587626,12.1710126 L22.4052753,13.7175252 L23.7175252,12.4052753 L22.1710126,10.8587626 L23.7175252,9.31224999 L22.4052753,8 L20.8587626,9.54651264 L19.31225,8 L18,9.31224999 L19.5465126,10.8587626 L18,12.4052753 L19.31225,13.7175252 L20.8587626,12.1710126 Z M11.8587626,12.1710126 L13.4052753,13.7175252 L14.7175252,12.4052753 L13.1710126,10.8587626 L14.7175252,9.31224999 L13.4052753,8 L11.8587626,9.54651264 L10.31225,8 L9,9.31224999 L10.5465126,10.8587626 L9,12.4052753 L10.31225,13.7175252 L11.8587626,12.1710126 Z" - id="path4" - style="fill:#898395;fill-opacity:1" /> - </g> -</svg> diff --git a/application/resources/assets/underconstruction.png b/application/resources/assets/underconstruction.png Binary files differdeleted file mode 100644 index 6ae06476..00000000 --- a/application/resources/assets/underconstruction.png +++ /dev/null diff --git a/application/widgets/DropLabel.cpp b/application/widgets/DropLabel.cpp new file mode 100644 index 00000000..a900e57c --- /dev/null +++ b/application/widgets/DropLabel.cpp @@ -0,0 +1,41 @@ +#include "DropLabel.h" + +#include <QMimeData> +#include <QDropEvent> + +DropLabel::DropLabel(QWidget *parent) : QLabel(parent) +{ + setAcceptDrops(true); +} + +void DropLabel::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); +} + +void DropLabel::dragMoveEvent(QDragMoveEvent *event) +{ + event->acceptProposedAction(); +} + +void DropLabel::dragLeaveEvent(QDragLeaveEvent *event) +{ + event->accept(); +} + +void DropLabel::dropEvent(QDropEvent *event) +{ + const QMimeData *mimeData = event->mimeData(); + + if (!mimeData) + { + return; + } + + if (mimeData->hasUrls()) { + auto urls = mimeData->urls(); + emit droppedURLs(urls); + } + + event->acceptProposedAction(); +} diff --git a/application/widgets/DropLabel.h b/application/widgets/DropLabel.h new file mode 100644 index 00000000..c5ca0bcc --- /dev/null +++ b/application/widgets/DropLabel.h @@ -0,0 +1,20 @@ +#pragma once + +#include <QLabel> + +class DropLabel : public QLabel +{ + Q_OBJECT + +public: + explicit DropLabel(QWidget *parent = nullptr); + +signals: + void droppedURLs(QList<QUrl> urls); + +protected: + void dropEvent(QDropEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; +}; diff --git a/application/widgets/ServerStatus.cpp b/application/widgets/ServerStatus.cpp index a7016c0c..ce0aed9c 100644 --- a/application/widgets/ServerStatus.cpp +++ b/application/widgets/ServerStatus.cpp @@ -125,7 +125,7 @@ void ServerStatus::addStatus(QString key, QString name) void ServerStatus::clicked() { - DesktopServices::openUrl(QUrl("https://help.mojang.com/")); + DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/Mojang-Services-Status")); } void ServerStatus::setStatus(QString key, int value) diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp index 09df75b7..fdcb84e6 100644 --- a/application/widgets/VersionListView.cpp +++ b/application/widgets/VersionListView.cpp @@ -82,7 +82,9 @@ void VersionListView::setEmptyMode(VersionListView::EmptyMode mode) void VersionListView::updateEmptyViewPort() { +#ifndef QT_NO_ACCESSIBILITY setAccessibleDescription(currentEmptyString()); +#endif /* !QT_NO_ACCESSIBILITY */ if(!m_itemCount) { |