From 47fa7b3f8c7a92fa14aa50d6bd7d7a3c100a06f3 Mon Sep 17 00:00:00 2001 From: OverMighty Date: Wed, 5 Feb 2020 00:29:23 +0100 Subject: GH-2988 add --import command-line option When specified, opens the "Import from zip" dialog as soon as the main window is shown, with the URL field prefilled with the argument given to the option. Closes #2998 --- application/MainWindow.h | 4 ++-- application/MultiMC.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++----- application/MultiMC.h | 2 ++ 3 files changed, 49 insertions(+), 7 deletions(-) (limited to 'application') 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 urls); signals: void isClosing(); @@ -180,8 +182,6 @@ private slots: */ void downloadUpdates(GoUpdate::Status status); - void droppedURLs(QList urls); - void konamiTriggered(); void globalSettingsClosed(); diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp index c4e4392d..393ea046 100644 --- a/application/MultiMC.cpp +++ b/application/MultiMC.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -173,6 +174,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 @@ -207,6 +212,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(); @@ -278,13 +284,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; @@ -812,6 +825,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 +866,40 @@ void MultiMC::messageReceived(const QString& message) qDebug() << "Received message" << message << "while still initializing. It will be ignored."; return; } - if(message == "activate") + + QStringList args = message.split(' '); + QString command = args.takeFirst(); + + if(command == "activate") { showMainWindow(); } - else + else if(command == "import") + { + if(args.isEmpty()) + { + qWarning() << "Received" << command << "message without a zip path/URL."; + return; + } + m_mainWindow->droppedURLs({ QUrl(args.takeFirst()) }); + } + else if(command == "launch") { - auto inst = instances()->getInstanceById(message); + if(args.isEmpty()) + { + qWarning() << "Received" << command << "message without an instance ID."; + return; + } + auto inst = instances()->getInstanceById(args.takeFirst()); 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 #include #include +#include #include #include @@ -221,5 +222,6 @@ private: public: QString m_instanceIdToLaunch; bool m_liveCheck = false; + QUrl m_zipToImport; std::unique_ptr logFile; }; -- cgit From 95a09ba09948e1d2d1cbda24dcddd1d4da0236d5 Mon Sep 17 00:00:00 2001 From: Brottweiler Date: Fri, 31 Jan 2020 13:17:22 +0100 Subject: GH-2979 Tweak GenericName --- application/package/linux/multimc.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application') 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 -- cgit From c9e851f12f501657629e41339ad604c3cfba82e1 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sat, 28 Mar 2020 15:55:12 +0100 Subject: GH-2544 enable Forge install button for >= 1.13 --- application/pages/instance/VersionPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application') diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp index 20298117..60ff8301 100644 --- a/application/pages/instance/VersionPage.cpp +++ b/application/pages/instance/VersionPage.cpp @@ -206,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(); -- cgit From 3ad9ea507e945709b01a5fcc51a60f7c45ad4c38 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 29 Mar 2020 03:12:57 +0200 Subject: NOISSUE update version number, changelog and credits in about dialog --- CMakeLists.txt | 2 +- application/dialogs/AboutDialog.cpp | 1 + changelog.md | 21 +++++++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) (limited to 'application') diff --git a/CMakeLists.txt b/CMakeLists.txt index ede71307..c4a7e52e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,7 @@ set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetc ######## Set version numbers ######## set(MultiMC_VERSION_MAJOR 0) set(MultiMC_VERSION_MINOR 6) -set(MultiMC_VERSION_HOTFIX 10) +set(MultiMC_VERSION_HOTFIX 11) # Build number set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp index a556ec05..c4e4ee24 100644 --- a/application/dialogs/AboutDialog.cpp +++ b/application/dialogs/AboutDialog.cpp @@ -46,6 +46,7 @@ QString getCreditsHtml(QStringList patrons) stream << "

TakSuyu <taksuyu@gmail.com>

\n"; stream << "

Kilobyte <stiepen22@gmx.de>

\n"; stream << "

Rootbear75 <@rootbear75>

\n"; + stream << "

Zeker Zhayard <@Zeker_Zhayard>

\n"; stream << "
\n"; if(!patrons.isEmpty()) { diff --git a/changelog.md b/changelog.md index ef7c8dc6..e315950c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,22 @@ -# MultiMC 0.6.8 +# MultiMC 0.6.11 + +This adds Forge 1.13+ support using [ForgeWrapper](https://github.com/ZekerZhayard/ForgeWrapper) by ZekerZhayard. + +### New or changed features + +- GH-2988: You can now import instances and curse modpacks from the command line with the `--import` option followed by either an URL or a local file path. + +- GH-2544: MultiMC now supports downloading library files without including them on the Java classpath. + + This is done by adding them to the `mavenFiles` list instead of the `libraries` list. + + Such downloads are not deduplicated or version upgraded like libraries are. + + This enables ForgeWrapper to work - MultiMC downloads all the files, and ForgeWrapper runs the Forge installer during instance start when needed. + +# Previous releases + +## MultiMC 0.6.8 This is mostly about removal of the 'curse URL' related features, because they were of low quality and generally unreliable. @@ -44,7 +62,6 @@ MultiMC also migrated to a new continuous deployment system, which makes everyth - When a component is customized, the launcher will not try to update it in an infinite loop when something else requires a different version. -# Previous releases ## MultiMC 0.6.7 -- cgit From 3ff93a42161a5fe9301db1054dcb62c7d79ac77d Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 31 Mar 2020 03:13:19 +0200 Subject: NOISSUE Bare-bones twitch pack browser --- api/logic/Env.cpp | 1 + application/CMakeLists.txt | 8 +- application/dialogs/NewInstanceDialog.cpp | 3 + application/dialogs/NewInstanceDialog.h | 2 + application/pages/modplatform/twitch/TwitchData.h | 38 ++++ .../pages/modplatform/twitch/TwitchModel.cpp | 253 +++++++++++++++++++++ application/pages/modplatform/twitch/TwitchModel.h | 65 ++++++ .../pages/modplatform/twitch/TwitchPage.cpp | 86 +++++++ application/pages/modplatform/twitch/TwitchPage.h | 77 +++++++ application/pages/modplatform/twitch/TwitchPage.ui | 63 +++++ 10 files changed, 595 insertions(+), 1 deletion(-) create mode 100644 application/pages/modplatform/twitch/TwitchData.h create mode 100644 application/pages/modplatform/twitch/TwitchModel.cpp create mode 100644 application/pages/modplatform/twitch/TwitchModel.h create mode 100644 application/pages/modplatform/twitch/TwitchPage.cpp create mode 100644 application/pages/modplatform/twitch/TwitchPage.h create mode 100644 application/pages/modplatform/twitch/TwitchPage.ui (limited to 'application') diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp index 77546bbc..0d496d4e 100644 --- a/api/logic/Env.cpp +++ b/api/logic/Env.cpp @@ -97,6 +97,7 @@ void Env::initHttpMetaCache() m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); m_metacache->addBase("general", QDir("cache").absolutePath()); m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); + m_metacache->addBase("TwitchPacks", QDir("cache/TwitchPacks").absolutePath()); m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("translations", QDir("translations").absolutePath()); diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 94ba56d0..99ec8b8f 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -133,6 +133,11 @@ SET(MULTIMC_SOURCES 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 @@ -251,7 +256,8 @@ SET(MULTIMC_UIS # Platform pages pages/modplatform/VanillaPage.ui pages/modplatform/legacy_ftb/Page.ui - pages/modplatform/ImportPage.ui + pages/modplatform/twitch/TwitchPage.ui + pages/modplatform/twitch/ImportPage.ui # Dialogs dialogs/CopyInstanceDialog.ui diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp index 804340bc..511f991e 100644 --- a/application/dialogs/NewInstanceDialog.cpp +++ b/application/dialogs/NewInstanceDialog.cpp @@ -35,6 +35,7 @@ #include "widgets/PageContainer.h" #include #include +#include #include @@ -119,11 +120,13 @@ void NewInstanceDialog::accept() QList NewInstanceDialog::getPages() { importPage = new ImportPage(this); + twitchPage = new TwitchPage(this); return { new VanillaPage(this), importPage, new LegacyFTB::Page(this), + twitchPage }; } diff --git a/application/dialogs/NewInstanceDialog.h b/application/dialogs/NewInstanceDialog.h index c86ab73f..0b8b2fb8 100644 --- a/application/dialogs/NewInstanceDialog.h +++ b/application/dialogs/NewInstanceDialog.h @@ -29,6 +29,7 @@ class NewInstanceDialog; class PageContainer; class QDialogButtonBox; class ImportPage; +class TwitchPage; class NewInstanceDialog : public QDialog, public BasePageProvider { @@ -67,6 +68,7 @@ private: QString InstIconKey; ImportPage *importPage = nullptr; + TwitchPage *twitchPage = nullptr; std::unique_ptr creationTask; bool importIcon = false; 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 +#include + +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 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..210341dc --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchModel.cpp @@ -0,0 +1,253 @@ +#include "TwitchModel.h" +#include "MultiMC.h" + +#include +#include + +#include +#include + +#include +#include + +#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("
")).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); +} + +void ListModel::searchWithTerm(const QString& term) +{ + if(currentSearchTerm == term) { + return; + } + 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=0&" + "pageSize=25&" + "searchFilter=%1&" + "sectionId=4471&" + "sort=0" + ).arg(term); + 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 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 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) { + continue; + } + pack.name = project.value("name").toString(); + pack.websiteUrl = project.value("websiteUrl").toString(); + pack.description = project.value("summary").toString(); + auto attachments = project.value("attachments").toArray(); + for(auto attachmentIter: attachments) { + auto attachment = attachmentIter.toObject(); + bool isDefault = attachment.value("isDefault").toBool(false); + if(!isDefault) { + continue; + } + pack.logoName = attachment.value("title").toString(); + pack.logoUrl = attachment.value("thumbnailUrl").toString(); + } + 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) { + 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) { + return; + } + pack.broken = false; + newList.append(pack); + } + beginResetModel(); + newList.swap(modpacks); + endResetModel(); +} + +void Twitch::ListModel::searchRequestFailed(QString reason) +{ + jobPtr.reset(); +} + +} diff --git a/application/pages/modplatform/twitch/TwitchModel.h b/application/pages/modplatform/twitch/TwitchModel.h new file mode 100644 index 00000000..1241a079 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchModel.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "TwitchData.h" + +namespace Twitch { + + +typedef QMap LogoMap; +typedef std::function 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; + + void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + void searchWithTerm(const QString & term); + +private slots: + 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 modpacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + LogoMap m_logoMap; + QMap waitingCallbacks; + + QString currentSearchTerm; + 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..80d83133 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.cpp @@ -0,0 +1,86 @@ +#include "TwitchPage.h" +#include "ui_TwitchPage.h" + +#include "MultiMC.h" +#include "dialogs/NewInstanceDialog.h" +#include +#include "TwitchModel.h" +#include + +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(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(); + } + return; + } + current = model->data(first, Qt::UserRole).value(); + 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/twitch/TwitchPage.h b/application/pages/modplatform/twitch/TwitchPage.h new file mode 100644 index 00000000..04e3a1c6 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.h @@ -0,0 +1,77 @@ +/* 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 + +#include "pages/BasePage.h" +#include +#include "tasks/Task.h" +#include "TwitchData.h" + +namespace Ui +{ +class TwitchPage; +} + +class NewInstanceDialog; + +namespace Twitch { + class ListModel; +} + +class TwitchPage : public QWidget, public BasePage +{ + Q_OBJECT + +public: + explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0); + virtual ~TwitchPage(); + virtual QString displayName() const override + { + return tr("Twitch"); + } + virtual QIcon icon() const override + { + return MMC->getThemedIcon("twitch"); + } + virtual QString id() const override + { + return "twitch"; + } + virtual QString helpPage() const override + { + return "Twitch-platform"; + } + virtual bool shouldDisplay() const override; + + void openedImpl() override; + + bool eventFilter(QObject * watched, QEvent * event) override; + +private: + void suggestCurrent(); + +private slots: + void triggerSearch(); + void onSelectionChanged(QModelIndex first, QModelIndex second); + +private: + Ui::TwitchPage *ui = nullptr; + NewInstanceDialog* dialog = nullptr; + 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..7a8203b1 --- /dev/null +++ b/application/pages/modplatform/twitch/TwitchPage.ui @@ -0,0 +1,63 @@ + + + TwitchPage + + + + 0 + 0 + 875 + 745 + + + + + + + + + + Search + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + false + + + true + + + false + + + true + + + false + + + + + + + searchEdit + searchButton + packView + + + -- cgit From 5e951299b2071377bc83dc7d2bf9bd7aa9d887e5 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Wed, 1 Apr 2020 00:59:18 +0200 Subject: NOISSUE fix build --- application/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'application') diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt index 99ec8b8f..53c21866 100644 --- a/application/CMakeLists.txt +++ b/application/CMakeLists.txt @@ -257,7 +257,7 @@ SET(MULTIMC_UIS pages/modplatform/VanillaPage.ui pages/modplatform/legacy_ftb/Page.ui pages/modplatform/twitch/TwitchPage.ui - pages/modplatform/twitch/ImportPage.ui + pages/modplatform/ImportPage.ui # Dialogs dialogs/CopyInstanceDialog.ui -- cgit From 296ff6de96f8228ae6de8967d6e34436001b3d00 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Wed, 1 Apr 2020 02:08:11 +0200 Subject: NOISSUE Add pagination support to twitch pack search Try searching for 'craft'. Now it gives ALL the results, not just the first page of 25. --- .../pages/modplatform/twitch/TwitchModel.cpp | 87 ++++++++++++++++++---- application/pages/modplatform/twitch/TwitchModel.h | 11 +++ 2 files changed, 85 insertions(+), 13 deletions(-) (limited to 'application') diff --git a/application/pages/modplatform/twitch/TwitchModel.cpp b/application/pages/modplatform/twitch/TwitchModel.cpp index 210341dc..d9358941 100644 --- a/application/pages/modplatform/twitch/TwitchModel.cpp +++ b/application/pages/modplatform/twitch/TwitchModel.cpp @@ -142,23 +142,36 @@ Qt::ItemFlags ListModel::flags(const QModelIndex &index) const return QAbstractListModel::flags(index); } -void ListModel::searchWithTerm(const QString& term) +bool ListModel::canFetchMore(const QModelIndex& parent) const { - if(currentSearchTerm == term) { + 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=0&" + "index=%1&" "pageSize=25&" - "searchFilter=%1&" + "searchFilter=%2&" "sectionId=4471&" "sort=0" - ).arg(term); + ).arg(nextSearchOffset).arg(currentSearchTerm); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); jobPtr = netJob; jobPtr->start(); @@ -166,6 +179,27 @@ void ListModel::searchWithTerm(const QString& term) 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(); @@ -185,20 +219,27 @@ void Twitch::ListModel::searchRequestFinished() 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) { - continue; + if(isDefault) { + thumbnailFound = true; + pack.logoName = attachment.value("title").toString(); + pack.logoUrl = attachment.value("thumbnailUrl").toString(); + break; } - pack.logoName = attachment.value("title").toString(); - pack.logoUrl = attachment.value("thumbnailUrl").toString(); + } + if(!thumbnailFound) { + qWarning() << "Pack without an icon, skipping: " << pack.name; + continue; } auto authors = project.value("authors").toArray(); for(auto authorIter: authors) { @@ -210,6 +251,7 @@ void Twitch::ListModel::searchRequestFinished() } int defaultFileId = project.value("defaultFileId").toInt(0); if(defaultFileId == 0) { + qWarning() << "Pack without default file, skipping: " << pack.name; continue; } bool found = false; @@ -235,19 +277,38 @@ void Twitch::ListModel::searchRequestFinished() break; } if(!found) { - return; + qWarning() << "Pack with no good file, skipping: " << pack.name; + continue; } pack.broken = false; newList.append(pack); } - beginResetModel(); - newList.swap(modpacks); - endResetModel(); + 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 index 1241a079..ad355c64 100644 --- a/application/pages/modplatform/twitch/TwitchModel.h +++ b/application/pages/modplatform/twitch/TwitchModel.h @@ -36,11 +36,15 @@ public: 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); @@ -58,6 +62,13 @@ private: QMap waitingCallbacks; QString currentSearchTerm; + int nextSearchOffset = 0; + enum SearchState { + None, + CanPossiblyFetchMore, + ResetRequested, + Finished + } searchState = None; NetJobPtr jobPtr; QByteArray response; }; -- cgit From 5ca5661c23050d738f1d5f9ced5e7fb71eef3fce Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Wed, 29 Apr 2020 21:17:51 +0200 Subject: NOISSUE expose twitch pack url, description and author list --- .../pages/modplatform/twitch/TwitchPage.cpp | 25 ++++++++++++++++++++++ application/pages/modplatform/twitch/TwitchPage.ui | 25 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) (limited to 'application') diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp index 80d83133..1e9f9dbb 100644 --- a/application/pages/modplatform/twitch/TwitchPage.cpp +++ b/application/pages/modplatform/twitch/TwitchPage.cpp @@ -59,9 +59,34 @@ void TwitchPage::onSelectionChanged(QModelIndex first, QModelIndex second) { dialog->setSuggestedPack(); } + ui->frame->clear(); return; } + current = model->data(first, Qt::UserRole).value(); + QString text = ""; + QString name = current.name; + + if (current.websiteUrl.isEmpty()) + text = name; + else + text = "" + name + ""; + if (!current.authors.empty()) { + auto authorToStr = [](Twitch::ModpackAuthor & author) { + if(author.url.isEmpty()) { + return author.name; + } + return QString("%2").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(); } diff --git a/application/pages/modplatform/twitch/TwitchPage.ui b/application/pages/modplatform/twitch/TwitchPage.ui index 7a8203b1..29bdc727 100644 --- a/application/pages/modplatform/twitch/TwitchPage.ui +++ b/application/pages/modplatform/twitch/TwitchPage.ui @@ -52,12 +52,37 @@ + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + MCModInfoFrame + QFrame +
widgets/MCModInfoFrame.h
+ 1 +
+
searchEdit searchButton packView + -- cgit