NOISSUE continue reshuffling the codebase
This commit is contained in:
132
launcher/ui/pages/modplatform/ImportPage.cpp
Normal file
132
launcher/ui/pages/modplatform/ImportPage.cpp
Normal file
@ -0,0 +1,132 @@
|
||||
#include "ImportPage.h"
|
||||
#include "ui_ImportPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QValidator>
|
||||
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
|
||||
#include "InstanceImportTask.h"
|
||||
|
||||
|
||||
class UrlValidator : public QValidator
|
||||
{
|
||||
public:
|
||||
using QValidator::QValidator;
|
||||
|
||||
State validate(QString &in, int &pos) const
|
||||
{
|
||||
const QUrl url(in);
|
||||
if (url.isValid() && !url.isRelative() && !url.isEmpty())
|
||||
{
|
||||
return Acceptable;
|
||||
}
|
||||
else if (QFile::exists(in))
|
||||
{
|
||||
return Acceptable;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Intermediate;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ImportPage::ImportPage(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::ImportPage), dialog(dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->modpackEdit->setValidator(new UrlValidator(ui->modpackEdit));
|
||||
connect(ui->modpackEdit, &QLineEdit::textChanged, this, &ImportPage::updateState);
|
||||
}
|
||||
|
||||
ImportPage::~ImportPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool ImportPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImportPage::openedImpl()
|
||||
{
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ImportPage::updateState()
|
||||
{
|
||||
if(!isOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(ui->modpackEdit->hasAcceptableInput())
|
||||
{
|
||||
QString input = ui->modpackEdit->text();
|
||||
auto url = QUrl::fromUserInput(input);
|
||||
if(url.isLocalFile())
|
||||
{
|
||||
// FIXME: actually do some validation of what's inside here... this is fake AF
|
||||
QFileInfo fi(input);
|
||||
if(fi.exists() && fi.suffix() == "zip")
|
||||
{
|
||||
QFileInfo fi(url.fileName());
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
|
||||
dialog->setSuggestedIcon("default");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(input.endsWith("?client=y")) {
|
||||
input.chop(9);
|
||||
input.append("/file");
|
||||
url = QUrl::fromUserInput(input);
|
||||
}
|
||||
// hook, line and sinker.
|
||||
QFileInfo fi(url.fileName());
|
||||
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
|
||||
dialog->setSuggestedIcon("default");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
}
|
||||
|
||||
void ImportPage::setUrl(const QString& url)
|
||||
{
|
||||
ui->modpackEdit->setText(url);
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ImportPage::on_modpackBtn_clicked()
|
||||
{
|
||||
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)"));
|
||||
if (url.isValid())
|
||||
{
|
||||
if (url.isLocalFile())
|
||||
{
|
||||
ui->modpackEdit->setText(url.toLocalFile());
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->modpackEdit->setText(url.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QUrl ImportPage::modpackUrl() const
|
||||
{
|
||||
const QUrl url(ui->modpackEdit->text());
|
||||
if (url.isValid() && !url.isRelative() && !url.host().isEmpty())
|
||||
{
|
||||
return url;
|
||||
}
|
||||
else
|
||||
{
|
||||
return QUrl::fromLocalFile(ui->modpackEdit->text());
|
||||
}
|
||||
}
|
70
launcher/ui/pages/modplatform/ImportPage.h
Normal file
70
launcher/ui/pages/modplatform/ImportPage.h
Normal file
@ -0,0 +1,70 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ImportPage;
|
||||
}
|
||||
|
||||
class NewInstanceDialog;
|
||||
|
||||
class ImportPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ImportPage(NewInstanceDialog* dialog, QWidget *parent = 0);
|
||||
virtual ~ImportPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Import from zip");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("viewfolder");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "import";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Zip-import";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
void setUrl(const QString & url);
|
||||
void openedImpl() override;
|
||||
|
||||
private slots:
|
||||
void on_modpackBtn_clicked();
|
||||
void updateState();
|
||||
|
||||
private:
|
||||
QUrl modpackUrl() const;
|
||||
|
||||
private:
|
||||
Ui::ImportPage *ui = nullptr;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
};
|
||||
|
52
launcher/ui/pages/modplatform/ImportPage.ui
Normal file
52
launcher/ui/pages/modplatform/ImportPage.ui
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ImportPage</class>
|
||||
<widget class="QWidget" name="ImportPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>546</width>
|
||||
<height>405</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="modpackBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="modpackEdit">
|
||||
<property name="placeholderText">
|
||||
<string notr="true">http://</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="modpackLabel">
|
||||
<property name="text">
|
||||
<string>Local file or link to a direct download:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
103
launcher/ui/pages/modplatform/VanillaPage.cpp
Normal file
103
launcher/ui/pages/modplatform/VanillaPage.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include "VanillaPage.h"
|
||||
#include "ui_VanillaPage.h"
|
||||
|
||||
#include <QTabBar>
|
||||
|
||||
#include "Application.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include "Filter.h"
|
||||
#include "InstanceCreationTask.h"
|
||||
|
||||
VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent)
|
||||
: QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
connect(ui->versionList, &VersionSelectWidget::selectedVersionChanged, this, &VanillaPage::setSelectedVersion);
|
||||
filterChanged();
|
||||
connect(ui->alphaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->betaFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
|
||||
connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh);
|
||||
}
|
||||
|
||||
void VanillaPage::openedImpl()
|
||||
{
|
||||
if(!initialized)
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("net.minecraft");
|
||||
ui->versionList->initialize(vlist.get());
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
suggestCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
void VanillaPage::refresh()
|
||||
{
|
||||
ui->versionList->loadList();
|
||||
}
|
||||
|
||||
void VanillaPage::filterChanged()
|
||||
{
|
||||
QStringList out;
|
||||
if(ui->alphaFilter->isChecked())
|
||||
out << "(old_alpha)";
|
||||
if(ui->betaFilter->isChecked())
|
||||
out << "(old_beta)";
|
||||
if(ui->snapshotFilter->isChecked())
|
||||
out << "(snapshot)";
|
||||
if(ui->oldSnapshotFilter->isChecked())
|
||||
out << "(old_snapshot)";
|
||||
if(ui->releaseFilter->isChecked())
|
||||
out << "(release)";
|
||||
if(ui->experimentsFilter->isChecked())
|
||||
out << "(experiment)";
|
||||
auto regexp = out.join('|');
|
||||
ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false));
|
||||
}
|
||||
|
||||
VanillaPage::~VanillaPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool VanillaPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
BaseVersionPtr VanillaPage::selectedVersion() const
|
||||
{
|
||||
return m_selectedVersion;
|
||||
}
|
||||
|
||||
void VanillaPage::suggestCurrent()
|
||||
{
|
||||
if (!isOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!m_selectedVersion)
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
return;
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion));
|
||||
dialog->setSuggestedIcon("default");
|
||||
}
|
||||
|
||||
void VanillaPage::setSelectedVersion(BaseVersionPtr version)
|
||||
{
|
||||
m_selectedVersion = version;
|
||||
suggestCurrent();
|
||||
}
|
75
launcher/ui/pages/modplatform/VanillaPage.h
Normal file
75
launcher/ui/pages/modplatform/VanillaPage.h
Normal file
@ -0,0 +1,75 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class VanillaPage;
|
||||
}
|
||||
|
||||
class NewInstanceDialog;
|
||||
|
||||
class VanillaPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VanillaPage(NewInstanceDialog *dialog, QWidget *parent = 0);
|
||||
virtual ~VanillaPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Vanilla");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("minecraft");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "vanilla";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Vanilla-platform";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
void openedImpl() override;
|
||||
|
||||
BaseVersionPtr selectedVersion() const;
|
||||
|
||||
public slots:
|
||||
void setSelectedVersion(BaseVersionPtr version);
|
||||
|
||||
private slots:
|
||||
void filterChanged();
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
void suggestCurrent();
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NewInstanceDialog *dialog = nullptr;
|
||||
Ui::VanillaPage *ui = nullptr;
|
||||
bool m_versionSetByUser = false;
|
||||
BaseVersionPtr m_selectedVersion;
|
||||
};
|
169
launcher/ui/pages/modplatform/VanillaPage.ui
Normal file
169
launcher/ui/pages/modplatform/VanillaPage.ui
Normal file
@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>VanillaPage</class>
|
||||
<widget class="QWidget" name="VanillaPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>815</width>
|
||||
<height>607</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string notr="true"/>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Filter</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="releaseFilter">
|
||||
<property name="text">
|
||||
<string>Releases</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="snapshotFilter">
|
||||
<property name="text">
|
||||
<string>Snapshots</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="oldSnapshotFilter">
|
||||
<property name="text">
|
||||
<string>Old Snapshots</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="betaFilter">
|
||||
<property name="text">
|
||||
<string>Betas</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alphaFilter">
|
||||
<property name="text">
|
||||
<string>Alphas</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="experimentsFilter">
|
||||
<property name="text">
|
||||
<string>Experiments</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshBtn">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="VersionSelectWidget" name="versionList" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>VersionSelectWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>ui/widgets/VersionSelectWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>releaseFilter</tabstop>
|
||||
<tabstop>snapshotFilter</tabstop>
|
||||
<tabstop>oldSnapshotFilter</tabstop>
|
||||
<tabstop>betaFilter</tabstop>
|
||||
<tabstop>alphaFilter</tabstop>
|
||||
<tabstop>experimentsFilter</tabstop>
|
||||
<tabstop>refreshBtn</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
81
launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
Normal file
81
launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
34
launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.h
Normal file
34
launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.h
Normal file
@ -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;
|
||||
|
||||
};
|
||||
|
||||
}
|
193
launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
Normal file
193
launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
52
launcher/ui/pages/modplatform/atlauncher/AtlListModel.h
Normal file
52
launcher/ui/pages/modplatform/atlauncher/AtlListModel.h
Normal file
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
};
|
@ -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>
|
171
launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
Normal file
171
launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
Normal file
@ -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();
|
||||
}
|
86
launcher/ui/pages/modplatform/atlauncher/AtlPage.h
Normal file
86
launcher/ui/pages/modplatform/atlauncher/AtlPage.h
Normal file
@ -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;
|
||||
};
|
92
launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
Normal file
92
launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
Normal file
@ -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>
|
258
launcher/ui/pages/modplatform/flame/FlameModel.cpp
Normal file
258
launcher/ui/pages/modplatform/flame/FlameModel.cpp
Normal file
@ -0,0 +1,258 @@
|
||||
#include "FlameModel.h"
|
||||
#include "Application.h"
|
||||
#include <Json.h>
|
||||
|
||||
#include <MMCStrings.h>
|
||||
#include <Version.h>
|
||||
|
||||
#include <QtMath>
|
||||
#include <QLabel>
|
||||
|
||||
#include <RWStorage.h>
|
||||
|
||||
namespace Flame {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
IndexedPack pack = modpacks.at(pos);
|
||||
if(role == Qt::DisplayRole)
|
||||
{
|
||||
return pack.name;
|
||||
}
|
||||
else if (role == Qt::ToolTipRole)
|
||||
{
|
||||
if(pack.description.length() > 100)
|
||||
{
|
||||
//some magic to prevent to long tooltips and replace html linebreaks
|
||||
QString edit = pack.description.left(97);
|
||||
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
|
||||
return edit;
|
||||
|
||||
}
|
||||
return pack.description;
|
||||
}
|
||||
else if(role == Qt::DecorationRole)
|
||||
{
|
||||
if(m_logoMap.contains(pack.logoName))
|
||||
{
|
||||
return (m_logoMap.value(pack.logoName));
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl);
|
||||
return icon;
|
||||
}
|
||||
else if(role == Qt::UserRole)
|
||||
{
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void ListModel::logoLoaded(QString logo, QIcon out)
|
||||
{
|
||||
m_loadingLogos.removeAll(logo);
|
||||
m_logoMap.insert(logo, out);
|
||||
for(int i = 0; i < modpacks.size(); i++) {
|
||||
if(modpacks[i].logoName == logo) {
|
||||
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::logoFailed(QString logo)
|
||||
{
|
||||
m_failedLogos.append(logo);
|
||||
m_loadingLogos.removeAll(logo);
|
||||
}
|
||||
|
||||
void ListModel::requestLogo(QString logo, QString url)
|
||||
{
|
||||
if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
|
||||
NetJob *job = new NetJob(QString("Flame Icon Download %1").arg(logo));
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, 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(APPLICATION->network());
|
||||
|
||||
m_loadingLogos.append(logo);
|
||||
}
|
||||
|
||||
void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
|
||||
{
|
||||
if(m_logoMap.contains(logo))
|
||||
{
|
||||
callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
return QAbstractListModel::flags(index);
|
||||
}
|
||||
|
||||
bool ListModel::canFetchMore(const QModelIndex& parent) const
|
||||
{
|
||||
return searchState == CanPossiblyFetchMore;
|
||||
}
|
||||
|
||||
void ListModel::fetchMore(const QModelIndex& parent)
|
||||
{
|
||||
if (parent.isValid())
|
||||
return;
|
||||
if(nextSearchOffset == 0) {
|
||||
qWarning() << "fetchMore with 0 offset is wrong...";
|
||||
return;
|
||||
}
|
||||
performPaginatedSearch();
|
||||
}
|
||||
|
||||
void ListModel::performPaginatedSearch()
|
||||
{
|
||||
NetJob *netJob = new NetJob("Flame::Search");
|
||||
auto searchUrl = QString(
|
||||
"https://addons-ecs.forgesvc.net/api/v2/addon/search?"
|
||||
"categoryId=0&"
|
||||
"gameId=432&"
|
||||
"index=%1&"
|
||||
"pageSize=25&"
|
||||
"searchFilter=%2&"
|
||||
"sectionId=4471&"
|
||||
"sort=%3"
|
||||
).arg(nextSearchOffset).arg(currentSearchTerm).arg(currentSort);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start(APPLICATION->network());
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
|
||||
}
|
||||
|
||||
void ListModel::searchWithTerm(const QString& term, int sort)
|
||||
{
|
||||
if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) {
|
||||
return;
|
||||
}
|
||||
currentSearchTerm = term;
|
||||
currentSort = sort;
|
||||
if(jobPtr) {
|
||||
jobPtr->abort();
|
||||
searchState = ResetRequested;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
}
|
||||
nextSearchOffset = 0;
|
||||
performPaginatedSearch();
|
||||
}
|
||||
|
||||
void Flame::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 CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Flame::IndexedPack> newList;
|
||||
auto packs = doc.array();
|
||||
for(auto packRaw : packs) {
|
||||
auto packObj = packRaw.toObject();
|
||||
|
||||
Flame::IndexedPack pack;
|
||||
try
|
||||
{
|
||||
Flame::loadIndexedPack(pack, packObj);
|
||||
newList.append(pack);
|
||||
}
|
||||
catch(const JSONValidationError &e)
|
||||
{
|
||||
qWarning() << "Error while loading pack from CurseForge: " << e.cause();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(packs.size() < 25) {
|
||||
searchState = Finished;
|
||||
} else {
|
||||
nextSearchOffset += 25;
|
||||
searchState = CanPossiblyFetchMore;
|
||||
}
|
||||
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
|
||||
modpacks.append(newList);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void Flame::ListModel::searchRequestFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
if(searchState == ResetRequested) {
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
|
||||
nextSearchOffset = 0;
|
||||
performPaginatedSearch();
|
||||
} else {
|
||||
searchState = Finished;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
76
launcher/ui/pages/modplatform/flame/FlameModel.h
Normal file
76
launcher/ui/pages/modplatform/flame/FlameModel.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <RWStorage.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QThreadPool>
|
||||
#include <QIcon>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QMetaType>
|
||||
|
||||
#include <functional>
|
||||
#include <net/NetJob.h>
|
||||
|
||||
#include <modplatform/flame/FlamePackIndex.h>
|
||||
|
||||
namespace Flame {
|
||||
|
||||
|
||||
typedef QMap<QString, QIcon> LogoMap;
|
||||
typedef std::function<void(QString)> LogoCallback;
|
||||
|
||||
class ListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ListModel(QObject *parent);
|
||||
virtual ~ListModel();
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
bool canFetchMore(const QModelIndex & parent) const override;
|
||||
void fetchMore(const QModelIndex & parent) override;
|
||||
|
||||
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
|
||||
void searchWithTerm(const QString & term, const int sort);
|
||||
|
||||
private slots:
|
||||
void performPaginatedSearch();
|
||||
|
||||
void logoFailed(QString logo);
|
||||
void logoLoaded(QString logo, QIcon out);
|
||||
|
||||
void searchRequestFinished();
|
||||
void searchRequestFailed(QString reason);
|
||||
|
||||
private:
|
||||
void requestLogo(QString file, QString url);
|
||||
|
||||
private:
|
||||
QList<IndexedPack> modpacks;
|
||||
QStringList m_failedLogos;
|
||||
QStringList m_loadingLogos;
|
||||
LogoMap m_logoMap;
|
||||
QMap<QString, LogoCallback> waitingCallbacks;
|
||||
|
||||
QString currentSearchTerm;
|
||||
int currentSort = 0;
|
||||
int nextSearchOffset = 0;
|
||||
enum SearchState {
|
||||
None,
|
||||
CanPossiblyFetchMore,
|
||||
ResetRequested,
|
||||
Finished
|
||||
} searchState = None;
|
||||
NetJob::Ptr jobPtr;
|
||||
QByteArray response;
|
||||
};
|
||||
|
||||
}
|
186
launcher/ui/pages/modplatform/flame/FlamePage.cpp
Normal file
186
launcher/ui/pages/modplatform/flame/FlamePage.cpp
Normal file
@ -0,0 +1,186 @@
|
||||
#include "FlamePage.h"
|
||||
#include "ui_FlamePage.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "Application.h"
|
||||
#include "Json.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include "InstanceImportTask.h"
|
||||
#include "FlameModel.h"
|
||||
|
||||
FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
|
||||
ui->searchEdit->installEventFilter(this);
|
||||
listModel = new Flame::ListModel(this);
|
||||
ui->packView->setModel(listModel);
|
||||
|
||||
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
|
||||
|
||||
// index is used to set the sorting with the curseforge api
|
||||
ui->sortByBox->addItem(tr("Sort by featured"));
|
||||
ui->sortByBox->addItem(tr("Sort by popularity"));
|
||||
ui->sortByBox->addItem(tr("Sort by last updated"));
|
||||
ui->sortByBox->addItem(tr("Sort by name"));
|
||||
ui->sortByBox->addItem(tr("Sort by author"));
|
||||
ui->sortByBox->addItem(tr("Sort by total downloads"));
|
||||
|
||||
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlamePage::onVersionSelectionChanged);
|
||||
}
|
||||
|
||||
FlamePage::~FlamePage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool FlamePage::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Return) {
|
||||
triggerSearch();
|
||||
keyEvent->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
bool FlamePage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlamePage::openedImpl()
|
||||
{
|
||||
suggestCurrent();
|
||||
triggerSearch();
|
||||
}
|
||||
|
||||
void FlamePage::triggerSearch()
|
||||
{
|
||||
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
|
||||
}
|
||||
|
||||
void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
|
||||
if(!first.isValid())
|
||||
{
|
||||
if(isOpened)
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>();
|
||||
QString text = "";
|
||||
QString name = current.name;
|
||||
|
||||
if (current.websiteUrl.isEmpty())
|
||||
text = name;
|
||||
else
|
||||
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
|
||||
if (!current.authors.empty()) {
|
||||
auto authorToStr = [](Flame::ModpackAuthor & author) {
|
||||
if(author.url.isEmpty()) {
|
||||
return author.name;
|
||||
}
|
||||
return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
|
||||
};
|
||||
QStringList authorStrs;
|
||||
for(auto & author: current.authors) {
|
||||
authorStrs.push_back(authorToStr(author));
|
||||
}
|
||||
text += "<br>" + tr(" by ") + authorStrs.join(", ");
|
||||
}
|
||||
text += "<br><br>";
|
||||
|
||||
ui->packDescription->setHtml(text + current.description);
|
||||
|
||||
if (current.versionsLoaded == false)
|
||||
{
|
||||
qDebug() << "Loading flame modpack versions";
|
||||
NetJob *netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name));
|
||||
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
|
||||
int addonId = current.addonId;
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get()));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response]
|
||||
{
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if(parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
QJsonArray arr = doc.array();
|
||||
try
|
||||
{
|
||||
Flame::loadIndexedPackVersions(current, arr);
|
||||
}
|
||||
catch(const JSONValidationError &e)
|
||||
{
|
||||
qDebug() << *response;
|
||||
qWarning() << "Error while reading flame modpack version: " << e.cause();
|
||||
}
|
||||
|
||||
for(auto version : current.versions) {
|
||||
ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));
|
||||
}
|
||||
|
||||
suggestCurrent();
|
||||
});
|
||||
netJob->start(APPLICATION->network());
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto version : current.versions) {
|
||||
ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));
|
||||
}
|
||||
|
||||
suggestCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
void FlamePage::suggestCurrent()
|
||||
{
|
||||
if(!isOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedVersion.isEmpty())
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
return;
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion));
|
||||
QString editedLogoName;
|
||||
editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
|
||||
listModel->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||
});
|
||||
}
|
||||
|
||||
void FlamePage::onVersionSelectionChanged(QString data)
|
||||
{
|
||||
if(data.isNull() || data.isEmpty())
|
||||
{
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
selectedVersion = ui->versionSelectionBox->currentData().toString();
|
||||
suggestCurrent();
|
||||
}
|
80
launcher/ui/pages/modplatform/flame/FlamePage.h
Normal file
80
launcher/ui/pages/modplatform/flame/FlamePage.h
Normal file
@ -0,0 +1,80 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "tasks/Task.h"
|
||||
#include <modplatform/flame/FlamePackIndex.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class FlamePage;
|
||||
}
|
||||
|
||||
class NewInstanceDialog;
|
||||
|
||||
namespace Flame {
|
||||
class ListModel;
|
||||
}
|
||||
|
||||
class FlamePage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FlamePage(NewInstanceDialog* dialog, QWidget *parent = 0);
|
||||
virtual ~FlamePage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("CurseForge");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("flame");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "flame";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Flame-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);
|
||||
void onVersionSelectionChanged(QString data);
|
||||
|
||||
private:
|
||||
Ui::FlamePage *ui = nullptr;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
Flame::ListModel* listModel = nullptr;
|
||||
Flame::IndexedPack current;
|
||||
|
||||
QString selectedVersion;
|
||||
};
|
90
launcher/ui/pages/modplatform/flame/FlamePage.ui
Normal file
90
launcher/ui/pages/modplatform/flame/FlamePage.ui
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FlamePage</class>
|
||||
<widget class="QWidget" name="FlamePage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>837</width>
|
||||
<height>685</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0">
|
||||
<widget class="QListView" name="packView">
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Version selected:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QComboBox" name="sortByBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>searchEdit</tabstop>
|
||||
<tabstop>searchButton</tabstop>
|
||||
<tabstop>packView</tabstop>
|
||||
<tabstop>packDescription</tabstop>
|
||||
<tabstop>sortByBox</tabstop>
|
||||
<tabstop>versionSelectionBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
76
launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
Normal file
76
launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#include "FtbFilterModel.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "modplatform/modpacksch/FTBPackManifest.h"
|
||||
#include <MMCStrings.h>
|
||||
|
||||
namespace Ftb {
|
||||
|
||||
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
currentSorting = Sorting::ByPlays;
|
||||
sortings.insert(tr("Sort by plays"), Sorting::ByPlays);
|
||||
sortings.insert(tr("Sort by installs"), Sorting::ByInstalls);
|
||||
sortings.insert(tr("Sort by name"), Sorting::ByName);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
auto index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
auto pack = sourceModel()->data(index, Qt::UserRole).value<ModpacksCH::Modpack>();
|
||||
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
ModpacksCH::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<ModpacksCH::Modpack>();
|
||||
ModpacksCH::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<ModpacksCH::Modpack>();
|
||||
|
||||
if (currentSorting == ByPlays) {
|
||||
return leftPack.plays < rightPack.plays;
|
||||
}
|
||||
else if (currentSorting == ByInstalls) {
|
||||
return leftPack.installs < rightPack.installs;
|
||||
}
|
||||
else if (currentSorting == ByName) {
|
||||
return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
|
||||
}
|
||||
|
||||
// Invalid sorting set, somehow...
|
||||
qWarning() << "Invalid sorting set!";
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
35
launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
Normal file
35
launcher/ui/pages/modplatform/ftb/FtbFilterModel.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QSortFilterProxyModel>
|
||||
|
||||
namespace Ftb {
|
||||
|
||||
class FilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FilterModel(QObject* parent = Q_NULLPTR);
|
||||
enum Sorting {
|
||||
ByPlays,
|
||||
ByInstalls,
|
||||
ByName,
|
||||
};
|
||||
const QMap<QString, Sorting> getAvailableSortings();
|
||||
QString translateCurrentSorting();
|
||||
void setSorting(Sorting sorting);
|
||||
Sorting getCurrentSorting();
|
||||
void setSearchTerm(const 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 { "" };
|
||||
|
||||
};
|
||||
|
||||
}
|
278
launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
Normal file
278
launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
Normal file
@ -0,0 +1,278 @@
|
||||
#include "FtbListModel.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "Application.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
namespace Ftb {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ModpacksCH::Modpack pack = modpacks.at(pos);
|
||||
if(role == Qt::DisplayRole)
|
||||
{
|
||||
return pack.name;
|
||||
}
|
||||
else if (role == Qt::ToolTipRole)
|
||||
{
|
||||
return pack.synopsis;
|
||||
}
|
||||
else if(role == Qt::DecorationRole)
|
||||
{
|
||||
QIcon placeholder = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
|
||||
auto iter = m_logoMap.find(pack.name);
|
||||
if (iter != m_logoMap.end()) {
|
||||
auto & logo = *iter;
|
||||
if(!logo.result.isNull()) {
|
||||
return logo.result;
|
||||
}
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
for(auto art : pack.art) {
|
||||
if(art.type == "square") {
|
||||
((ListModel *)this)->requestLogo(pack.name, art.url);
|
||||
}
|
||||
}
|
||||
return placeholder;
|
||||
}
|
||||
else if(role == Qt::UserRole)
|
||||
{
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
|
||||
{
|
||||
if(m_logoMap.contains(logo))
|
||||
{
|
||||
callback(APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::request()
|
||||
{
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
|
||||
auto *netJob = new NetJob("Ftb::Request");
|
||||
auto url = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all");
|
||||
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();
|
||||
remainingPacks.clear();
|
||||
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
||||
if(parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
auto packs = doc.object().value("packs").toArray();
|
||||
for(auto pack : packs) {
|
||||
auto packId = pack.toInt();
|
||||
remainingPacks.append(packId);
|
||||
}
|
||||
|
||||
if(!remainingPacks.isEmpty()) {
|
||||
currentPack = remainingPacks.at(0);
|
||||
requestPack();
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::requestFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
remainingPacks.clear();
|
||||
}
|
||||
|
||||
void ListModel::requestPack()
|
||||
{
|
||||
auto *netJob = new NetJob("Ftb::Search");
|
||||
auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1")
|
||||
.arg(currentPack);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start(APPLICATION->network());
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::packRequestFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &ListModel::packRequestFailed);
|
||||
}
|
||||
|
||||
void ListModel::packRequestFinished()
|
||||
{
|
||||
jobPtr.reset();
|
||||
remainingPacks.removeOne(currentPack);
|
||||
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
||||
|
||||
if(parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
auto obj = doc.object();
|
||||
|
||||
ModpacksCH::Modpack pack;
|
||||
try
|
||||
{
|
||||
ModpacksCH::loadModpack(pack, obj);
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
qDebug() << QString::fromUtf8(response);
|
||||
qWarning() << "Error while reading pack manifest from FTB: " << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
// Since there is no guarantee that packs have a version, this will just
|
||||
// ignore those "dud" packs.
|
||||
if (pack.versions.empty())
|
||||
{
|
||||
qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions";
|
||||
}
|
||||
else
|
||||
{
|
||||
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size());
|
||||
modpacks.append(pack);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
if(!remainingPacks.isEmpty()) {
|
||||
currentPack = remainingPacks.at(0);
|
||||
requestPack();
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::packRequestFailed(QString reason)
|
||||
{
|
||||
jobPtr.reset();
|
||||
remainingPacks.removeOne(currentPack);
|
||||
}
|
||||
|
||||
void ListModel::logoLoaded(QString logo, bool stale)
|
||||
{
|
||||
auto & logoObj = m_logoMap[logo];
|
||||
logoObj.downloadJob.reset();
|
||||
QString smallPath = logoObj.fullpath + ".small";
|
||||
|
||||
QFileInfo smallInfo(smallPath);
|
||||
|
||||
if(stale || !smallInfo.exists()) {
|
||||
QImage image(logoObj.fullpath);
|
||||
if (image.isNull())
|
||||
{
|
||||
logoObj.failed = true;
|
||||
return;
|
||||
}
|
||||
QImage small;
|
||||
if (image.width() > image.height()) {
|
||||
small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
|
||||
}
|
||||
else {
|
||||
small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
|
||||
}
|
||||
QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
|
||||
QImage square(QSize(256, 256), QImage::Format_ARGB32);
|
||||
square.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&square);
|
||||
painter.drawImage(offset, small);
|
||||
painter.end();
|
||||
|
||||
square.save(logoObj.fullpath + ".small", "PNG");
|
||||
}
|
||||
|
||||
logoObj.result = QIcon(logoObj.fullpath + ".small");
|
||||
for(int i = 0; i < modpacks.size(); i++) {
|
||||
if(modpacks[i].name == logo) {
|
||||
emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListModel::logoFailed(QString logo)
|
||||
{
|
||||
m_logoMap[logo].failed = true;
|
||||
m_logoMap[logo].downloadJob.reset();
|
||||
}
|
||||
|
||||
void ListModel::requestLogo(QString logo, QString url)
|
||||
{
|
||||
if(m_logoMap.contains(logo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
|
||||
|
||||
bool stale = entry->isStale();
|
||||
|
||||
NetJob *job = new NetJob(QString("FTB 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, stale]
|
||||
{
|
||||
logoLoaded(logo, stale);
|
||||
});
|
||||
|
||||
QObject::connect(job, &NetJob::failed, this, [this, logo]
|
||||
{
|
||||
logoFailed(logo);
|
||||
});
|
||||
|
||||
auto &newLogoEntry = m_logoMap[logo];
|
||||
newLogoEntry.downloadJob = job;
|
||||
newLogoEntry.fullpath = fullPath;
|
||||
job->start(APPLICATION->network());
|
||||
}
|
||||
|
||||
}
|
61
launcher/ui/pages/modplatform/ftb/FtbListModel.h
Normal file
61
launcher/ui/pages/modplatform/ftb/FtbListModel.h
Normal file
@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "modplatform/modpacksch/FTBPackManifest.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QIcon>
|
||||
|
||||
namespace Ftb {
|
||||
|
||||
struct Logo {
|
||||
QString fullpath;
|
||||
NetJob::Ptr downloadJob;
|
||||
QIcon result;
|
||||
bool failed = false;
|
||||
};
|
||||
|
||||
typedef QMap<QString, Logo> 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 requestPack();
|
||||
void packRequestFinished();
|
||||
void packRequestFailed(QString reason);
|
||||
|
||||
void logoFailed(QString logo);
|
||||
void logoLoaded(QString logo, bool stale);
|
||||
|
||||
private:
|
||||
void requestLogo(QString file, QString url);
|
||||
|
||||
private:
|
||||
QList<ModpacksCH::Modpack> modpacks;
|
||||
LogoMap m_logoMap;
|
||||
|
||||
NetJob::Ptr jobPtr;
|
||||
int currentPack;
|
||||
QList<int> remainingPacks;
|
||||
QByteArray response;
|
||||
};
|
||||
|
||||
}
|
150
launcher/ui/pages/modplatform/ftb/FtbPage.cpp
Normal file
150
launcher/ui/pages/modplatform/ftb/FtbPage.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
#include "FtbPage.h"
|
||||
#include "ui_FtbPage.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include "modplatform/modpacksch/FTBPackInstallTask.h"
|
||||
|
||||
#include "HoeDown.h"
|
||||
|
||||
FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::FtbPage), dialog(dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
filterModel = new Ftb::FilterModel(this);
|
||||
listModel = new Ftb::ListModel(this);
|
||||
filterModel->setSourceModel(listModel);
|
||||
ui->packView->setModel(filterModel);
|
||||
ui->packView->setSortingEnabled(true);
|
||||
ui->packView->header()->hide();
|
||||
ui->packView->setIndentation(0);
|
||||
|
||||
ui->searchEdit->installEventFilter(this);
|
||||
|
||||
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, &FtbPage::triggerSearch);
|
||||
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged);
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged);
|
||||
}
|
||||
|
||||
FtbPage::~FtbPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool FtbPage::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Return) {
|
||||
triggerSearch();
|
||||
keyEvent->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
bool FtbPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FtbPage::openedImpl()
|
||||
{
|
||||
if(!initialised)
|
||||
{
|
||||
listModel->request();
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void FtbPage::suggestCurrent()
|
||||
{
|
||||
if(!isOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedVersion.isEmpty())
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
return;
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion));
|
||||
for(auto art : selected.art) {
|
||||
if(art.type == "square") {
|
||||
QString editedLogoName;
|
||||
editedLogoName = selected.name;
|
||||
|
||||
listModel->getLogo(selected.name, art.url, [this, editedLogoName](QString logo)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FtbPage::triggerSearch()
|
||||
{
|
||||
filterModel->setSearchTerm(ui->searchEdit->text());
|
||||
}
|
||||
|
||||
void FtbPage::onSortingSelectionChanged(QString data)
|
||||
{
|
||||
auto toSet = filterModel->getAvailableSortings().value(data);
|
||||
filterModel->setSorting(toSet);
|
||||
}
|
||||
|
||||
void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
|
||||
if(!first.isValid())
|
||||
{
|
||||
if(isOpened)
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
selected = filterModel->data(first, Qt::UserRole).value<ModpacksCH::Modpack>();
|
||||
|
||||
HoeDown hoedown;
|
||||
QString output = hoedown.process(selected.description.toUtf8());
|
||||
ui->packDescription->setHtml(output);
|
||||
|
||||
// reverse foreach, so that the newest versions are first
|
||||
for (auto i = selected.versions.size(); i--;) {
|
||||
ui->versionSelectionBox->addItem(selected.versions.at(i).name);
|
||||
}
|
||||
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void FtbPage::onVersionSelectionChanged(QString data)
|
||||
{
|
||||
if(data.isNull() || data.isEmpty())
|
||||
{
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
|
||||
selectedVersion = data;
|
||||
suggestCurrent();
|
||||
}
|
83
launcher/ui/pages/modplatform/ftb/FtbPage.h
Normal file
83
launcher/ui/pages/modplatform/ftb/FtbPage.h
Normal file
@ -0,0 +1,83 @@
|
||||
/* Copyright 2013-2021 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 "FtbFilterModel.h"
|
||||
#include "FtbListModel.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "Application.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class FtbPage;
|
||||
}
|
||||
|
||||
class NewInstanceDialog;
|
||||
|
||||
class FtbPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0);
|
||||
virtual ~FtbPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("FTB");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("ftb_logo");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "ftb";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "FTB-platform";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
void openedImpl() override;
|
||||
|
||||
bool eventFilter(QObject * watched, QEvent * event) override;
|
||||
|
||||
private:
|
||||
void suggestCurrent();
|
||||
|
||||
private slots:
|
||||
void triggerSearch();
|
||||
|
||||
void onSortingSelectionChanged(QString data);
|
||||
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
void onVersionSelectionChanged(QString data);
|
||||
|
||||
private:
|
||||
Ui::FtbPage *ui = nullptr;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
Ftb::ListModel* listModel = nullptr;
|
||||
Ftb::FilterModel* filterModel = nullptr;
|
||||
|
||||
ModpacksCH::Modpack selected;
|
||||
QString selectedVersion;
|
||||
|
||||
bool initialised { false };
|
||||
};
|
79
launcher/ui/pages/modplatform/ftb/FtbPage.ui
Normal file
79
launcher/ui/pages/modplatform/ftb/FtbPage.ui
Normal file
@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>FtbPage</class>
|
||||
<widget class="QWidget" name="FtbPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>875</width>
|
||||
<height>745</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="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>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeView" name="packView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextBrowser" name="packDescription">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>searchEdit</tabstop>
|
||||
<tabstop>versionSelectionBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
259
launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
Normal file
259
launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
Normal file
@ -0,0 +1,259 @@
|
||||
#include "ListModel.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include <MMCStrings.h>
|
||||
#include <Version.h>
|
||||
|
||||
#include <QtMath>
|
||||
#include <QLabel>
|
||||
|
||||
#include <RWStorage.h>
|
||||
|
||||
#include <BuildConfig.h>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
currentSorting = Sorting::ByGameVersion;
|
||||
sortings.insert(tr("Sort by name"), Sorting::ByName);
|
||||
sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion);
|
||||
}
|
||||
|
||||
bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
|
||||
Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
|
||||
|
||||
if(currentSorting == Sorting::ByGameVersion) {
|
||||
Version lv(leftPack.mcVersion);
|
||||
Version rv(rightPack.mcVersion);
|
||||
return lv < rv;
|
||||
|
||||
} else if(currentSorting == Sorting::ByName) {
|
||||
return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
|
||||
}
|
||||
|
||||
//UHM, some inavlid value set?!
|
||||
qWarning() << "Invalid sorting set!";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
|
||||
{
|
||||
return sortings;
|
||||
}
|
||||
|
||||
QString FilterModel::translateCurrentSorting()
|
||||
{
|
||||
return sortings.key(currentSorting);
|
||||
}
|
||||
|
||||
void FilterModel::setSorting(Sorting s)
|
||||
{
|
||||
currentSorting = s;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
FilterModel::Sorting FilterModel::getCurrentSorting()
|
||||
{
|
||||
return currentSorting;
|
||||
}
|
||||
|
||||
ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ListModel::~ListModel()
|
||||
{
|
||||
}
|
||||
|
||||
QString ListModel::translatePackType(PackType type) const
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case PackType::Public:
|
||||
return tr("Public Modpack");
|
||||
case PackType::ThirdParty:
|
||||
return tr("Third Party Modpack");
|
||||
case PackType::Private:
|
||||
return tr("Private Modpack");
|
||||
}
|
||||
qWarning() << "Unknown FTB modpack type:" << int(type);
|
||||
return QString();
|
||||
}
|
||||
|
||||
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 + "\n" + translatePackType(pack.type);
|
||||
}
|
||||
else if (role == Qt::ToolTipRole)
|
||||
{
|
||||
if(pack.description.length() > 100)
|
||||
{
|
||||
//some magic to prevent to long tooltips and replace html linebreaks
|
||||
QString edit = pack.description.left(97);
|
||||
edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
|
||||
return edit;
|
||||
|
||||
}
|
||||
return pack.description;
|
||||
}
|
||||
else if(role == Qt::DecorationRole)
|
||||
{
|
||||
if(m_logoMap.contains(pack.logo))
|
||||
{
|
||||
return (m_logoMap.value(pack.logo));
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel *)this)->requestLogo(pack.logo);
|
||||
return icon;
|
||||
}
|
||||
else if(role == Qt::TextColorRole)
|
||||
{
|
||||
if(pack.broken)
|
||||
{
|
||||
//FIXME: Hardcoded color
|
||||
return QColor(255, 0, 50);
|
||||
}
|
||||
else if(pack.bugged)
|
||||
{
|
||||
//FIXME: Hardcoded color
|
||||
//bugged pack, currently only indicates bugged xml
|
||||
return QColor(244, 229, 66);
|
||||
}
|
||||
}
|
||||
else if(role == Qt::UserRole)
|
||||
{
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void ListModel::fill(ModpackList modpacks)
|
||||
{
|
||||
beginResetModel();
|
||||
this->modpacks = modpacks;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void ListModel::addPack(Modpack modpack)
|
||||
{
|
||||
beginResetModel();
|
||||
this->modpacks.append(modpack);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void ListModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
Modpack ListModel::at(int row)
|
||||
{
|
||||
return modpacks.at(row);
|
||||
}
|
||||
|
||||
void ListModel::remove(int row)
|
||||
{
|
||||
if(row < 0 || row >= modpacks.size())
|
||||
{
|
||||
qWarning() << "Attempt to remove FTB modpacks with invalid row" << row;
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
modpacks.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void ListModel::logoLoaded(QString logo, QIcon out)
|
||||
{
|
||||
m_loadingLogos.removeAll(logo);
|
||||
m_logoMap.insert(logo, out);
|
||||
emit dataChanged(createIndex(0, 0), createIndex(1, 0));
|
||||
}
|
||||
|
||||
void ListModel::logoFailed(QString logo)
|
||||
{
|
||||
m_failedLogos.append(logo);
|
||||
m_loadingLogos.removeAll(logo);
|
||||
}
|
||||
|
||||
void ListModel::requestLogo(QString file)
|
||||
{
|
||||
if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
|
||||
NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file));
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::finished, 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);
|
||||
}
|
||||
|
||||
void ListModel::getLogo(const QString &logo, LogoCallback callback)
|
||||
{
|
||||
if(m_logoMap.contains(logo))
|
||||
{
|
||||
callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
requestLogo(logo);
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
return QAbstractListModel::flags(index);
|
||||
}
|
||||
|
||||
}
|
78
launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
Normal file
78
launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <modplatform/legacy_ftb/PackHelpers.h>
|
||||
#include <RWStorage.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QThreadPool>
|
||||
#include <QIcon>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
typedef QMap<QString, QIcon> FTBLogoMap;
|
||||
typedef std::function<void(QString)> LogoCallback;
|
||||
|
||||
class FilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FilterModel(QObject* parent = Q_NULLPTR);
|
||||
enum Sorting {
|
||||
ByName,
|
||||
ByGameVersion
|
||||
};
|
||||
const QMap<QString, Sorting> getAvailableSortings();
|
||||
QString translateCurrentSorting();
|
||||
void setSorting(Sorting sorting);
|
||||
Sorting getCurrentSorting();
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
class ListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
ModpackList modpacks;
|
||||
QStringList m_failedLogos;
|
||||
QStringList m_loadingLogos;
|
||||
FTBLogoMap m_logoMap;
|
||||
QMap<QString, LogoCallback> waitingCallbacks;
|
||||
|
||||
void requestLogo(QString file);
|
||||
QString translatePackType(PackType type) const;
|
||||
|
||||
|
||||
private slots:
|
||||
void logoFailed(QString logo);
|
||||
void logoLoaded(QString logo, QIcon out);
|
||||
|
||||
public:
|
||||
ListModel(QObject *parent);
|
||||
~ListModel();
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
void fill(ModpackList modpacks);
|
||||
void addPack(Modpack modpack);
|
||||
void clear();
|
||||
void remove(int row);
|
||||
|
||||
Modpack at(int row);
|
||||
void getLogo(const QString &logo, LogoCallback callback);
|
||||
};
|
||||
|
||||
}
|
371
launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
Normal file
371
launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
Normal file
@ -0,0 +1,371 @@
|
||||
#include "Page.h"
|
||||
#include "ui_Page.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
|
||||
#include "modplatform/legacy_ftb/PackFetchTask.h"
|
||||
#include "modplatform/legacy_ftb/PackInstallTask.h"
|
||||
#include "modplatform/legacy_ftb/PrivatePackManager.h"
|
||||
#include "ListModel.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
Page::Page(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), dialog(dialog), ui(new Ui::Page)
|
||||
{
|
||||
ftbFetchTask.reset(new PackFetchTask(APPLICATION->network()));
|
||||
ftbPrivatePacks.reset(new PrivatePackManager());
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
{
|
||||
publicFilterModel = new FilterModel(this);
|
||||
publicListModel = new ListModel(this);
|
||||
publicFilterModel->setSourceModel(publicListModel);
|
||||
|
||||
ui->publicPackList->setModel(publicFilterModel);
|
||||
ui->publicPackList->setSortingEnabled(true);
|
||||
ui->publicPackList->header()->hide();
|
||||
ui->publicPackList->setIndentation(0);
|
||||
ui->publicPackList->setIconSize(QSize(42, 42));
|
||||
|
||||
for(int i = 0; i < publicFilterModel->getAvailableSortings().size(); i++)
|
||||
{
|
||||
ui->sortByBox->addItem(publicFilterModel->getAvailableSortings().keys().at(i));
|
||||
}
|
||||
|
||||
ui->sortByBox->setCurrentText(publicFilterModel->translateCurrentSorting());
|
||||
}
|
||||
|
||||
{
|
||||
thirdPartyFilterModel = new FilterModel(this);
|
||||
thirdPartyModel = new ListModel(this);
|
||||
thirdPartyFilterModel->setSourceModel(thirdPartyModel);
|
||||
|
||||
ui->thirdPartyPackList->setModel(thirdPartyFilterModel);
|
||||
ui->thirdPartyPackList->setSortingEnabled(true);
|
||||
ui->thirdPartyPackList->header()->hide();
|
||||
ui->thirdPartyPackList->setIndentation(0);
|
||||
ui->thirdPartyPackList->setIconSize(QSize(42, 42));
|
||||
|
||||
thirdPartyFilterModel->setSorting(publicFilterModel->getCurrentSorting());
|
||||
}
|
||||
|
||||
{
|
||||
privateFilterModel = new FilterModel(this);
|
||||
privateListModel = new ListModel(this);
|
||||
privateFilterModel->setSourceModel(privateListModel);
|
||||
|
||||
ui->privatePackList->setModel(privateFilterModel);
|
||||
ui->privatePackList->setSortingEnabled(true);
|
||||
ui->privatePackList->header()->hide();
|
||||
ui->privatePackList->setIndentation(0);
|
||||
ui->privatePackList->setIconSize(QSize(42, 42));
|
||||
|
||||
privateFilterModel->setSorting(publicFilterModel->getCurrentSorting());
|
||||
}
|
||||
|
||||
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
|
||||
|
||||
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
|
||||
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
|
||||
|
||||
connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
|
||||
connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
|
||||
connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
|
||||
|
||||
connect(ui->addPackBtn, &QPushButton::pressed, this, &Page::onAddPackClicked);
|
||||
connect(ui->removePackBtn, &QPushButton::pressed, this, &Page::onRemovePackClicked);
|
||||
|
||||
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &Page::onTabChanged);
|
||||
|
||||
// ui->modpackInfo->setOpenExternalLinks(true);
|
||||
|
||||
ui->publicPackList->selectionModel()->reset();
|
||||
ui->thirdPartyPackList->selectionModel()->reset();
|
||||
ui->privatePackList->selectionModel()->reset();
|
||||
|
||||
onTabChanged(ui->tabWidget->currentIndex());
|
||||
}
|
||||
|
||||
Page::~Page()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool Page::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Page::openedImpl()
|
||||
{
|
||||
if(!initialized)
|
||||
{
|
||||
connect(ftbFetchTask.get(), &PackFetchTask::finished, this, &Page::ftbPackDataDownloadSuccessfully);
|
||||
connect(ftbFetchTask.get(), &PackFetchTask::failed, this, &Page::ftbPackDataDownloadFailed);
|
||||
|
||||
connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFinished, this, &Page::ftbPrivatePackDataDownloadSuccessfully);
|
||||
connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFailed, this, &Page::ftbPrivatePackDataDownloadFailed);
|
||||
|
||||
ftbFetchTask->fetch();
|
||||
ftbPrivatePacks->load();
|
||||
ftbFetchTask->fetchPrivate(ftbPrivatePacks->getCurrentPackCodes().toList());
|
||||
initialized = true;
|
||||
}
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void Page::suggestCurrent()
|
||||
{
|
||||
if(!isOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(selected.broken || selectedVersion.isEmpty())
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
return;
|
||||
}
|
||||
|
||||
dialog->setSuggestedPack(selected.name, new PackInstallTask(APPLICATION->network(), selected, selectedVersion));
|
||||
QString editedLogoName;
|
||||
if(selected.logo.toLower().startsWith("ftb"))
|
||||
{
|
||||
editedLogoName = selected.logo;
|
||||
}
|
||||
else
|
||||
{
|
||||
editedLogoName = "ftb_" + selected.logo;
|
||||
}
|
||||
|
||||
editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png"));
|
||||
|
||||
if(selected.type == PackType::Public)
|
||||
{
|
||||
publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||
});
|
||||
}
|
||||
else if (selected.type == PackType::ThirdParty)
|
||||
{
|
||||
thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||
});
|
||||
}
|
||||
else if (selected.type == PackType::Private)
|
||||
{
|
||||
privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Page::ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks)
|
||||
{
|
||||
publicListModel->fill(publicPacks);
|
||||
thirdPartyModel->fill(thirdPartyPacks);
|
||||
}
|
||||
|
||||
void Page::ftbPackDataDownloadFailed(QString reason)
|
||||
{
|
||||
//TODO: Display the error
|
||||
}
|
||||
|
||||
void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack)
|
||||
{
|
||||
privateListModel->addPack(pack);
|
||||
}
|
||||
|
||||
void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
|
||||
{
|
||||
auto reply = QMessageBox::question(
|
||||
this,
|
||||
tr("FTB private packs"),
|
||||
tr("Failed to download pack information for code %1.\nShould it be removed now?").arg(packCode)
|
||||
);
|
||||
if(reply == QMessageBox::Yes)
|
||||
{
|
||||
ftbPrivatePacks->remove(packCode);
|
||||
}
|
||||
}
|
||||
|
||||
void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
{
|
||||
if(!now.isValid())
|
||||
{
|
||||
onPackSelectionChanged();
|
||||
return;
|
||||
}
|
||||
Modpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<Modpack>();
|
||||
onPackSelectionChanged(&selectedPack);
|
||||
}
|
||||
|
||||
void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
{
|
||||
if(!now.isValid())
|
||||
{
|
||||
onPackSelectionChanged();
|
||||
return;
|
||||
}
|
||||
Modpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<Modpack>();
|
||||
onPackSelectionChanged(&selectedPack);
|
||||
}
|
||||
|
||||
void Page::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev)
|
||||
{
|
||||
if(!now.isValid())
|
||||
{
|
||||
onPackSelectionChanged();
|
||||
return;
|
||||
}
|
||||
Modpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<Modpack>();
|
||||
onPackSelectionChanged(&selectedPack);
|
||||
}
|
||||
|
||||
void Page::onPackSelectionChanged(Modpack* pack)
|
||||
{
|
||||
ui->versionSelectionBox->clear();
|
||||
if(pack)
|
||||
{
|
||||
currentModpackInfo->setHtml("Pack by <b>" + pack->author + "</b>" +
|
||||
"<br>Minecraft " + pack->mcVersion + "<br>" + "<br>" + pack->description + "<ul><li>" + pack->mods.replace(";", "</li><li>")
|
||||
+ "</li></ul>");
|
||||
bool currentAdded = false;
|
||||
|
||||
for(int i = 0; i < pack->oldVersions.size(); i++)
|
||||
{
|
||||
if(pack->currentVersion == pack->oldVersions.at(i))
|
||||
{
|
||||
currentAdded = true;
|
||||
}
|
||||
ui->versionSelectionBox->addItem(pack->oldVersions.at(i));
|
||||
}
|
||||
|
||||
if(!currentAdded)
|
||||
{
|
||||
ui->versionSelectionBox->addItem(pack->currentVersion);
|
||||
}
|
||||
selected = *pack;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentModpackInfo->setHtml("");
|
||||
ui->versionSelectionBox->clear();
|
||||
if(isOpened)
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void Page::onVersionSelectionItemChanged(QString data)
|
||||
{
|
||||
if(data.isNull() || data.isEmpty())
|
||||
{
|
||||
selectedVersion = "";
|
||||
return;
|
||||
}
|
||||
|
||||
selectedVersion = data;
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void Page::onSortingSelectionChanged(QString data)
|
||||
{
|
||||
FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data);
|
||||
publicFilterModel->setSorting(toSet);
|
||||
thirdPartyFilterModel->setSorting(toSet);
|
||||
privateFilterModel->setSorting(toSet);
|
||||
}
|
||||
|
||||
void Page::onTabChanged(int tab)
|
||||
{
|
||||
if(tab == 1)
|
||||
{
|
||||
currentModel = thirdPartyFilterModel;
|
||||
currentList = ui->thirdPartyPackList;
|
||||
currentModpackInfo = ui->thirdPartyPackDescription;
|
||||
}
|
||||
else if(tab == 2)
|
||||
{
|
||||
currentModel = privateFilterModel;
|
||||
currentList = ui->privatePackList;
|
||||
currentModpackInfo = ui->privatePackDescription;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentModel = publicFilterModel;
|
||||
currentList = ui->publicPackList;
|
||||
currentModpackInfo = ui->publicPackDescription;
|
||||
}
|
||||
|
||||
currentList->selectionModel()->reset();
|
||||
QModelIndex idx = currentList->currentIndex();
|
||||
if(idx.isValid())
|
||||
{
|
||||
auto pack = currentModel->data(idx, Qt::UserRole).value<Modpack>();
|
||||
onPackSelectionChanged(&pack);
|
||||
}
|
||||
else
|
||||
{
|
||||
onPackSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Page::onAddPackClicked()
|
||||
{
|
||||
bool ok;
|
||||
QString text = QInputDialog::getText(
|
||||
this,
|
||||
tr("Add FTB pack"),
|
||||
tr("Enter pack code:"),
|
||||
QLineEdit::Normal,
|
||||
QString(),
|
||||
&ok
|
||||
);
|
||||
if(ok && !text.isEmpty())
|
||||
{
|
||||
ftbPrivatePacks->add(text);
|
||||
ftbFetchTask->fetchPrivate({text});
|
||||
}
|
||||
}
|
||||
|
||||
void Page::onRemovePackClicked()
|
||||
{
|
||||
auto index = ui->privatePackList->currentIndex();
|
||||
if(!index.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto row = index.row();
|
||||
Modpack pack = privateListModel->at(row);
|
||||
auto answer = QMessageBox::question(
|
||||
this,
|
||||
tr("Remove pack"),
|
||||
tr("Are you sure you want to remove pack %1?").arg(pack.name),
|
||||
QMessageBox::Yes | QMessageBox::No
|
||||
);
|
||||
if(answer != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ftbPrivatePacks->remove(pack.packCode);
|
||||
privateListModel->remove(row);
|
||||
onPackSelectionChanged();
|
||||
}
|
||||
|
||||
}
|
119
launcher/ui/pages/modplatform/legacy_ftb/Page.h
Normal file
119
launcher/ui/pages/modplatform/legacy_ftb/Page.h
Normal file
@ -0,0 +1,119 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTreeView>
|
||||
#include <QTextBrowser>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "tasks/Task.h"
|
||||
#include "modplatform/legacy_ftb/PackHelpers.h"
|
||||
#include "modplatform/legacy_ftb/PackFetchTask.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class NewInstanceDialog;
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class Page;
|
||||
}
|
||||
|
||||
class ListModel;
|
||||
class FilterModel;
|
||||
class PrivatePackListModel;
|
||||
class PrivatePackFilterModel;
|
||||
class PrivatePackManager;
|
||||
|
||||
class Page : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Page(NewInstanceDialog * dialog, QWidget *parent = 0);
|
||||
virtual ~Page();
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("FTB Legacy");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("ftb_logo");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "legacy_ftb";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "FTB-platform";
|
||||
}
|
||||
bool shouldDisplay() const override;
|
||||
void openedImpl() override;
|
||||
|
||||
private:
|
||||
void suggestCurrent();
|
||||
void onPackSelectionChanged(Modpack *pack = nullptr);
|
||||
|
||||
private slots:
|
||||
void ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks);
|
||||
void ftbPackDataDownloadFailed(QString reason);
|
||||
|
||||
void ftbPrivatePackDataDownloadSuccessfully(Modpack pack);
|
||||
void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode);
|
||||
|
||||
void onSortingSelectionChanged(QString data);
|
||||
void onVersionSelectionItemChanged(QString data);
|
||||
|
||||
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
void onThirdPartyPackSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
void onPrivatePackSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
|
||||
void onTabChanged(int tab);
|
||||
|
||||
void onAddPackClicked();
|
||||
void onRemovePackClicked();
|
||||
|
||||
private:
|
||||
FilterModel* currentModel = nullptr;
|
||||
QTreeView* currentList = nullptr;
|
||||
QTextBrowser* currentModpackInfo = nullptr;
|
||||
|
||||
bool initialized = false;
|
||||
Modpack selected;
|
||||
QString selectedVersion;
|
||||
|
||||
ListModel* publicListModel = nullptr;
|
||||
FilterModel* publicFilterModel = nullptr;
|
||||
|
||||
ListModel *thirdPartyModel = nullptr;
|
||||
FilterModel *thirdPartyFilterModel = nullptr;
|
||||
|
||||
ListModel *privateListModel = nullptr;
|
||||
FilterModel *privateFilterModel = nullptr;
|
||||
|
||||
unique_qobject_ptr<PackFetchTask> ftbFetchTask;
|
||||
std::unique_ptr<PrivatePackManager> ftbPrivatePacks;
|
||||
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
|
||||
Ui::Page *ui = nullptr;
|
||||
};
|
||||
|
||||
}
|
135
launcher/ui/pages/modplatform/legacy_ftb/Page.ui
Normal file
135
launcher/ui/pages/modplatform/legacy_ftb/Page.ui
Normal file
@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LegacyFTB::Page</class>
|
||||
<widget class="QWidget" name="LegacyFTB::Page">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>709</width>
|
||||
<height>602</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Public</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeView" name="publicPackList">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextBrowser" name="publicPackDescription"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>3rd Party</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="1">
|
||||
<widget class="QTextBrowser" name="thirdPartyPackDescription"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeView" name="thirdPartyPackList">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Private</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeView" name="privatePackList">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>250</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="addPackBtn">
|
||||
<property name="text">
|
||||
<string>Add pack</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="removePackBtn">
|
||||
<property name="text">
|
||||
<string>Remove selected pack</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="3">
|
||||
<widget class="QTextBrowser" name="privatePackDescription"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<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="2">
|
||||
<widget class="QComboBox" name="versionSelectionBox"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QComboBox" name="sortByBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>265</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
42
launcher/ui/pages/modplatform/technic/TechnicData.h
Normal file
42
launcher/ui/pages/modplatform/technic/TechnicData.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* Copyright 2020-2021 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 <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace Technic {
|
||||
struct Modpack {
|
||||
QString slug;
|
||||
|
||||
QString name;
|
||||
QString logoUrl;
|
||||
QString logoName;
|
||||
|
||||
bool broken = true;
|
||||
|
||||
QString url;
|
||||
bool isSolder = false;
|
||||
QString minecraftVersion;
|
||||
|
||||
bool metadataLoaded = false;
|
||||
QString websiteUrl;
|
||||
QString author;
|
||||
QString description;
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(Technic::Modpack)
|
237
launcher/ui/pages/modplatform/technic/TechnicModel.cpp
Normal file
237
launcher/ui/pages/modplatform/technic/TechnicModel.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
/* Copyright 2020-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "TechnicModel.h"
|
||||
#include "Application.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
Technic::ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Technic::ListModel::~ListModel()
|
||||
{
|
||||
}
|
||||
|
||||
QVariant Technic::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::DecorationRole)
|
||||
{
|
||||
if(m_logoMap.contains(pack.logoName))
|
||||
{
|
||||
return (m_logoMap.value(pack.logoName));
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl);
|
||||
return icon;
|
||||
}
|
||||
else if(role == Qt::UserRole)
|
||||
{
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int Technic::ListModel::columnCount(const QModelIndex&) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Technic::ListModel::rowCount(const QModelIndex&) const
|
||||
{
|
||||
return modpacks.size();
|
||||
}
|
||||
|
||||
void Technic::ListModel::searchWithTerm(const QString& term)
|
||||
{
|
||||
if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) {
|
||||
return;
|
||||
}
|
||||
currentSearchTerm = term;
|
||||
if(jobPtr) {
|
||||
jobPtr->abort();
|
||||
searchState = ResetRequested;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
searchState = None;
|
||||
}
|
||||
performSearch();
|
||||
}
|
||||
|
||||
void Technic::ListModel::performSearch()
|
||||
{
|
||||
NetJob *netJob = new NetJob("Technic::Search");
|
||||
QString searchUrl = "";
|
||||
if (currentSearchTerm.isEmpty()) {
|
||||
searchUrl = "https://api.technicpack.net/trending?build=multimc";
|
||||
}
|
||||
else
|
||||
{
|
||||
searchUrl = QString(
|
||||
"https://api.technicpack.net/search?build=multimc&q=%1"
|
||||
).arg(currentSearchTerm);
|
||||
}
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start(APPLICATION->network());
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||
QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
|
||||
}
|
||||
|
||||
void Technic::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 Technic at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << response;
|
||||
return;
|
||||
}
|
||||
|
||||
QList<Modpack> newList;
|
||||
try {
|
||||
auto root = Json::requireObject(doc);
|
||||
auto objs = Json::requireArray(root, "modpacks");
|
||||
for (auto technicPack: objs) {
|
||||
Modpack pack;
|
||||
auto technicPackObject = Json::requireObject(technicPack);
|
||||
pack.name = Json::requireString(technicPackObject, "name");
|
||||
pack.slug = Json::requireString(technicPackObject, "slug");
|
||||
if (pack.slug == "vanilla")
|
||||
continue;
|
||||
|
||||
auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null");
|
||||
if(rawURL == "null") {
|
||||
pack.logoUrl = "null";
|
||||
pack.logoName = "null";
|
||||
}
|
||||
else {
|
||||
pack.logoUrl = rawURL;
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
|
||||
}
|
||||
pack.broken = false;
|
||||
newList.append(pack);
|
||||
}
|
||||
}
|
||||
catch (const JSONValidationError &err)
|
||||
{
|
||||
qCritical() << "Couldn't parse technic search results:" << err.cause() ;
|
||||
return;
|
||||
}
|
||||
searchState = Finished;
|
||||
beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
|
||||
modpacks.append(newList);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl, Technic::LogoCallback callback)
|
||||
{
|
||||
if(m_logoMap.contains(logo))
|
||||
{
|
||||
callback(APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
}
|
||||
|
||||
void Technic::ListModel::searchRequestFailed()
|
||||
{
|
||||
jobPtr.reset();
|
||||
|
||||
if(searchState == ResetRequested)
|
||||
{
|
||||
beginResetModel();
|
||||
modpacks.clear();
|
||||
endResetModel();
|
||||
|
||||
performSearch();
|
||||
}
|
||||
else
|
||||
{
|
||||
searchState = Finished;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Technic::ListModel::logoLoaded(QString logo, QString out)
|
||||
{
|
||||
m_loadingLogos.removeAll(logo);
|
||||
m_logoMap.insert(logo, QIcon(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 Technic::ListModel::logoFailed(QString logo)
|
||||
{
|
||||
m_failedLogos.append(logo);
|
||||
m_loadingLogos.removeAll(logo);
|
||||
}
|
||||
|
||||
void Technic::ListModel::requestLogo(QString logo, QString url)
|
||||
{
|
||||
if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
|
||||
NetJob *job = new NetJob(QString("Technic Icon Download %1").arg(logo));
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath]
|
||||
{
|
||||
logoLoaded(logo, fullPath);
|
||||
});
|
||||
|
||||
QObject::connect(job, &NetJob::failed, this, [this, logo]
|
||||
{
|
||||
logoFailed(logo);
|
||||
});
|
||||
|
||||
job->start(APPLICATION->network());
|
||||
|
||||
m_loadingLogos.append(logo);
|
||||
}
|
70
launcher/ui/pages/modplatform/technic/TechnicModel.h
Normal file
70
launcher/ui/pages/modplatform/technic/TechnicModel.h
Normal file
@ -0,0 +1,70 @@
|
||||
/* Copyright 2020-2021 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 <QModelIndex>
|
||||
|
||||
#include "TechnicData.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
namespace Technic {
|
||||
|
||||
typedef std::function<void(QString)> LogoCallback;
|
||||
|
||||
class ListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ListModel(QObject *parent);
|
||||
virtual ~ListModel();
|
||||
|
||||
virtual QVariant data(const QModelIndex& index, int role) const;
|
||||
virtual int columnCount(const QModelIndex& parent) const;
|
||||
virtual int rowCount(const QModelIndex& parent) const;
|
||||
|
||||
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
|
||||
void searchWithTerm(const QString & term);
|
||||
|
||||
private slots:
|
||||
void searchRequestFinished();
|
||||
void searchRequestFailed();
|
||||
|
||||
void logoFailed(QString logo);
|
||||
void logoLoaded(QString logo, QString out);
|
||||
|
||||
private:
|
||||
void performSearch();
|
||||
void requestLogo(QString logo, QString url);
|
||||
|
||||
private:
|
||||
QList<Modpack> modpacks;
|
||||
QStringList m_failedLogos;
|
||||
QStringList m_loadingLogos;
|
||||
QMap<QString, QIcon> m_logoMap;
|
||||
QMap<QString, LogoCallback> waitingCallbacks;
|
||||
|
||||
QString currentSearchTerm;
|
||||
enum SearchState {
|
||||
None,
|
||||
ResetRequested,
|
||||
Finished
|
||||
} searchState = None;
|
||||
NetJob::Ptr jobPtr;
|
||||
QByteArray response;
|
||||
};
|
||||
|
||||
}
|
201
launcher/ui/pages/modplatform/technic/TechnicPage.cpp
Normal file
201
launcher/ui/pages/modplatform/technic/TechnicPage.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "TechnicPage.h"
|
||||
#include "ui_TechnicPage.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
|
||||
#include "TechnicModel.h"
|
||||
#include "modplatform/technic/SingleZipPackInstallTask.h"
|
||||
#include "modplatform/technic/SolderPackInstallTask.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
|
||||
ui->searchEdit->installEventFilter(this);
|
||||
model = new Technic::ListModel(this);
|
||||
ui->packView->setModel(model);
|
||||
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
|
||||
}
|
||||
|
||||
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
|
||||
{
|
||||
if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Return) {
|
||||
triggerSearch();
|
||||
keyEvent->accept();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
TechnicPage::~TechnicPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool TechnicPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void TechnicPage::openedImpl()
|
||||
{
|
||||
suggestCurrent();
|
||||
triggerSearch();
|
||||
}
|
||||
|
||||
void TechnicPage::triggerSearch() {
|
||||
model->searchWithTerm(ui->searchEdit->text());
|
||||
}
|
||||
|
||||
void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second)
|
||||
{
|
||||
if(!first.isValid())
|
||||
{
|
||||
if(isOpened)
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
}
|
||||
//ui->frame->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
current = model->data(first, Qt::UserRole).value<Technic::Modpack>();
|
||||
suggestCurrent();
|
||||
}
|
||||
|
||||
void TechnicPage::suggestCurrent()
|
||||
{
|
||||
if (!isOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (current.broken)
|
||||
{
|
||||
dialog->setSuggestedPack();
|
||||
return;
|
||||
}
|
||||
|
||||
QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0);
|
||||
model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||
});
|
||||
|
||||
if (current.metadataLoaded)
|
||||
{
|
||||
metadataLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name));
|
||||
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
|
||||
QString slug = current.slug;
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get()));
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug]
|
||||
{
|
||||
if (current.slug != slug)
|
||||
{
|
||||
return;
|
||||
}
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
QJsonObject obj = doc.object();
|
||||
if(parse_error.error != QJsonParseError::NoError)
|
||||
{
|
||||
qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
if (!obj.contains("url"))
|
||||
{
|
||||
qWarning() << "Json doesn't contain an url key";
|
||||
return;
|
||||
}
|
||||
QJsonValueRef url = obj["url"];
|
||||
if (url.isString())
|
||||
{
|
||||
current.url = url.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!obj.contains("solder"))
|
||||
{
|
||||
qWarning() << "Json doesn't contain a valid url or solder key";
|
||||
return;
|
||||
}
|
||||
QJsonValueRef solderUrl = obj["solder"];
|
||||
if (solderUrl.isString())
|
||||
{
|
||||
current.url = solderUrl.toString();
|
||||
current.isSolder = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Json doesn't contain a valid url or solder key";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
current.minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__");
|
||||
current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__");
|
||||
current.author = Json::ensureString(obj, "user", QString(), "__placeholder__");
|
||||
current.description = Json::ensureString(obj, "description", QString(), "__placeholder__");
|
||||
current.metadataLoaded = true;
|
||||
metadataLoaded();
|
||||
});
|
||||
netJob->start(APPLICATION->network());
|
||||
}
|
||||
|
||||
// expects current.metadataLoaded to be true
|
||||
void TechnicPage::metadataLoaded()
|
||||
{
|
||||
QString text = "";
|
||||
QString name = current.name;
|
||||
|
||||
if (current.websiteUrl.isEmpty())
|
||||
// This allows injecting HTML here.
|
||||
text = name;
|
||||
else
|
||||
// URL not properly escaped for inclusion in HTML. The name allows for injecting HTML.
|
||||
text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
|
||||
if (!current.author.isEmpty()) {
|
||||
// This allows injecting HTML here
|
||||
text += tr(" by ") + current.author;
|
||||
}
|
||||
|
||||
ui->frame->setModText(text);
|
||||
ui->frame->setModDescription(current.description);
|
||||
if (!current.isSolder)
|
||||
{
|
||||
dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion));
|
||||
}
|
||||
else
|
||||
{
|
||||
while (current.url.endsWith('/')) current.url.chop(1);
|
||||
dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url + "/modpack/" + current.slug, current.minecraftVersion));
|
||||
}
|
||||
}
|
78
launcher/ui/pages/modplatform/technic/TechnicPage.h
Normal file
78
launcher/ui/pages/modplatform/technic/TechnicPage.h
Normal file
@ -0,0 +1,78 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "tasks/Task.h"
|
||||
#include "TechnicData.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class TechnicPage;
|
||||
}
|
||||
|
||||
class NewInstanceDialog;
|
||||
|
||||
namespace Technic {
|
||||
class ListModel;
|
||||
}
|
||||
|
||||
class TechnicPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0);
|
||||
virtual ~TechnicPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Technic");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("technic");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "technic";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Technic-platform";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
void openedImpl() override;
|
||||
|
||||
bool eventFilter(QObject* watched, QEvent* event) override;
|
||||
|
||||
private:
|
||||
void suggestCurrent();
|
||||
void metadataLoaded();
|
||||
|
||||
private slots:
|
||||
void triggerSearch();
|
||||
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
||||
|
||||
private:
|
||||
Ui::TechnicPage *ui = nullptr;
|
||||
NewInstanceDialog* dialog = nullptr;
|
||||
Technic::ListModel* model = nullptr;
|
||||
Technic::Modpack current;
|
||||
};
|
95
launcher/ui/pages/modplatform/technic/TechnicPage.ui
Normal file
95
launcher/ui/pages/modplatform/technic/TechnicPage.ui
Normal file
@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TechnicPage</class>
|
||||
<widget class="QWidget" name="TechnicPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>546</width>
|
||||
<height>405</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="searchEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Search and filter ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="searchButton">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="packView">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="MCModInfoFrame" name="frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MCModInfoFrame</class>
|
||||
<extends>QFrame</extends>
|
||||
<header>ui/widgets/MCModInfoFrame.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>searchEdit</tabstop>
|
||||
<tabstop>searchButton</tabstop>
|
||||
<tabstop>packView</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Reference in New Issue
Block a user