aboutsummaryrefslogtreecommitdiff
path: root/launcher/ui/pages/modplatform/atlauncher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/ui/pages/modplatform/atlauncher')
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp81
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.h34
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp193
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.h52
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp209
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h66
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui65
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp171
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.h86
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.ui92
10 files changed, 1049 insertions, 0 deletions
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
new file mode 100644
index 00000000..b5d8f22b
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
@@ -0,0 +1,81 @@
+#include "AtlFilterModel.h"
+
+#include <QDebug>
+
+#include <modplatform/atlauncher/ATLPackIndex.h>
+#include <Version.h>
+#include <MMCStrings.h>
+
+namespace Atl {
+
+FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
+{
+ currentSorting = Sorting::ByPopularity;
+ sortings.insert(tr("Sort by popularity"), Sorting::ByPopularity);
+ sortings.insert(tr("Sort by name"), Sorting::ByName);
+ sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion);
+
+ searchTerm = "";
+}
+
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
+{
+ return sortings;
+}
+
+QString FilterModel::translateCurrentSorting()
+{
+ return sortings.key(currentSorting);
+}
+
+void FilterModel::setSorting(Sorting sorting)
+{
+ currentSorting = sorting;
+ invalidate();
+}
+
+FilterModel::Sorting FilterModel::getCurrentSorting()
+{
+ return currentSorting;
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
+}
+
+bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ ATLauncher::IndexedPack leftPack = sourceModel()->data(left, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ ATLauncher::IndexedPack rightPack = sourceModel()->data(right, Qt::UserRole).value<ATLauncher::IndexedPack>();
+
+ if (currentSorting == ByPopularity) {
+ return leftPack.position > rightPack.position;
+ }
+ else if (currentSorting == ByGameVersion) {
+ Version lv(leftPack.versions.at(0).minecraft);
+ Version rv(rightPack.versions.at(0).minecraft);
+ return lv < rv;
+ }
+ else if (currentSorting == ByName) {
+ return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ }
+
+ // Invalid sorting set, somehow...
+ qWarning() << "Invalid sorting set!";
+ return true;
+}
+
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.h b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.h
new file mode 100644
index 00000000..bd72ad91
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <QtCore/QSortFilterProxyModel>
+
+namespace Atl {
+
+class FilterModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+public:
+ FilterModel(QObject* parent = Q_NULLPTR);
+ enum Sorting {
+ ByPopularity,
+ ByGameVersion,
+ ByName,
+ };
+ const QMap<QString, Sorting> getAvailableSortings();
+ QString translateCurrentSorting();
+ void setSorting(Sorting sorting);
+ Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
+
+protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+ bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
+
+private:
+ QMap<QString, Sorting> sortings;
+ Sorting currentSorting;
+ QString searchTerm;
+
+};
+
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
new file mode 100644
index 00000000..e8c6deee
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -0,0 +1,193 @@
+#include "AtlListModel.h"
+
+#include <BuildConfig.h>
+#include <Application.h>
+#include <Json.h>
+
+namespace Atl {
+
+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);
+ }
+
+ ATLauncher::IndexedPack pack = modpacks.at(pos);
+ if(role == Qt::DisplayRole)
+ {
+ return pack.name;
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ return pack.name;
+ }
+ else if(role == Qt::DecorationRole)
+ {
+ if(m_logoMap.contains(pack.safeName))
+ {
+ return (m_logoMap.value(pack.safeName));
+ }
+ auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
+
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
+ ((ListModel *)this)->requestLogo(pack.safeName, url);
+
+ return icon;
+ }
+ else if(role == Qt::UserRole)
+ {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+
+ return QVariant();
+}
+
+void ListModel::request()
+{
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+
+ auto *netJob = new NetJob("Atl::Request");
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
+ jobPtr = netJob;
+ jobPtr->start(APPLICATION->network());
+
+ QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed);
+}
+
+void ListModel::requestFinished()
+{
+ 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 ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ QList<ATLauncher::IndexedPack> newList;
+
+ auto packs = doc.array();
+ for(auto packRaw : packs) {
+ auto packObj = packRaw.toObject();
+
+ ATLauncher::IndexedPack pack;
+
+ try {
+ ATLauncher::loadIndexedPack(pack, packObj);
+ }
+ catch (const JSONValidationError &e) {
+ qDebug() << QString::fromUtf8(response);
+ qWarning() << "Error while reading pack manifest from ATLauncher: " << e.cause();
+ return;
+ }
+
+ // ignore packs without a published version
+ if(pack.versions.length() == 0) continue;
+ // only display public packs (for now)
+ if(pack.type != ATLauncher::PackType::Public) continue;
+ // ignore "system" packs (Vanilla, Vanilla with Forge, etc)
+ if(pack.system) continue;
+
+ newList.append(pack);
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
+ modpacks.append(newList);
+ endInsertRows();
+}
+
+void ListModel::requestFailed(QString reason)
+{
+ jobPtr.reset();
+}
+
+void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
+{
+ if(m_logoMap.contains(logo))
+ {
+ callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ }
+ else
+ {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+void ListModel::logoFailed(QString logo)
+{
+ m_failedLogos.append(logo);
+ m_loadingLogos.removeAll(logo);
+}
+
+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].safeName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ }
+ }
+}
+
+void ListModel::requestLogo(QString file, QString url)
+{
+ if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
+ {
+ return;
+ }
+
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
+ NetJob *job = new NetJob(QString("ATLauncher Icon Download %1").arg(file));
+ job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+
+ auto fullPath = entry->getFullPath();
+ QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath]
+ {
+ emit logoLoaded(file, QIcon(fullPath));
+ if(waitingCallbacks.contains(file))
+ {
+ waitingCallbacks.value(file)(fullPath);
+ }
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, file]
+ {
+ emit logoFailed(file);
+ });
+
+ job->start(APPLICATION->network());
+
+ m_loadingLogos.append(file);
+}
+
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h
new file mode 100644
index 00000000..79aa8180
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <QAbstractListModel>
+
+#include "net/NetJob.h"
+#include <QIcon>
+#include <modplatform/atlauncher/ATLPackIndex.h>
+
+namespace Atl {
+
+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;
+
+ void request();
+
+ void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+
+private slots:
+ void requestFinished();
+ void requestFailed(QString reason);
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, QIcon out);
+
+private:
+ void requestLogo(QString file, QString url);
+
+private:
+ QList<ATLauncher::IndexedPack> modpacks;
+
+ QStringList m_failedLogos;
+ QStringList m_loadingLogos;
+ LogoMap m_logoMap;
+ QMap<QString, LogoCallback> waitingCallbacks;
+
+ NetJob::Ptr jobPtr;
+ QByteArray response;
+};
+
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
new file mode 100644
index 00000000..14bbd18b
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -0,0 +1,209 @@
+#include "AtlOptionalModDialog.h"
+#include "ui_AtlOptionalModDialog.h"
+
+AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
+ : QAbstractListModel(parent), m_mods(mods) {
+
+ // fill mod index
+ for (int i = 0; i < m_mods.size(); i++) {
+ auto mod = m_mods.at(i);
+ m_index[mod.name] = i;
+ }
+ // set initial state
+ for (int i = 0; i < m_mods.size(); i++) {
+ auto mod = m_mods.at(i);
+ m_selection[mod.name] = false;
+ setMod(mod, i, mod.selected, false);
+ }
+}
+
+QVector<QString> AtlOptionalModListModel::getResult() {
+ QVector<QString> result;
+
+ for (const auto& mod : m_mods) {
+ if (m_selection[mod.name]) {
+ result.push_back(mod.name);
+ }
+ }
+
+ return result;
+}
+
+int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const {
+ return m_mods.size();
+}
+
+int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const {
+ // Enabled, Name, Description
+ return 3;
+}
+
+QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const {
+ auto row = index.row();
+ auto mod = m_mods.at(row);
+
+ if (role == Qt::DisplayRole) {
+ if (index.column() == NameColumn) {
+ return mod.name;
+ }
+ if (index.column() == DescriptionColumn) {
+ return mod.description;
+ }
+ }
+ else if (role == Qt::ToolTipRole) {
+ if (index.column() == DescriptionColumn) {
+ return mod.description;
+ }
+ }
+ else if (role == Qt::CheckStateRole) {
+ if (index.column() == EnabledColumn) {
+ return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked;
+ }
+ }
+
+ return QVariant();
+}
+
+bool AtlOptionalModListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
+ if (role == Qt::CheckStateRole) {
+ auto row = index.row();
+ auto mod = m_mods.at(row);
+
+ toggleMod(mod, row);
+ return true;
+ }
+
+ return false;
+}
+
+QVariant AtlOptionalModListModel::headerData(int section, Qt::Orientation orientation, int role) const {
+ if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
+ switch (section) {
+ case EnabledColumn:
+ return QString();
+ case NameColumn:
+ return QString("Name");
+ case DescriptionColumn:
+ return QString("Description");
+ }
+ }
+
+ return QVariant();
+}
+
+Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
+ auto flags = QAbstractListModel::flags(index);
+ if (index.isValid() && index.column() == EnabledColumn) {
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ return flags;
+}
+
+void AtlOptionalModListModel::selectRecommended() {
+ for (const auto& mod : m_mods) {
+ m_selection[mod.name] = mod.recommended;
+ }
+
+ emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn),
+ AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
+}
+
+void AtlOptionalModListModel::clearAll() {
+ for (const auto& mod : m_mods) {
+ m_selection[mod.name] = false;
+ }
+
+ emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn),
+ AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
+}
+
+void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) {
+ setMod(mod, index, !m_selection[mod.name]);
+}
+
+void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) {
+ if (m_selection[mod.name] == enable) return;
+
+ m_selection[mod.name] = enable;
+
+ // disable other mods in the group, if applicable
+ if (enable && !mod.group.isEmpty()) {
+ for (int i = 0; i < m_mods.size(); i++) {
+ if (index == i) continue;
+ auto other = m_mods.at(i);
+
+ if (mod.group == other.group) {
+ setMod(other, i, false, shouldEmit);
+ }
+ }
+ }
+
+ for (const auto& dependencyName : mod.depends) {
+ auto dependencyIndex = m_index[dependencyName];
+ auto dependencyMod = m_mods.at(dependencyIndex);
+
+ // enable/disable dependencies
+ if (enable) {
+ setMod(dependencyMod, dependencyIndex, true, shouldEmit);
+ }
+
+ // if the dependency is 'effectively hidden', then track which mods
+ // depend on it - so we can efficiently disable it when no more dependents
+ // depend on it.
+ auto dependants = m_dependants[dependencyName];
+
+ if (enable) {
+ dependants.append(mod.name);
+ }
+ else {
+ dependants.removeAll(mod.name);
+
+ // if there are no longer any dependents, let's disable the mod
+ if (dependencyMod.effectively_hidden && dependants.isEmpty()) {
+ setMod(dependencyMod, dependencyIndex, false, shouldEmit);
+ }
+ }
+ }
+
+ // disable mods that depend on this one, if disabling
+ if (!enable) {
+ auto dependants = m_dependants[mod.name];
+ for (const auto& dependencyName : dependants) {
+ auto dependencyIndex = m_index[dependencyName];
+ auto dependencyMod = m_mods.at(dependencyIndex);
+
+ setMod(dependencyMod, dependencyIndex, false, shouldEmit);
+ }
+ }
+
+ if (shouldEmit) {
+ emit dataChanged(AtlOptionalModListModel::index(index, EnabledColumn),
+ AtlOptionalModListModel::index(index, EnabledColumn));
+ }
+}
+
+
+AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
+ : QDialog(parent), ui(new Ui::AtlOptionalModDialog) {
+ ui->setupUi(this);
+
+ listModel = new AtlOptionalModListModel(this, mods);
+ ui->treeView->setModel(listModel);
+
+ ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ ui->treeView->header()->setSectionResizeMode(
+ AtlOptionalModListModel::NameColumn, QHeaderView::ResizeToContents);
+ ui->treeView->header()->setSectionResizeMode(
+ AtlOptionalModListModel::DescriptionColumn, QHeaderView::Stretch);
+
+ connect(ui->selectRecommendedButton, &QPushButton::pressed,
+ listModel, &AtlOptionalModListModel::selectRecommended);
+ connect(ui->clearAllButton, &QPushButton::pressed,
+ listModel, &AtlOptionalModListModel::clearAll);
+ connect(ui->installButton, &QPushButton::pressed,
+ this, &QDialog::close);
+}
+
+AtlOptionalModDialog::~AtlOptionalModDialog() {
+ delete ui;
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
new file mode 100644
index 00000000..a1df43f6
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <QDialog>
+#include <QAbstractListModel>
+
+#include "modplatform/atlauncher/ATLPackIndex.h"
+
+namespace Ui {
+class AtlOptionalModDialog;
+}
+
+class AtlOptionalModListModel : public QAbstractListModel {
+ Q_OBJECT
+
+public:
+ enum Columns
+ {
+ EnabledColumn = 0,
+ NameColumn,
+ DescriptionColumn,
+ };
+
+ AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
+
+ QVector<QString> getResult();
+
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+public slots:
+ void selectRecommended();
+ void clearAll();
+
+private:
+ void toggleMod(ATLauncher::VersionMod mod, int index);
+ void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
+
+private:
+ QVector<ATLauncher::VersionMod> m_mods;
+ QMap<QString, bool> m_selection;
+ QMap<QString, int> m_index;
+ QMap<QString, QVector<QString>> m_dependants;
+};
+
+class AtlOptionalModDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
+ ~AtlOptionalModDialog() override;
+
+ QVector<QString> getResult() {
+ return listModel->getResult();
+ }
+
+private:
+ Ui::AtlOptionalModDialog *ui;
+
+ AtlOptionalModListModel *listModel;
+};
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
new file mode 100644
index 00000000..4c5c2ec5
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AtlOptionalModDialog</class>
+ <widget class="QDialog" name="AtlOptionalModDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>550</width>
+ <height>310</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Select Mods To Install</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="3">
+ <widget class="QPushButton" name="installButton">
+ <property name="text">
+ <string>Install</string>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="selectRecommendedButton">
+ <property name="text">
+ <string>Select Recommended</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QPushButton" name="shareCodeButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use Share Code</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="clearAllButton">
+ <property name="text">
+ <string>Clear All</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="4">
+ <widget class="ModListView" name="treeView"/>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ModListView</class>
+ <extends>QTreeView</extends>
+ <header>ui/widgets/ModListView.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
new file mode 100644
index 00000000..5f6a1396
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -0,0 +1,171 @@
+#include "AtlPage.h"
+#include "ui_AtlPage.h"
+
+#include "modplatform/atlauncher/ATLPackInstallTask.h"
+
+#include "AtlOptionalModDialog.h"
+#include "ui/dialogs/NewInstanceDialog.h"
+#include "ui/dialogs/VersionSelectDialog.h"
+
+#include <BuildConfig.h>
+
+AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent)
+ : QWidget(parent), ui(new Ui::AtlPage), dialog(dialog)
+{
+ ui->setupUi(this);
+
+ filterModel = new Atl::FilterModel(this);
+ listModel = new Atl::ListModel(this);
+ filterModel->setSourceModel(listModel);
+ ui->packView->setModel(filterModel);
+ ui->packView->setSortingEnabled(true);
+
+ ui->packView->header()->hide();
+ ui->packView->setIndentation(0);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ for(int i = 0; i < filterModel->getAvailableSortings().size(); i++)
+ {
+ ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i));
+ }
+ ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting());
+
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &AtlPage::triggerSearch);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
+}
+
+AtlPage::~AtlPage()
+{
+ delete ui;
+}
+
+bool AtlPage::shouldDisplay() const
+{
+ return true;
+}
+
+void AtlPage::openedImpl()
+{
+ if(!initialized)
+ {
+ listModel->request();
+ initialized = true;
+ }
+
+ suggestCurrent();
+}
+
+void AtlPage::suggestCurrent()
+{
+ if(!isOpened)
+ {
+ return;
+ }
+
+ if (selectedVersion.isEmpty())
+ {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion));
+ auto editedLogoName = selected.safeName;
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
+ listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo)
+ {
+ dialog->setSuggestedIconFromFile(logo, editedLogoName);
+ });
+}
+
+void AtlPage::triggerSearch()
+{
+ filterModel->setSearchTerm(ui->searchEdit->text());
+}
+
+void AtlPage::onSortingSelectionChanged(QString data)
+{
+ auto toSet = filterModel->getAvailableSortings().value(data);
+ filterModel->setSorting(toSet);
+}
+
+void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ ui->versionSelectionBox->clear();
+
+ if(!first.isValid())
+ {
+ if(isOpened)
+ {
+ dialog->setSuggestedPack();
+ }
+ return;
+ }
+
+ selected = filterModel->data(first, Qt::UserRole).value<ATLauncher::IndexedPack>();
+
+ ui->packDescription->setHtml(selected.description.replace("\n", "<br>"));
+
+ for(const auto& version : selected.versions) {
+ ui->versionSelectionBox->addItem(version.version);
+ }
+
+ suggestCurrent();
+}
+
+void AtlPage::onVersionSelectionChanged(QString data)
+{
+ if(data.isNull() || data.isEmpty())
+ {
+ selectedVersion = "";
+ return;
+ }
+
+ selectedVersion = data;
+ suggestCurrent();
+}
+
+QVector<QString> AtlPage::chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) {
+ AtlOptionalModDialog optionalModDialog(this, mods);
+ optionalModDialog.exec();
+ return optionalModDialog.getResult();
+}
+
+QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) {
+ VersionSelectDialog vselect(vlist.get(), "Choose Version", APPLICATION->activeWindow(), false);
+ if (minecraftVersion != Q_NULLPTR) {
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+ vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
+ }
+ else {
+ vselect.setEmptyString(tr("No versions are currently available"));
+ }
+ vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!"));
+
+ // select recommended build
+ for (int i = 0; i < vlist->versions().size(); i++) {
+ auto version = vlist->versions().at(i);
+ auto reqs = version->requires();
+
+ // filter by minecraft version, if the loader depends on a certain version.
+ if (minecraftVersion != Q_NULLPTR) {
+ auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) {
+ return req.uid == "net.minecraft";
+ });
+ if (iter == reqs.end()) continue;
+ if (iter->equalsVersion != minecraftVersion) continue;
+ }
+
+ // first recommended build we find, we use.
+ if (version->isRecommended()) {
+ vselect.setCurrentVersion(version->descriptor());
+ break;
+ }
+ }
+
+ vselect.exec();
+ return vselect.selectedVersion()->descriptor();
+}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
new file mode 100644
index 00000000..b95b3d9e
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h
@@ -0,0 +1,86 @@
+/* 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 "AtlFilterModel.h"
+#include "AtlListModel.h"
+
+#include <QWidget>
+#include <modplatform/atlauncher/ATLPackInstallTask.h>
+
+#include "Application.h"
+#include "ui/pages/BasePage.h"
+#include "tasks/Task.h"
+
+namespace Ui
+{
+ class AtlPage;
+}
+
+class NewInstanceDialog;
+
+class AtlPage : public QWidget, public BasePage, public ATLauncher::UserInteractionSupport
+{
+Q_OBJECT
+
+public:
+ explicit AtlPage(NewInstanceDialog* dialog, QWidget *parent = 0);
+ virtual ~AtlPage();
+ virtual QString displayName() const override
+ {
+ return tr("ATLauncher");
+ }
+ virtual QIcon icon() const override
+ {
+ return APPLICATION->getThemedIcon("atlauncher");
+ }
+ virtual QString id() const override
+ {
+ return "atl";
+ }
+ virtual QString helpPage() const override
+ {
+ return "ATL-platform";
+ }
+ virtual bool shouldDisplay() const override;
+
+ void openedImpl() override;
+
+private:
+ void suggestCurrent();
+
+ QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
+ QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) override;
+
+private slots:
+ void triggerSearch();
+
+ void onSortingSelectionChanged(QString data);
+
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+
+private:
+ Ui::AtlPage *ui = nullptr;
+ NewInstanceDialog* dialog = nullptr;
+ Atl::ListModel* listModel = nullptr;
+ Atl::FilterModel* filterModel = nullptr;
+
+ ATLauncher::IndexedPack selected;
+ QString selectedVersion;
+
+ bool initialized = false;
+};
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
new file mode 100644
index 00000000..9085766a
--- /dev/null
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AtlPage</class>
+ <widget class="QWidget" name="AtlPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0">
+ <widget class="QTreeView" name="packView">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>96</width>
+ <height>48</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>