diff options
Diffstat (limited to 'launcher/ui/pages/modplatform/atlauncher')
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> |