NOISSUE continue reshuffling the codebase
This commit is contained in:
58
launcher/ui/pages/BasePage.h
Normal file
58
launcher/ui/pages/BasePage.h
Normal file
@ -0,0 +1,58 @@
|
||||
/* 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 <QString>
|
||||
#include <QIcon>
|
||||
#include <memory>
|
||||
|
||||
#include "BasePageContainer.h"
|
||||
|
||||
class BasePage
|
||||
{
|
||||
public:
|
||||
virtual ~BasePage() {}
|
||||
virtual QString id() const = 0;
|
||||
virtual QString displayName() const = 0;
|
||||
virtual QIcon icon() const = 0;
|
||||
virtual bool apply() { return true; }
|
||||
virtual bool shouldDisplay() const { return true; }
|
||||
virtual QString helpPage() const { return QString(); }
|
||||
void opened()
|
||||
{
|
||||
isOpened = true;
|
||||
openedImpl();
|
||||
}
|
||||
void closed()
|
||||
{
|
||||
isOpened = false;
|
||||
closedImpl();
|
||||
}
|
||||
virtual void openedImpl() {}
|
||||
virtual void closedImpl() {}
|
||||
virtual void setParentContainer(BasePageContainer * container)
|
||||
{
|
||||
m_container = container;
|
||||
};
|
||||
public:
|
||||
int stackIndex = -1;
|
||||
int listIndex = -1;
|
||||
protected:
|
||||
BasePageContainer * m_container = nullptr;
|
||||
bool isOpened = false;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<BasePage> BasePagePtr;
|
10
launcher/ui/pages/BasePageContainer.h
Normal file
10
launcher/ui/pages/BasePageContainer.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
class BasePageContainer
|
||||
{
|
||||
public:
|
||||
virtual ~BasePageContainer(){};
|
||||
virtual bool selectPage(QString pageId) = 0;
|
||||
virtual void refreshContainer() = 0;
|
||||
virtual bool requestClose() = 0;
|
||||
};
|
68
launcher/ui/pages/BasePageProvider.h
Normal file
68
launcher/ui/pages/BasePageProvider.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* 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 "ui/pages/BasePage.h"
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
class BasePageProvider
|
||||
{
|
||||
public:
|
||||
virtual QList<BasePage *> getPages() = 0;
|
||||
virtual QString dialogTitle() = 0;
|
||||
};
|
||||
|
||||
class GenericPageProvider : public BasePageProvider
|
||||
{
|
||||
typedef std::function<BasePage *()> PageCreator;
|
||||
public:
|
||||
explicit GenericPageProvider(const QString &dialogTitle)
|
||||
: m_dialogTitle(dialogTitle)
|
||||
{
|
||||
}
|
||||
virtual ~GenericPageProvider() {}
|
||||
|
||||
QList<BasePage *> getPages() override
|
||||
{
|
||||
QList<BasePage *> pages;
|
||||
for (PageCreator creator : m_creators)
|
||||
{
|
||||
pages.append(creator());
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
QString dialogTitle() override { return m_dialogTitle; }
|
||||
|
||||
void setDialogTitle(const QString &title)
|
||||
{
|
||||
m_dialogTitle = title;
|
||||
}
|
||||
void addPageCreator(PageCreator page)
|
||||
{
|
||||
m_creators.append(page);
|
||||
}
|
||||
|
||||
template<typename PageClass>
|
||||
void addPage()
|
||||
{
|
||||
addPageCreator([](){return new PageClass();});
|
||||
}
|
||||
|
||||
private:
|
||||
QList<PageCreator> m_creators;
|
||||
QString m_dialogTitle;
|
||||
};
|
260
launcher/ui/pages/global/AccountListPage.cpp
Normal file
260
launcher/ui/pages/global/AccountListPage.cpp
Normal file
@ -0,0 +1,260 @@
|
||||
/* 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 "AccountListPage.h"
|
||||
#include "ui_AccountListPage.h"
|
||||
|
||||
#include <QItemSelectionModel>
|
||||
#include <QMenu>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/LoginDialog.h"
|
||||
#include "ui/dialogs/MSALoginDialog.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/SkinUploadDialog.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "minecraft/auth/AccountTask.h"
|
||||
#include "minecraft/services/SkinDelete.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "Secrets.h"
|
||||
|
||||
AccountListPage::AccountListPage(QWidget *parent)
|
||||
: QMainWindow(parent), ui(new Ui::AccountListPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->listView->setEmptyString(tr(
|
||||
"Welcome!\n"
|
||||
"If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account."
|
||||
));
|
||||
ui->listView->setEmptyMode(VersionListView::String);
|
||||
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
m_accounts = APPLICATION->accounts();
|
||||
|
||||
ui->listView->setModel(m_accounts.get());
|
||||
ui->listView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
ui->listView->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
|
||||
// Expand the account column
|
||||
|
||||
QItemSelectionModel *selectionModel = ui->listView->selectionModel();
|
||||
|
||||
connect(selectionModel, &QItemSelectionModel::selectionChanged, [this](const QItemSelection &sel, const QItemSelection &dsel) {
|
||||
updateButtonStates();
|
||||
});
|
||||
connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu);
|
||||
|
||||
connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged);
|
||||
connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged);
|
||||
connect(m_accounts.get(), &AccountList::defaultAccountChanged, this, &AccountListPage::listChanged);
|
||||
|
||||
updateButtonStates();
|
||||
|
||||
// Xbox authentication won't work without a client identifier, so disable the button if it is missing
|
||||
ui->actionAddMicrosoft->setVisible(Secrets::hasMSAClientID());
|
||||
}
|
||||
|
||||
AccountListPage::~AccountListPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AccountListPage::ShowContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
menu->exec(ui->listView->mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
|
||||
void AccountListPage::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::LanguageChange)
|
||||
{
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
QMainWindow::changeEvent(event);
|
||||
}
|
||||
|
||||
QMenu * AccountListPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction(ui->toolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
|
||||
void AccountListPage::listChanged()
|
||||
{
|
||||
updateButtonStates();
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAddMojang_triggered()
|
||||
{
|
||||
MinecraftAccountPtr account = LoginDialog::newAccount(
|
||||
this,
|
||||
tr("Please enter your Mojang account email and password to add your account.")
|
||||
);
|
||||
|
||||
if (account)
|
||||
{
|
||||
m_accounts->addAccount(account);
|
||||
if (m_accounts->count() == 1) {
|
||||
m_accounts->setDefaultAccount(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionAddMicrosoft_triggered()
|
||||
{
|
||||
if(BuildConfig.BUILD_PLATFORM == "osx64") {
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Microsoft Accounts not available"),
|
||||
tr(
|
||||
"Microsoft accounts are only usable on macOS 10.13 or newer, with fully updated MultiMC.\n\n"
|
||||
"Please update both your operating system and MultiMC."
|
||||
),
|
||||
QMessageBox::Warning
|
||||
)->exec();
|
||||
return;
|
||||
}
|
||||
MinecraftAccountPtr account = MSALoginDialog::newAccount(
|
||||
this,
|
||||
tr("Please enter your Mojang account email and password to add your account.")
|
||||
);
|
||||
|
||||
if (account)
|
||||
{
|
||||
m_accounts->addAccount(account);
|
||||
if (m_accounts->count() == 1) {
|
||||
m_accounts->setDefaultAccount(account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionRemove_triggered()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
{
|
||||
QModelIndex selected = selection.first();
|
||||
m_accounts->removeAccount(selected);
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionRefresh_triggered() {
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0) {
|
||||
QModelIndex selected = selection.first();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
AuthSessionPtr session = std::make_shared<AuthSession>();
|
||||
auto task = account->refresh(session);
|
||||
if (task) {
|
||||
ProgressDialog progDialog(this);
|
||||
progDialog.execWithTask(task.get());
|
||||
// TODO: respond to results of the task
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AccountListPage::on_actionSetDefault_triggered()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
{
|
||||
QModelIndex selected = selection.first();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
m_accounts->setDefaultAccount(account);
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionNoDefault_triggered()
|
||||
{
|
||||
m_accounts->setDefaultAccount(nullptr);
|
||||
}
|
||||
|
||||
void AccountListPage::updateButtonStates()
|
||||
{
|
||||
// If there is no selection, disable buttons that require something selected.
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
bool hasSelection = selection.size() > 0;
|
||||
bool accountIsReady = false;
|
||||
if (hasSelection)
|
||||
{
|
||||
QModelIndex selected = selection.first();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
accountIsReady = !account->isActive();
|
||||
}
|
||||
ui->actionRemove->setEnabled(accountIsReady);
|
||||
ui->actionSetDefault->setEnabled(accountIsReady);
|
||||
ui->actionUploadSkin->setEnabled(accountIsReady);
|
||||
ui->actionDeleteSkin->setEnabled(accountIsReady);
|
||||
ui->actionRefresh->setEnabled(accountIsReady);
|
||||
|
||||
if(m_accounts->defaultAccount().get() == nullptr) {
|
||||
ui->actionNoDefault->setEnabled(false);
|
||||
ui->actionNoDefault->setChecked(true);
|
||||
}
|
||||
else {
|
||||
ui->actionNoDefault->setEnabled(true);
|
||||
ui->actionNoDefault->setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionUploadSkin_triggered()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() > 0)
|
||||
{
|
||||
QModelIndex selected = selection.first();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
SkinUploadDialog dialog(account, this);
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountListPage::on_actionDeleteSkin_triggered()
|
||||
{
|
||||
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.size() <= 0)
|
||||
return;
|
||||
|
||||
QModelIndex selected = selection.first();
|
||||
AuthSessionPtr session = std::make_shared<AuthSession>();
|
||||
MinecraftAccountPtr account = selected.data(AccountList::PointerRole).value<MinecraftAccountPtr>();
|
||||
auto login = account->refresh(session);
|
||||
ProgressDialog prog(this);
|
||||
if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to login!"), QMessageBox::Warning)->exec();
|
||||
return;
|
||||
}
|
||||
auto deleteSkinTask = std::make_shared<SkinDelete>(this, session);
|
||||
if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) {
|
||||
CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec();
|
||||
return;
|
||||
}
|
||||
}
|
85
launcher/ui/pages/global/AccountListPage.h
Normal file
85
launcher/ui/pages/global/AccountListPage.h
Normal file
@ -0,0 +1,85 @@
|
||||
/* 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 <QMainWindow>
|
||||
#include <memory>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
|
||||
#include "minecraft/auth/AccountList.h"
|
||||
#include "Application.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class AccountListPage;
|
||||
}
|
||||
|
||||
class AuthenticateTask;
|
||||
|
||||
class AccountListPage : public QMainWindow, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AccountListPage(QWidget *parent = 0);
|
||||
~AccountListPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Accounts");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
auto icon = APPLICATION->getThemedIcon("accounts");
|
||||
if(icon.isNull())
|
||||
{
|
||||
icon = APPLICATION->getThemedIcon("noaccount");
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "accounts";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Getting-Started#adding-an-account";
|
||||
}
|
||||
|
||||
public slots:
|
||||
void on_actionAddMojang_triggered();
|
||||
void on_actionAddMicrosoft_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
void on_actionRefresh_triggered();
|
||||
void on_actionSetDefault_triggered();
|
||||
void on_actionNoDefault_triggered();
|
||||
void on_actionUploadSkin_triggered();
|
||||
void on_actionDeleteSkin_triggered();
|
||||
|
||||
void listChanged();
|
||||
|
||||
//! Updates the states of the dialog's buttons.
|
||||
void updateButtonStates();
|
||||
|
||||
protected slots:
|
||||
void ShowContextMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
void changeEvent(QEvent * event) override;
|
||||
QMenu * createPopupMenu() override;
|
||||
shared_qobject_ptr<AccountList> m_accounts;
|
||||
Ui::AccountListPage *ui;
|
||||
};
|
129
launcher/ui/pages/global/AccountListPage.ui
Normal file
129
launcher/ui/pages/global/AccountListPage.ui
Normal file
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AccountListPage</class>
|
||||
<widget class="QMainWindow" name="AccountListPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<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="VersionListView" name="listView">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="WideBar" name="toolBar">
|
||||
<attribute name="toolBarArea">
|
||||
<enum>RightToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAddMicrosoft"/>
|
||||
<addaction name="actionAddMojang"/>
|
||||
<addaction name="actionRefresh"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionSetDefault"/>
|
||||
<addaction name="actionNoDefault"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionUploadSkin"/>
|
||||
<addaction name="actionDeleteSkin"/>
|
||||
</widget>
|
||||
<action name="actionAddMojang">
|
||||
<property name="text">
|
||||
<string>Add Mojang</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSetDefault">
|
||||
<property name="text">
|
||||
<string>Set Default</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNoDefault">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Default</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUploadSkin">
|
||||
<property name="text">
|
||||
<string>Upload Skin</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDeleteSkin">
|
||||
<property name="text">
|
||||
<string>Delete Skin</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete the currently active skin and go back to the default one</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAddMicrosoft">
|
||||
<property name="text">
|
||||
<string>Add Microsoft</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRefresh">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Refresh the account tokens</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>VersionListView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>ui/widgets/VersionListView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>WideBar</class>
|
||||
<extends>QToolBar</extends>
|
||||
<header>ui/widgets/WideBar.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
51
launcher/ui/pages/global/CustomCommandsPage.cpp
Normal file
51
launcher/ui/pages/global/CustomCommandsPage.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "CustomCommandsPage.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QTabWidget>
|
||||
#include <QTabBar>
|
||||
|
||||
CustomCommandsPage::CustomCommandsPage(QWidget* parent): QWidget(parent)
|
||||
{
|
||||
|
||||
auto verticalLayout = new QVBoxLayout(this);
|
||||
verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
|
||||
verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto tabWidget = new QTabWidget(this);
|
||||
tabWidget->setObjectName(QStringLiteral("tabWidget"));
|
||||
commands = new CustomCommands(this);
|
||||
commands->setContentsMargins(6, 6, 6, 6);
|
||||
tabWidget->addTab(commands, "Foo");
|
||||
tabWidget->tabBar()->hide();
|
||||
verticalLayout->addWidget(tabWidget);
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
CustomCommandsPage::~CustomCommandsPage()
|
||||
{
|
||||
}
|
||||
|
||||
bool CustomCommandsPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomCommandsPage::applySettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
s->set("PreLaunchCommand", commands->prelaunchCommand());
|
||||
s->set("WrapperCommand", commands->wrapperCommand());
|
||||
s->set("PostExitCommand", commands->postexitCommand());
|
||||
}
|
||||
|
||||
void CustomCommandsPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
commands->initialize(
|
||||
false,
|
||||
true,
|
||||
s->get("PreLaunchCommand").toString(),
|
||||
s->get("WrapperCommand").toString(),
|
||||
s->get("PostExitCommand").toString()
|
||||
);
|
||||
}
|
55
launcher/ui/pages/global/CustomCommandsPage.h
Normal file
55
launcher/ui/pages/global/CustomCommandsPage.h
Normal file
@ -0,0 +1,55 @@
|
||||
/* Copyright 2018-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 <memory>
|
||||
#include <QDialog>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "ui/widgets/CustomCommands.h"
|
||||
|
||||
class CustomCommandsPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CustomCommandsPage(QWidget *parent = 0);
|
||||
~CustomCommandsPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Custom Commands");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("custom-commands");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "custom-commands";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Custom-commands";
|
||||
}
|
||||
bool apply() override;
|
||||
|
||||
private:
|
||||
void applySettings();
|
||||
void loadSettings();
|
||||
CustomCommands * commands;
|
||||
};
|
233
launcher/ui/pages/global/ExternalToolsPage.cpp
Normal file
233
launcher/ui/pages/global/ExternalToolsPage.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
/* 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 "ExternalToolsPage.h"
|
||||
#include "ui_ExternalToolsPage.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QTabBar>
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "tools/BaseProfiler.h"
|
||||
#include <FileSystem.h>
|
||||
#include "Application.h"
|
||||
#include <tools/MCEditTool.h>
|
||||
|
||||
ExternalToolsPage::ExternalToolsPage(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::ExternalToolsPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
|
||||
ui->jsonEditorTextBox->setClearButtonEnabled(true);
|
||||
#endif
|
||||
|
||||
ui->mceditLink->setOpenExternalLinks(true);
|
||||
ui->jvisualvmLink->setOpenExternalLinks(true);
|
||||
ui->jprofilerLink->setOpenExternalLinks(true);
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
ExternalToolsPage::~ExternalToolsPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ExternalToolsPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
ui->jprofilerPathEdit->setText(s->get("JProfilerPath").toString());
|
||||
ui->jvisualvmPathEdit->setText(s->get("JVisualVMPath").toString());
|
||||
ui->mceditPathEdit->setText(s->get("MCEditPath").toString());
|
||||
|
||||
// Editors
|
||||
ui->jsonEditorTextBox->setText(s->get("JsonEditor").toString());
|
||||
}
|
||||
void ExternalToolsPage::applySettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
|
||||
s->set("JProfilerPath", ui->jprofilerPathEdit->text());
|
||||
s->set("JVisualVMPath", ui->jvisualvmPathEdit->text());
|
||||
s->set("MCEditPath", ui->mceditPathEdit->text());
|
||||
|
||||
// Editors
|
||||
QString jsonEditor = ui->jsonEditorTextBox->text();
|
||||
if (!jsonEditor.isEmpty() &&
|
||||
(!QFileInfo(jsonEditor).exists() || !QFileInfo(jsonEditor).isExecutable()))
|
||||
{
|
||||
QString found = QStandardPaths::findExecutable(jsonEditor);
|
||||
if (!found.isEmpty())
|
||||
{
|
||||
jsonEditor = found;
|
||||
}
|
||||
}
|
||||
s->set("JsonEditor", jsonEditor);
|
||||
}
|
||||
|
||||
void ExternalToolsPage::on_jprofilerPathBtn_clicked()
|
||||
{
|
||||
QString raw_dir = ui->jprofilerPathEdit->text();
|
||||
QString error;
|
||||
do
|
||||
{
|
||||
raw_dir = QFileDialog::getExistingDirectory(this, tr("JProfiler Folder"), raw_dir);
|
||||
if (raw_dir.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
QString cooked_dir = FS::NormalizePath(raw_dir);
|
||||
if (!APPLICATION->profilers()["jprofiler"]->check(cooked_dir, &error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->jprofilerPathEdit->setText(cooked_dir);
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
void ExternalToolsPage::on_jprofilerCheckBtn_clicked()
|
||||
{
|
||||
QString error;
|
||||
if (!APPLICATION->profilers()["jprofiler"]->check(ui->jprofilerPathEdit->text(), &error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Error while checking JProfiler install:\n%1").arg(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information(this, tr("OK"), tr("JProfiler setup seems to be OK"));
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalToolsPage::on_jvisualvmPathBtn_clicked()
|
||||
{
|
||||
QString raw_dir = ui->jvisualvmPathEdit->text();
|
||||
QString error;
|
||||
do
|
||||
{
|
||||
raw_dir = QFileDialog::getOpenFileName(this, tr("JVisualVM Executable"), raw_dir);
|
||||
if (raw_dir.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
QString cooked_dir = FS::NormalizePath(raw_dir);
|
||||
if (!APPLICATION->profilers()["jvisualvm"]->check(cooked_dir, &error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->jvisualvmPathEdit->setText(cooked_dir);
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
void ExternalToolsPage::on_jvisualvmCheckBtn_clicked()
|
||||
{
|
||||
QString error;
|
||||
if (!APPLICATION->profilers()["jvisualvm"]->check(ui->jvisualvmPathEdit->text(), &error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Error while checking JVisualVM install:\n%1").arg(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information(this, tr("OK"), tr("JVisualVM setup seems to be OK"));
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalToolsPage::on_mceditPathBtn_clicked()
|
||||
{
|
||||
QString raw_dir = ui->mceditPathEdit->text();
|
||||
QString error;
|
||||
do
|
||||
{
|
||||
#ifdef Q_OS_OSX
|
||||
raw_dir = QFileDialog::getOpenFileName(this, tr("MCEdit Application"), raw_dir);
|
||||
#else
|
||||
raw_dir = QFileDialog::getExistingDirectory(this, tr("MCEdit Folder"), raw_dir);
|
||||
#endif
|
||||
if (raw_dir.isEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
QString cooked_dir = FS::NormalizePath(raw_dir);
|
||||
if (!APPLICATION->mcedit()->check(cooked_dir, error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->mceditPathEdit->setText(cooked_dir);
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
void ExternalToolsPage::on_mceditCheckBtn_clicked()
|
||||
{
|
||||
QString error;
|
||||
if (!APPLICATION->mcedit()->check(ui->mceditPathEdit->text(), error))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Error while checking MCEdit install:\n%1").arg(error));
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::information(this, tr("OK"), tr("MCEdit setup seems to be OK"));
|
||||
}
|
||||
}
|
||||
|
||||
void ExternalToolsPage::on_jsonEditorBrowseBtn_clicked()
|
||||
{
|
||||
QString raw_file = QFileDialog::getOpenFileName(
|
||||
this, tr("JSON Editor"),
|
||||
ui->jsonEditorTextBox->text().isEmpty()
|
||||
#if defined(Q_OS_LINUX)
|
||||
? QString("/usr/bin")
|
||||
#else
|
||||
? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first()
|
||||
#endif
|
||||
: ui->jsonEditorTextBox->text());
|
||||
|
||||
if (raw_file.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
QString cooked_file = FS::NormalizePath(raw_file);
|
||||
|
||||
// it has to exist and be an executable
|
||||
if (QFileInfo(cooked_file).exists() && QFileInfo(cooked_file).isExecutable())
|
||||
{
|
||||
ui->jsonEditorTextBox->setText(cooked_file);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid"),
|
||||
tr("The file chosen does not seem to be an executable"));
|
||||
}
|
||||
}
|
||||
|
||||
bool ExternalToolsPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
74
launcher/ui/pages/global/ExternalToolsPage.h
Normal file
74
launcher/ui/pages/global/ExternalToolsPage.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* 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>
|
||||
|
||||
namespace Ui {
|
||||
class ExternalToolsPage;
|
||||
}
|
||||
|
||||
class ExternalToolsPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ExternalToolsPage(QWidget *parent = 0);
|
||||
~ExternalToolsPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("External Tools");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
auto icon = APPLICATION->getThemedIcon("externaltools");
|
||||
if(icon.isNull())
|
||||
{
|
||||
icon = APPLICATION->getThemedIcon("loadermods");
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "external-tools";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Tools";
|
||||
}
|
||||
virtual bool apply() override;
|
||||
|
||||
private:
|
||||
void loadSettings();
|
||||
void applySettings();
|
||||
|
||||
private:
|
||||
Ui::ExternalToolsPage *ui;
|
||||
|
||||
private
|
||||
slots:
|
||||
void on_jprofilerPathBtn_clicked();
|
||||
void on_jprofilerCheckBtn_clicked();
|
||||
void on_jvisualvmPathBtn_clicked();
|
||||
void on_jvisualvmCheckBtn_clicked();
|
||||
void on_mceditPathBtn_clicked();
|
||||
void on_mceditCheckBtn_clicked();
|
||||
void on_jsonEditorBrowseBtn_clicked();
|
||||
};
|
194
launcher/ui/pages/global/ExternalToolsPage.ui
Normal file
194
launcher/ui/pages/global/ExternalToolsPage.ui
Normal file
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ExternalToolsPage</class>
|
||||
<widget class="QWidget" name="ExternalToolsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>673</width>
|
||||
<height>751</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string notr="true">JProfiler</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="jprofilerPathEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="jprofilerPathBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="jprofilerCheckBtn">
|
||||
<property name="text">
|
||||
<string>Check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="jprofilerLink">
|
||||
<property name="text">
|
||||
<string notr="true"><html><head/><body><p><a href="https://www.ej-technologies.com/products/jprofiler/overview.html">https://www.ej-technologies.com/products/jprofiler/overview.html</a></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string notr="true">JVisualVM</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="jvisualvmPathEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="jvisualvmPathBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="jvisualvmCheckBtn">
|
||||
<property name="text">
|
||||
<string>Check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="jvisualvmLink">
|
||||
<property name="text">
|
||||
<string notr="true"><html><head/><body><p><a href="https://visualvm.github.io/">https://visualvm.github.io/</a></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string notr="true">MCEdit</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="mceditPathEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mceditPathBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mceditCheckBtn">
|
||||
<property name="text">
|
||||
<string>Check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="mceditLink">
|
||||
<property name="text">
|
||||
<string notr="true"><html><head/><body><p><a href="https://www.mcedit.net/">https://www.mcedit.net/</a></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="editorsBox">
|
||||
<property name="title">
|
||||
<string>External Editors (leave empty for system default)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="foldersBoxLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="jsonEditorTextBox"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelJsonEditor">
|
||||
<property name="text">
|
||||
<string>Text Editor:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="jsonEditorBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>216</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
153
launcher/ui/pages/global/JavaPage.cpp
Normal file
153
launcher/ui/pages/global/JavaPage.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
/* 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 "JavaPage.h"
|
||||
#include "JavaCommon.h"
|
||||
#include "ui_JavaPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
#include <QTabBar>
|
||||
|
||||
#include "ui/dialogs/VersionSelectDialog.h"
|
||||
|
||||
#include "java/JavaUtils.h"
|
||||
#include "java/JavaInstallList.h"
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include <FileSystem.h>
|
||||
#include "Application.h"
|
||||
#include <sys.h>
|
||||
|
||||
JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
|
||||
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
|
||||
ui->maxMemSpinBox->setMaximum(sysMiB);
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
JavaPage::~JavaPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool JavaPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void JavaPage::applySettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
|
||||
// Memory
|
||||
int min = ui->minMemSpinBox->value();
|
||||
int max = ui->maxMemSpinBox->value();
|
||||
if(min < max)
|
||||
{
|
||||
s->set("MinMemAlloc", min);
|
||||
s->set("MaxMemAlloc", max);
|
||||
}
|
||||
else
|
||||
{
|
||||
s->set("MinMemAlloc", max);
|
||||
s->set("MaxMemAlloc", min);
|
||||
}
|
||||
s->set("PermGen", ui->permGenSpinBox->value());
|
||||
|
||||
// Java Settings
|
||||
s->set("JavaPath", ui->javaPathTextBox->text());
|
||||
s->set("JvmArgs", ui->jvmArgsTextBox->text());
|
||||
JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget());
|
||||
}
|
||||
void JavaPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
// Memory
|
||||
int min = s->get("MinMemAlloc").toInt();
|
||||
int max = s->get("MaxMemAlloc").toInt();
|
||||
if(min < max)
|
||||
{
|
||||
ui->minMemSpinBox->setValue(min);
|
||||
ui->maxMemSpinBox->setValue(max);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->minMemSpinBox->setValue(max);
|
||||
ui->maxMemSpinBox->setValue(min);
|
||||
}
|
||||
ui->permGenSpinBox->setValue(s->get("PermGen").toInt());
|
||||
|
||||
// Java Settings
|
||||
ui->javaPathTextBox->setText(s->get("JavaPath").toString());
|
||||
ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString());
|
||||
}
|
||||
|
||||
void JavaPage::on_javaDetectBtn_clicked()
|
||||
{
|
||||
JavaInstallPtr java;
|
||||
|
||||
VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true);
|
||||
vselect.setResizeOn(2);
|
||||
vselect.exec();
|
||||
|
||||
if (vselect.result() == QDialog::Accepted && vselect.selectedVersion())
|
||||
{
|
||||
java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion());
|
||||
ui->javaPathTextBox->setText(java->path);
|
||||
}
|
||||
}
|
||||
|
||||
void JavaPage::on_javaBrowseBtn_clicked()
|
||||
{
|
||||
QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"));
|
||||
|
||||
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
|
||||
if(raw_path.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QString cooked_path = FS::NormalizePath(raw_path);
|
||||
QFileInfo javaInfo(cooked_path);;
|
||||
if(!javaInfo.exists() || !javaInfo.isExecutable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ui->javaPathTextBox->setText(cooked_path);
|
||||
}
|
||||
|
||||
void JavaPage::on_javaTestBtn_clicked()
|
||||
{
|
||||
if(checker)
|
||||
{
|
||||
return;
|
||||
}
|
||||
checker.reset(new JavaCommon::TestCheck(
|
||||
this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->text(),
|
||||
ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value()));
|
||||
connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished()));
|
||||
checker->run();
|
||||
}
|
||||
|
||||
void JavaPage::checkerFinished()
|
||||
{
|
||||
checker.reset();
|
||||
}
|
72
launcher/ui/pages/global/JavaPage.h
Normal file
72
launcher/ui/pages/global/JavaPage.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* 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 <memory>
|
||||
#include <QDialog>
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "JavaCommon.h"
|
||||
#include <Application.h>
|
||||
#include <QObjectPtr.h>
|
||||
|
||||
class SettingsObject;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class JavaPage;
|
||||
}
|
||||
|
||||
class JavaPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit JavaPage(QWidget *parent = 0);
|
||||
~JavaPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Java");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("java");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "java-settings";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Java-settings";
|
||||
}
|
||||
bool apply() override;
|
||||
|
||||
private:
|
||||
void applySettings();
|
||||
void loadSettings();
|
||||
|
||||
private
|
||||
slots:
|
||||
void on_javaDetectBtn_clicked();
|
||||
void on_javaTestBtn_clicked();
|
||||
void on_javaBrowseBtn_clicked();
|
||||
void checkerFinished();
|
||||
|
||||
private:
|
||||
Ui::JavaPage *ui;
|
||||
unique_qobject_ptr<JavaCommon::TestCheck> checker;
|
||||
};
|
260
launcher/ui/pages/global/JavaPage.ui
Normal file
260
launcher/ui/pages/global/JavaPage.ui
Normal file
@ -0,0 +1,260 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>JavaPage</class>
|
||||
<widget class="QWidget" name="JavaPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>545</width>
|
||||
<height>580</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="memoryGroupBox">
|
||||
<property name="title">
|
||||
<string>Memory</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="maxMemSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>The maximum amount of memory Minecraft is allowed to use.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelMinMem">
|
||||
<property name="text">
|
||||
<string>Minimum memory allocation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelMaxMem">
|
||||
<property name="text">
|
||||
<string>Maximum memory allocation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="minMemSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory Minecraft is started with.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelPermGen">
|
||||
<property name="text">
|
||||
<string notr="true">PermGen:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="permGenSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory available to store loaded Java classes.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>64</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999999999</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>64</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="javaSettingsGroupBox">
|
||||
<property name="title">
|
||||
<string>Java Runtime</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelJavaPath">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Java path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="javaPathTextBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="javaBrowseBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>28</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="jvmArgsTextBox"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelJVMArgs">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>JVM arguments:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="javaDetectBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Auto-detect...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="javaTestBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>minMemSpinBox</tabstop>
|
||||
<tabstop>maxMemSpinBox</tabstop>
|
||||
<tabstop>permGenSpinBox</tabstop>
|
||||
<tabstop>javaBrowseBtn</tabstop>
|
||||
<tabstop>javaPathTextBox</tabstop>
|
||||
<tabstop>jvmArgsTextBox</tabstop>
|
||||
<tabstop>javaDetectBtn</tabstop>
|
||||
<tabstop>javaTestBtn</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
51
launcher/ui/pages/global/LanguagePage.cpp
Normal file
51
launcher/ui/pages/global/LanguagePage.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "LanguagePage.h"
|
||||
|
||||
#include "ui/widgets/LanguageSelectionWidget.h"
|
||||
#include <QVBoxLayout>
|
||||
|
||||
LanguagePage::LanguagePage(QWidget* parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
setObjectName(QStringLiteral("languagePage"));
|
||||
auto layout = new QVBoxLayout(this);
|
||||
mainWidget = new LanguageSelectionWidget(this);
|
||||
layout->setContentsMargins(0,0,0,0);
|
||||
layout->addWidget(mainWidget);
|
||||
retranslate();
|
||||
}
|
||||
|
||||
LanguagePage::~LanguagePage()
|
||||
{
|
||||
}
|
||||
|
||||
bool LanguagePage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LanguagePage::applySettings()
|
||||
{
|
||||
auto settings = APPLICATION->settings();
|
||||
QString key = mainWidget->getSelectedLanguageKey();
|
||||
settings->set("Language", key);
|
||||
}
|
||||
|
||||
void LanguagePage::loadSettings()
|
||||
{
|
||||
// NIL
|
||||
}
|
||||
|
||||
void LanguagePage::retranslate()
|
||||
{
|
||||
mainWidget->retranslate();
|
||||
}
|
||||
|
||||
void LanguagePage::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::LanguageChange)
|
||||
{
|
||||
retranslate();
|
||||
}
|
||||
QWidget::changeEvent(event);
|
||||
}
|
60
launcher/ui/pages/global/LanguagePage.h
Normal file
60
launcher/ui/pages/global/LanguagePage.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* 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 <memory>
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include <QWidget>
|
||||
|
||||
class LanguageSelectionWidget;
|
||||
|
||||
class LanguagePage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LanguagePage(QWidget *parent = 0);
|
||||
virtual ~LanguagePage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Language");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("language");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "language-settings";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Language-settings";
|
||||
}
|
||||
bool apply() override;
|
||||
|
||||
void changeEvent(QEvent * ) override;
|
||||
|
||||
private:
|
||||
void applySettings();
|
||||
void loadSettings();
|
||||
void retranslate();
|
||||
|
||||
private:
|
||||
LanguageSelectionWidget *mainWidget;
|
||||
};
|
466
launcher/ui/pages/global/LauncherPage.cpp
Normal file
466
launcher/ui/pages/global/LauncherPage.cpp
Normal file
@ -0,0 +1,466 @@
|
||||
/* 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 "LauncherPage.h"
|
||||
#include "ui_LauncherPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
#include <QTextCharFormat>
|
||||
|
||||
#include "updater/UpdateChecker.h"
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include <FileSystem.h>
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "ui/themes/ITheme.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QProcess>
|
||||
|
||||
// FIXME: possibly move elsewhere
|
||||
enum InstSortMode
|
||||
{
|
||||
// Sort alphabetically by name.
|
||||
Sort_Name,
|
||||
// Sort by which instance was launched most recently.
|
||||
Sort_LastLaunch
|
||||
};
|
||||
|
||||
LauncherPage::LauncherPage(QWidget *parent) : QWidget(parent), ui(new Ui::LauncherPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
auto origForeground = ui->fontPreview->palette().color(ui->fontPreview->foregroundRole());
|
||||
auto origBackground = ui->fontPreview->palette().color(ui->fontPreview->backgroundRole());
|
||||
m_colors.reset(new LogColorCache(origForeground, origBackground));
|
||||
|
||||
ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name);
|
||||
ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch);
|
||||
|
||||
defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat());
|
||||
|
||||
m_languageModel = APPLICATION->translations();
|
||||
loadSettings();
|
||||
|
||||
if(BuildConfig.UPDATER_ENABLED)
|
||||
{
|
||||
QObject::connect(APPLICATION->updateChecker().get(), &UpdateChecker::channelListLoaded, this, &LauncherPage::refreshUpdateChannelList);
|
||||
|
||||
if (APPLICATION->updateChecker()->hasChannels())
|
||||
{
|
||||
refreshUpdateChannelList();
|
||||
}
|
||||
else
|
||||
{
|
||||
APPLICATION->updateChecker()->updateChanList(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->updateSettingsBox->setHidden(true);
|
||||
}
|
||||
// Analytics
|
||||
if(BuildConfig.ANALYTICS_ID.isEmpty())
|
||||
{
|
||||
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->analyticsTab));
|
||||
}
|
||||
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
|
||||
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
|
||||
|
||||
//move mac data button
|
||||
QFile file(QDir::current().absolutePath() + "/dontmovemacdata");
|
||||
if (!file.exists())
|
||||
{
|
||||
ui->migrateDataFolderMacBtn->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
LauncherPage::~LauncherPage()
|
||||
{
|
||||
delete ui;
|
||||
delete defaultFormat;
|
||||
}
|
||||
|
||||
bool LauncherPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LauncherPage::on_instDirBrowseBtn_clicked()
|
||||
{
|
||||
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text());
|
||||
|
||||
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
|
||||
if (!raw_dir.isEmpty() && QDir(raw_dir).exists())
|
||||
{
|
||||
QString cooked_dir = FS::NormalizePath(raw_dir);
|
||||
if (FS::checkProblemticPathJava(QDir(cooked_dir)))
|
||||
{
|
||||
QMessageBox warning;
|
||||
warning.setText(tr("You're trying to specify an instance folder which\'s path "
|
||||
"contains at least one \'!\'. "
|
||||
"Java is known to cause problems if that is the case, your "
|
||||
"instances (probably) won't start!"));
|
||||
warning.setInformativeText(
|
||||
tr("Do you really want to use this path? "
|
||||
"Selecting \"No\" will close this and not alter your instance path."));
|
||||
warning.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
||||
int result = warning.exec();
|
||||
if (result == QMessageBox::Yes)
|
||||
{
|
||||
ui->instDirTextBox->setText(cooked_dir);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->instDirTextBox->setText(cooked_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherPage::on_iconsDirBrowseBtn_clicked()
|
||||
{
|
||||
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text());
|
||||
|
||||
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
|
||||
if (!raw_dir.isEmpty() && QDir(raw_dir).exists())
|
||||
{
|
||||
QString cooked_dir = FS::NormalizePath(raw_dir);
|
||||
ui->iconsDirTextBox->setText(cooked_dir);
|
||||
}
|
||||
}
|
||||
void LauncherPage::on_modsDirBrowseBtn_clicked()
|
||||
{
|
||||
QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text());
|
||||
|
||||
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
|
||||
if (!raw_dir.isEmpty() && QDir(raw_dir).exists())
|
||||
{
|
||||
QString cooked_dir = FS::NormalizePath(raw_dir);
|
||||
ui->modsDirTextBox->setText(cooked_dir);
|
||||
}
|
||||
}
|
||||
void LauncherPage::on_migrateDataFolderMacBtn_clicked()
|
||||
{
|
||||
QFile file(QDir::current().absolutePath() + "/dontmovemacdata");
|
||||
file.remove();
|
||||
QProcess::startDetached(qApp->arguments()[0]);
|
||||
qApp->quit();
|
||||
}
|
||||
|
||||
void LauncherPage::refreshUpdateChannelList()
|
||||
{
|
||||
// Stop listening for selection changes. It's going to change a lot while we update it and
|
||||
// we don't need to update the
|
||||
// description label constantly.
|
||||
QObject::disconnect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(updateChannelSelectionChanged(int)));
|
||||
|
||||
QList<UpdateChecker::ChannelListEntry> channelList = APPLICATION->updateChecker()->getChannelList();
|
||||
ui->updateChannelComboBox->clear();
|
||||
int selection = -1;
|
||||
for (int i = 0; i < channelList.count(); i++)
|
||||
{
|
||||
UpdateChecker::ChannelListEntry entry = channelList.at(i);
|
||||
|
||||
// When it comes to selection, we'll rely on the indexes of a channel entry being the
|
||||
// same in the
|
||||
// combo box as it is in the update checker's channel list.
|
||||
// This probably isn't very safe, but the channel list doesn't change often enough (or
|
||||
// at all) for
|
||||
// this to be a big deal. Hope it doesn't break...
|
||||
ui->updateChannelComboBox->addItem(entry.name);
|
||||
|
||||
// If the update channel we just added was the selected one, set the current index in
|
||||
// the combo box to it.
|
||||
if (entry.id == m_currentUpdateChannel)
|
||||
{
|
||||
qDebug() << "Selected index" << i << "channel id" << m_currentUpdateChannel;
|
||||
selection = i;
|
||||
}
|
||||
}
|
||||
|
||||
ui->updateChannelComboBox->setCurrentIndex(selection);
|
||||
|
||||
// Start listening for selection changes again and update the description label.
|
||||
QObject::connect(ui->updateChannelComboBox, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(updateChannelSelectionChanged(int)));
|
||||
refreshUpdateChannelDesc();
|
||||
|
||||
// Now that we've updated the channel list, we can enable the combo box.
|
||||
// It starts off disabled so that if the channel list hasn't been loaded, it will be
|
||||
// disabled.
|
||||
ui->updateChannelComboBox->setEnabled(true);
|
||||
}
|
||||
|
||||
void LauncherPage::updateChannelSelectionChanged(int index)
|
||||
{
|
||||
refreshUpdateChannelDesc();
|
||||
}
|
||||
|
||||
void LauncherPage::refreshUpdateChannelDesc()
|
||||
{
|
||||
// Get the channel list.
|
||||
QList<UpdateChecker::ChannelListEntry> channelList = APPLICATION->updateChecker()->getChannelList();
|
||||
int selectedIndex = ui->updateChannelComboBox->currentIndex();
|
||||
if (selectedIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (selectedIndex < channelList.count())
|
||||
{
|
||||
// Find the channel list entry with the given index.
|
||||
UpdateChecker::ChannelListEntry selected = channelList.at(selectedIndex);
|
||||
|
||||
// Set the description text.
|
||||
ui->updateChannelDescLabel->setText(selected.description);
|
||||
|
||||
// Set the currently selected channel ID.
|
||||
m_currentUpdateChannel = selected.id;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherPage::applySettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
|
||||
if (ui->resetNotificationsBtn->isChecked())
|
||||
{
|
||||
s->set("ShownNotifications", QString());
|
||||
}
|
||||
|
||||
// Updates
|
||||
s->set("AutoUpdate", ui->autoUpdateCheckBox->isChecked());
|
||||
s->set("UpdateChannel", m_currentUpdateChannel);
|
||||
auto original = s->get("IconTheme").toString();
|
||||
//FIXME: make generic
|
||||
switch (ui->themeComboBox->currentIndex())
|
||||
{
|
||||
case 1:
|
||||
s->set("IconTheme", "pe_dark");
|
||||
break;
|
||||
case 2:
|
||||
s->set("IconTheme", "pe_light");
|
||||
break;
|
||||
case 3:
|
||||
s->set("IconTheme", "pe_blue");
|
||||
break;
|
||||
case 4:
|
||||
s->set("IconTheme", "pe_colored");
|
||||
break;
|
||||
case 5:
|
||||
s->set("IconTheme", "OSX");
|
||||
break;
|
||||
case 6:
|
||||
s->set("IconTheme", "iOS");
|
||||
break;
|
||||
case 7:
|
||||
s->set("IconTheme", "flat");
|
||||
break;
|
||||
case 8:
|
||||
s->set("IconTheme", "custom");
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
s->set("IconTheme", "multimc");
|
||||
break;
|
||||
}
|
||||
|
||||
if(original != s->get("IconTheme"))
|
||||
{
|
||||
APPLICATION->setIconTheme(s->get("IconTheme").toString());
|
||||
}
|
||||
|
||||
auto originalAppTheme = s->get("ApplicationTheme").toString();
|
||||
auto newAppTheme = ui->themeComboBoxColors->currentData().toString();
|
||||
if(originalAppTheme != newAppTheme)
|
||||
{
|
||||
s->set("ApplicationTheme", newAppTheme);
|
||||
APPLICATION->setApplicationTheme(newAppTheme, false);
|
||||
}
|
||||
|
||||
// Console settings
|
||||
s->set("ShowConsole", ui->showConsoleCheck->isChecked());
|
||||
s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked());
|
||||
s->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked());
|
||||
QString consoleFontFamily = ui->consoleFont->currentFont().family();
|
||||
s->set("ConsoleFont", consoleFontFamily);
|
||||
s->set("ConsoleFontSize", ui->fontSizeBox->value());
|
||||
s->set("ConsoleMaxLines", ui->lineLimitSpinBox->value());
|
||||
s->set("ConsoleOverflowStop", ui->checkStopLogging->checkState() != Qt::Unchecked);
|
||||
|
||||
// Folders
|
||||
// TODO: Offer to move instances to new instance folder.
|
||||
s->set("InstanceDir", ui->instDirTextBox->text());
|
||||
s->set("CentralModsDir", ui->modsDirTextBox->text());
|
||||
s->set("IconsDir", ui->iconsDirTextBox->text());
|
||||
|
||||
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
|
||||
switch (sortMode)
|
||||
{
|
||||
case Sort_LastLaunch:
|
||||
s->set("InstSortMode", "LastLaunch");
|
||||
break;
|
||||
case Sort_Name:
|
||||
default:
|
||||
s->set("InstSortMode", "Name");
|
||||
break;
|
||||
}
|
||||
|
||||
// Analytics
|
||||
if(!BuildConfig.ANALYTICS_ID.isEmpty())
|
||||
{
|
||||
s->set("Analytics", ui->analyticsCheck->isChecked());
|
||||
}
|
||||
}
|
||||
void LauncherPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
// Updates
|
||||
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
|
||||
m_currentUpdateChannel = s->get("UpdateChannel").toString();
|
||||
//FIXME: make generic
|
||||
auto theme = s->get("IconTheme").toString();
|
||||
if (theme == "pe_dark")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(1);
|
||||
}
|
||||
else if (theme == "pe_light")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(2);
|
||||
}
|
||||
else if (theme == "pe_blue")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(3);
|
||||
}
|
||||
else if (theme == "pe_colored")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(4);
|
||||
}
|
||||
else if (theme == "OSX")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(5);
|
||||
}
|
||||
else if (theme == "iOS")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(6);
|
||||
}
|
||||
else if (theme == "flat")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(7);
|
||||
}
|
||||
else if (theme == "custom")
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(8);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->themeComboBox->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
{
|
||||
auto currentTheme = s->get("ApplicationTheme").toString();
|
||||
auto themes = APPLICATION->getValidApplicationThemes();
|
||||
int idx = 0;
|
||||
for(auto &theme: themes)
|
||||
{
|
||||
ui->themeComboBoxColors->addItem(theme->name(), theme->id());
|
||||
if(currentTheme == theme->id())
|
||||
{
|
||||
ui->themeComboBoxColors->setCurrentIndex(idx);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
// Console settings
|
||||
ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool());
|
||||
ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool());
|
||||
ui->showConsoleErrorCheck->setChecked(s->get("ShowConsoleOnError").toBool());
|
||||
QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString();
|
||||
QFont consoleFont(fontFamily);
|
||||
ui->consoleFont->setCurrentFont(consoleFont);
|
||||
|
||||
bool conversionOk = true;
|
||||
int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk);
|
||||
if(!conversionOk)
|
||||
{
|
||||
fontSize = 11;
|
||||
}
|
||||
ui->fontSizeBox->setValue(fontSize);
|
||||
refreshFontPreview();
|
||||
ui->lineLimitSpinBox->setValue(s->get("ConsoleMaxLines").toInt());
|
||||
ui->checkStopLogging->setChecked(s->get("ConsoleOverflowStop").toBool());
|
||||
|
||||
// Folders
|
||||
ui->instDirTextBox->setText(s->get("InstanceDir").toString());
|
||||
ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
|
||||
ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
|
||||
|
||||
QString sortMode = s->get("InstSortMode").toString();
|
||||
|
||||
if (sortMode == "LastLaunch")
|
||||
{
|
||||
ui->sortLastLaunchedBtn->setChecked(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->sortByNameBtn->setChecked(true);
|
||||
}
|
||||
|
||||
// Analytics
|
||||
if(!BuildConfig.ANALYTICS_ID.isEmpty())
|
||||
{
|
||||
ui->analyticsCheck->setChecked(s->get("Analytics").toBool());
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherPage::refreshFontPreview()
|
||||
{
|
||||
int fontSize = ui->fontSizeBox->value();
|
||||
QString fontFamily = ui->consoleFont->currentFont().family();
|
||||
ui->fontPreview->clear();
|
||||
defaultFormat->setFont(QFont(fontFamily, fontSize));
|
||||
{
|
||||
QTextCharFormat format(*defaultFormat);
|
||||
format.setForeground(m_colors->getFront(MessageLevel::Error));
|
||||
// append a paragraph/line
|
||||
auto workCursor = ui->fontPreview->textCursor();
|
||||
workCursor.movePosition(QTextCursor::End);
|
||||
workCursor.insertText(tr("[Something/ERROR] A spooky error!"), format);
|
||||
workCursor.insertBlock();
|
||||
}
|
||||
{
|
||||
QTextCharFormat format(*defaultFormat);
|
||||
format.setForeground(m_colors->getFront(MessageLevel::Message));
|
||||
// append a paragraph/line
|
||||
auto workCursor = ui->fontPreview->textCursor();
|
||||
workCursor.movePosition(QTextCursor::End);
|
||||
workCursor.insertText(tr("[Test/INFO] A harmless message..."), format);
|
||||
workCursor.insertBlock();
|
||||
}
|
||||
{
|
||||
QTextCharFormat format(*defaultFormat);
|
||||
format.setForeground(m_colors->getFront(MessageLevel::Warning));
|
||||
// append a paragraph/line
|
||||
auto workCursor = ui->fontPreview->textCursor();
|
||||
workCursor.movePosition(QTextCursor::End);
|
||||
workCursor.insertText(tr("[Something/WARN] A not so spooky warning."), format);
|
||||
workCursor.insertBlock();
|
||||
}
|
||||
}
|
103
launcher/ui/pages/global/LauncherPage.h
Normal file
103
launcher/ui/pages/global/LauncherPage.h
Normal file
@ -0,0 +1,103 @@
|
||||
/* 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 <memory>
|
||||
#include <QDialog>
|
||||
|
||||
#include "java/JavaChecker.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "ui/ColorCache.h"
|
||||
#include <translations/TranslationsModel.h>
|
||||
|
||||
class QTextCharFormat;
|
||||
class SettingsObject;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class LauncherPage;
|
||||
}
|
||||
|
||||
class LauncherPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LauncherPage(QWidget *parent = 0);
|
||||
~LauncherPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return "Launcher";
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("launcher");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "launcher-settings";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Launcher-settings";
|
||||
}
|
||||
bool apply() override;
|
||||
|
||||
private:
|
||||
void applySettings();
|
||||
void loadSettings();
|
||||
|
||||
private
|
||||
slots:
|
||||
void on_instDirBrowseBtn_clicked();
|
||||
void on_modsDirBrowseBtn_clicked();
|
||||
void on_iconsDirBrowseBtn_clicked();
|
||||
void on_migrateDataFolderMacBtn_clicked();
|
||||
|
||||
/*!
|
||||
* Updates the list of update channels in the combo box.
|
||||
*/
|
||||
void refreshUpdateChannelList();
|
||||
|
||||
/*!
|
||||
* Updates the channel description label.
|
||||
*/
|
||||
void refreshUpdateChannelDesc();
|
||||
|
||||
/*!
|
||||
* Updates the font preview
|
||||
*/
|
||||
void refreshFontPreview();
|
||||
|
||||
void updateChannelSelectionChanged(int index);
|
||||
|
||||
private:
|
||||
Ui::LauncherPage *ui;
|
||||
|
||||
/*!
|
||||
* Stores the currently selected update channel.
|
||||
*/
|
||||
QString m_currentUpdateChannel;
|
||||
|
||||
// default format for the font preview...
|
||||
QTextCharFormat *defaultFormat;
|
||||
|
||||
std::unique_ptr<LogColorCache> m_colors;
|
||||
|
||||
std::shared_ptr<TranslationsModel> m_languageModel;
|
||||
};
|
584
launcher/ui/pages/global/LauncherPage.ui
Normal file
584
launcher/ui/pages/global/LauncherPage.ui
Normal file
@ -0,0 +1,584 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LauncherPage</class>
|
||||
<widget class="QWidget" name="LauncherPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>514</width>
|
||||
<height>629</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
<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="toolTip">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="tabShape">
|
||||
<enum>QTabWidget::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="featuresTab">
|
||||
<attribute name="title">
|
||||
<string>Features</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="updateSettingsBox">
|
||||
<property name="title">
|
||||
<string>Update Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoUpdateCheckBox">
|
||||
<property name="text">
|
||||
<string>Check for updates on start?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="updateChannelLabel">
|
||||
<property name="text">
|
||||
<string>Up&date Channel:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>updateChannelComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="updateChannelComboBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="updateChannelDescLabel">
|
||||
<property name="text">
|
||||
<string>No channel selected.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="foldersBox">
|
||||
<property name="title">
|
||||
<string>Folders</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="foldersBoxLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelInstDir">
|
||||
<property name="text">
|
||||
<string>I&nstances:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>instDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="instDirTextBox"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="instDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelModsDir">
|
||||
<property name="text">
|
||||
<string>&Mods:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>modsDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="modsDirTextBox"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="modsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="iconsDirTextBox"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelIconsDir">
|
||||
<property name="text">
|
||||
<string>&Icons:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>iconsDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="iconsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="migrateDataFolderMacBtn">
|
||||
<property name="text">
|
||||
<string>Move the data to new location (will restart the launcher)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="generalTab">
|
||||
<attribute name="title">
|
||||
<string>User Interface</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Launcher notifications</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QPushButton" name="resetNotificationsBtn">
|
||||
<property name="text">
|
||||
<string>Reset hidden notifications</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="sortingModeBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Instance view sorting mode</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="sortingModeBoxLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortLastLaunchedBtn">
|
||||
<property name="text">
|
||||
<string>By &last launched</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">sortingModeGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByNameBtn">
|
||||
<property name="text">
|
||||
<string>By &name</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">sortingModeGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="themeBox">
|
||||
<property name="title">
|
||||
<string>Theme</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>&Icons</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>themeComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="themeComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simple (Dark Icons)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simple (Light Icons)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simple (Blue Icons)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Simple (Colored Icons)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">OSX</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">iOS</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Flat</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Custom</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="themeComboBoxColors">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Colors</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>themeComboBoxColors</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="generalTabSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="consoleTab">
|
||||
<attribute name="title">
|
||||
<string>Console</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="consoleSettingsBox">
|
||||
<property name="title">
|
||||
<string>Console Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showConsoleCheck">
|
||||
<property name="text">
|
||||
<string>Show console while the game is running?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoCloseConsoleCheck">
|
||||
<property name="text">
|
||||
<string>Automatically close console when the game quits?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showConsoleErrorCheck">
|
||||
<property name="text">
|
||||
<string>Show console when the game crashes?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>History limit</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="checkStopLogging">
|
||||
<property name="text">
|
||||
<string>Stop logging when log overflows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QSpinBox" name="lineLimitSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> lines</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="themeBox_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Console font</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QTextEdit" name="fontPreview">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="undoRedoEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QFontComboBox" name="consoleFont">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="fontSizeBox">
|
||||
<property name="minimum">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>11</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="analyticsTab">
|
||||
<attribute name="title">
|
||||
<string>Analytics</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="consoleSettingsBox_2">
|
||||
<property name="title">
|
||||
<string>Analytics Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="analyticsCheck">
|
||||
<property name="text">
|
||||
<string>Send anonymous usage statistics?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><html><head/>
|
||||
<body>
|
||||
<p>The launcher sends anonymous usage statistics on every start of the application.</p><p>The following data is collected:</p>
|
||||
<ul>
|
||||
<li>Launcher version.</li>
|
||||
<li>Operating system name, version and architecture.</li>
|
||||
<li>CPU architecture (kernel architecture on linux).</li>
|
||||
<li>Size of system memory.</li>
|
||||
<li>Java version, architecture and memory settings.</li>
|
||||
</ul>
|
||||
</body></html></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>autoUpdateCheckBox</tabstop>
|
||||
<tabstop>updateChannelComboBox</tabstop>
|
||||
<tabstop>instDirTextBox</tabstop>
|
||||
<tabstop>instDirBrowseBtn</tabstop>
|
||||
<tabstop>modsDirTextBox</tabstop>
|
||||
<tabstop>modsDirBrowseBtn</tabstop>
|
||||
<tabstop>iconsDirTextBox</tabstop>
|
||||
<tabstop>iconsDirBrowseBtn</tabstop>
|
||||
<tabstop>resetNotificationsBtn</tabstop>
|
||||
<tabstop>sortLastLaunchedBtn</tabstop>
|
||||
<tabstop>sortByNameBtn</tabstop>
|
||||
<tabstop>themeComboBox</tabstop>
|
||||
<tabstop>themeComboBoxColors</tabstop>
|
||||
<tabstop>showConsoleCheck</tabstop>
|
||||
<tabstop>autoCloseConsoleCheck</tabstop>
|
||||
<tabstop>showConsoleErrorCheck</tabstop>
|
||||
<tabstop>lineLimitSpinBox</tabstop>
|
||||
<tabstop>checkStopLogging</tabstop>
|
||||
<tabstop>consoleFont</tabstop>
|
||||
<tabstop>fontSizeBox</tabstop>
|
||||
<tabstop>fontPreview</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="sortingModeGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
91
launcher/ui/pages/global/MinecraftPage.cpp
Normal file
91
launcher/ui/pages/global/MinecraftPage.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/* 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 "MinecraftPage.h"
|
||||
#include "ui_MinecraftPage.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
#include <QTabBar>
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "Application.h"
|
||||
|
||||
MinecraftPage::MinecraftPage(QWidget *parent) : QWidget(parent), ui(new Ui::MinecraftPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
loadSettings();
|
||||
updateCheckboxStuff();
|
||||
}
|
||||
|
||||
MinecraftPage::~MinecraftPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool MinecraftPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MinecraftPage::updateCheckboxStuff()
|
||||
{
|
||||
ui->windowWidthSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked());
|
||||
ui->windowHeightSpinBox->setEnabled(!ui->maximizedCheckBox->isChecked());
|
||||
}
|
||||
|
||||
void MinecraftPage::on_maximizedCheckBox_clicked(bool checked)
|
||||
{
|
||||
Q_UNUSED(checked);
|
||||
updateCheckboxStuff();
|
||||
}
|
||||
|
||||
void MinecraftPage::applySettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
|
||||
// Window Size
|
||||
s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked());
|
||||
s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
|
||||
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
|
||||
|
||||
// Native library workarounds
|
||||
s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
|
||||
s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
|
||||
|
||||
// Game time
|
||||
s->set("ShowGameTime", ui->showGameTime->isChecked());
|
||||
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
|
||||
s->set("RecordGameTime", ui->recordGameTime->isChecked());
|
||||
}
|
||||
|
||||
void MinecraftPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
|
||||
// Window Size
|
||||
ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool());
|
||||
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
|
||||
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
|
||||
|
||||
ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
|
||||
ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool());
|
||||
|
||||
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
|
||||
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
|
||||
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
|
||||
}
|
70
launcher/ui/pages/global/MinecraftPage.h
Normal file
70
launcher/ui/pages/global/MinecraftPage.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 <memory>
|
||||
#include <QDialog>
|
||||
|
||||
#include "java/JavaChecker.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
class SettingsObject;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class MinecraftPage;
|
||||
}
|
||||
|
||||
class MinecraftPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MinecraftPage(QWidget *parent = 0);
|
||||
~MinecraftPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Minecraft");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("minecraft");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "minecraft-settings";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Minecraft-settings";
|
||||
}
|
||||
bool apply() override;
|
||||
|
||||
private:
|
||||
void updateCheckboxStuff();
|
||||
void applySettings();
|
||||
void loadSettings();
|
||||
|
||||
private
|
||||
slots:
|
||||
void on_maximizedCheckBox_clicked(bool checked);
|
||||
|
||||
private:
|
||||
Ui::MinecraftPage *ui;
|
||||
|
||||
};
|
196
launcher/ui/pages/global/MinecraftPage.ui
Normal file
196
launcher/ui/pages/global/MinecraftPage.ui
Normal file
@ -0,0 +1,196 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MinecraftPage</class>
|
||||
<widget class="QWidget" name="MinecraftPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>936</width>
|
||||
<height>1134</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
<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="tabShape">
|
||||
<enum>QTabWidget::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="minecraftTab">
|
||||
<attribute name="title">
|
||||
<string notr="true">Minecraft</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="windowSizeGroupBox">
|
||||
<property name="title">
|
||||
<string>Window Size</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="maximizedCheckBox">
|
||||
<property name="text">
|
||||
<string>Start Minecraft maximized?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayoutWindowSize">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelWindowHeight">
|
||||
<property name="text">
|
||||
<string>Window hei&ght:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>windowHeightSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelWindowWidth">
|
||||
<property name="text">
|
||||
<string>W&indow width:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>windowWidthSpinBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="windowWidthSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>854</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="windowHeightSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>480</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
|
||||
<property name="title">
|
||||
<string>Native library workarounds</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useNativeGLFWCheck">
|
||||
<property name="text">
|
||||
<string>Use system installation of GLFW</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useNativeOpenALCheck">
|
||||
<property name="text">
|
||||
<string>Use system installation of OpenAL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameTimeGroupBox">
|
||||
<property name="title">
|
||||
<string>Game time</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showGameTime">
|
||||
<property name="text">
|
||||
<string>Show time spent playing instances</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showGlobalGameTime">
|
||||
<property name="text">
|
||||
<string>Show time spent playing across all instances</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="recordGameTime">
|
||||
<property name="text">
|
||||
<string>Record time spent playing instances</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacerMinecraft">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>maximizedCheckBox</tabstop>
|
||||
<tabstop>windowWidthSpinBox</tabstop>
|
||||
<tabstop>windowHeightSpinBox</tabstop>
|
||||
<tabstop>useNativeGLFWCheck</tabstop>
|
||||
<tabstop>useNativeOpenALCheck</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
81
launcher/ui/pages/global/PasteEEPage.cpp
Normal file
81
launcher/ui/pages/global/PasteEEPage.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
/* 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 "PasteEEPage.h"
|
||||
#include "ui_PasteEEPage.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <QTabBar>
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "tools/BaseProfiler.h"
|
||||
#include "Application.h"
|
||||
|
||||
PasteEEPage::PasteEEPage(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::PasteEEPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();\
|
||||
connect(ui->customAPIkeyEdit, &QLineEdit::textEdited, this, &PasteEEPage::textEdited);
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
PasteEEPage::~PasteEEPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void PasteEEPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
QString keyToUse = s->get("PasteEEAPIKey").toString();
|
||||
if(keyToUse == "multimc")
|
||||
{
|
||||
ui->multimcButton->setChecked(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->customButton->setChecked(true);
|
||||
ui->customAPIkeyEdit->setText(keyToUse);
|
||||
}
|
||||
}
|
||||
|
||||
void PasteEEPage::applySettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
|
||||
QString pasteKeyToUse;
|
||||
if (ui->customButton->isChecked())
|
||||
pasteKeyToUse = ui->customAPIkeyEdit->text();
|
||||
else
|
||||
{
|
||||
pasteKeyToUse = "multimc";
|
||||
}
|
||||
s->set("PasteEEAPIKey", pasteKeyToUse);
|
||||
}
|
||||
|
||||
bool PasteEEPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PasteEEPage::textEdited(const QString& text)
|
||||
{
|
||||
ui->customButton->setChecked(true);
|
||||
}
|
62
launcher/ui/pages/global/PasteEEPage.h
Normal file
62
launcher/ui/pages/global/PasteEEPage.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* 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>
|
||||
|
||||
namespace Ui {
|
||||
class PasteEEPage;
|
||||
}
|
||||
|
||||
class PasteEEPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PasteEEPage(QWidget *parent = 0);
|
||||
~PasteEEPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Log Upload");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("log");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "log-upload";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Log-Upload";
|
||||
}
|
||||
virtual bool apply() override;
|
||||
|
||||
private:
|
||||
void loadSettings();
|
||||
void applySettings();
|
||||
|
||||
private slots:
|
||||
void textEdited(const QString &text);
|
||||
|
||||
private:
|
||||
Ui::PasteEEPage *ui;
|
||||
};
|
128
launcher/ui/pages/global/PasteEEPage.ui
Normal file
128
launcher/ui/pages/global/PasteEEPage.ui
Normal file
@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PasteEEPage</class>
|
||||
<widget class="QWidget" name="PasteEEPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>491</width>
|
||||
<height>474</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>paste.ee API key</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="multimcButton">
|
||||
<property name="text">
|
||||
<string>MultiMC key - 12MB &upload limit</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">pasteButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="customButton">
|
||||
<property name="text">
|
||||
<string>&Your own key - 12MB upload limit:</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">pasteButtonGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="customAPIkeyEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Paste your API key here!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><a href="https://paste.ee">paste.ee</a> is used by MultiMC for log uploads. If you have a <a href="https://paste.ee">paste.ee</a> account, you can add your API key here and have your uploaded logs paired with your account.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>216</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>multimcButton</tabstop>
|
||||
<tabstop>customButton</tabstop>
|
||||
<tabstop>customAPIkeyEdit</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="pasteButtonGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
106
launcher/ui/pages/global/ProxyPage.cpp
Normal file
106
launcher/ui/pages/global/ProxyPage.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
/* 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 "ProxyPage.h"
|
||||
#include "ui_ProxyPage.h"
|
||||
|
||||
#include <QTabBar>
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "Application.h"
|
||||
#include "Application.h"
|
||||
|
||||
ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
loadSettings();
|
||||
updateCheckboxStuff();
|
||||
|
||||
connect(ui->proxyGroup, SIGNAL(buttonClicked(int)), SLOT(proxyChanged(int)));
|
||||
}
|
||||
|
||||
ProxyPage::~ProxyPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool ProxyPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxyPage::updateCheckboxStuff()
|
||||
{
|
||||
ui->proxyAddrBox->setEnabled(!ui->proxyNoneBtn->isChecked() &&
|
||||
!ui->proxyDefaultBtn->isChecked());
|
||||
ui->proxyAuthBox->setEnabled(!ui->proxyNoneBtn->isChecked() &&
|
||||
!ui->proxyDefaultBtn->isChecked());
|
||||
}
|
||||
|
||||
void ProxyPage::proxyChanged(int)
|
||||
{
|
||||
updateCheckboxStuff();
|
||||
}
|
||||
|
||||
void ProxyPage::applySettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
|
||||
// Proxy
|
||||
QString proxyType = "None";
|
||||
if (ui->proxyDefaultBtn->isChecked())
|
||||
proxyType = "Default";
|
||||
else if (ui->proxyNoneBtn->isChecked())
|
||||
proxyType = "None";
|
||||
else if (ui->proxySOCKS5Btn->isChecked())
|
||||
proxyType = "SOCKS5";
|
||||
else if (ui->proxyHTTPBtn->isChecked())
|
||||
proxyType = "HTTP";
|
||||
|
||||
s->set("ProxyType", proxyType);
|
||||
s->set("ProxyAddr", ui->proxyAddrEdit->text());
|
||||
s->set("ProxyPort", ui->proxyPortEdit->value());
|
||||
s->set("ProxyUser", ui->proxyUserEdit->text());
|
||||
s->set("ProxyPass", ui->proxyPassEdit->text());
|
||||
|
||||
APPLICATION->updateProxySettings(
|
||||
proxyType,
|
||||
ui->proxyAddrEdit->text(),
|
||||
ui->proxyPortEdit->value(),
|
||||
ui->proxyUserEdit->text(),
|
||||
ui->proxyPassEdit->text()
|
||||
);
|
||||
}
|
||||
void ProxyPage::loadSettings()
|
||||
{
|
||||
auto s = APPLICATION->settings();
|
||||
// Proxy
|
||||
QString proxyType = s->get("ProxyType").toString();
|
||||
if (proxyType == "Default")
|
||||
ui->proxyDefaultBtn->setChecked(true);
|
||||
else if (proxyType == "None")
|
||||
ui->proxyNoneBtn->setChecked(true);
|
||||
else if (proxyType == "SOCKS5")
|
||||
ui->proxySOCKS5Btn->setChecked(true);
|
||||
else if (proxyType == "HTTP")
|
||||
ui->proxyHTTPBtn->setChecked(true);
|
||||
|
||||
ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString());
|
||||
ui->proxyPortEdit->setValue(s->get("ProxyPort").value<uint16_t>());
|
||||
ui->proxyUserEdit->setText(s->get("ProxyUser").toString());
|
||||
ui->proxyPassEdit->setText(s->get("ProxyPass").toString());
|
||||
}
|
66
launcher/ui/pages/global/ProxyPage.h
Normal file
66
launcher/ui/pages/global/ProxyPage.h
Normal file
@ -0,0 +1,66 @@
|
||||
/* 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 <memory>
|
||||
#include <QDialog>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ProxyPage;
|
||||
}
|
||||
|
||||
class ProxyPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ProxyPage(QWidget *parent = 0);
|
||||
~ProxyPage();
|
||||
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Proxy");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("proxy");
|
||||
}
|
||||
QString id() const override
|
||||
{
|
||||
return "proxy-settings";
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Proxy-settings";
|
||||
}
|
||||
bool apply() override;
|
||||
|
||||
private:
|
||||
void updateCheckboxStuff();
|
||||
void applySettings();
|
||||
void loadSettings();
|
||||
|
||||
private
|
||||
slots:
|
||||
void proxyChanged(int);
|
||||
|
||||
private:
|
||||
Ui::ProxyPage *ui;
|
||||
};
|
203
launcher/ui/pages/global/ProxyPage.ui
Normal file
203
launcher/ui/pages/global/ProxyPage.ui
Normal file
@ -0,0 +1,203 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ProxyPage</class>
|
||||
<widget class="QWidget" name="ProxyPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>598</width>
|
||||
<height>617</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<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">
|
||||
<widget class="QWidget" name="tabWidgetPage1">
|
||||
<attribute name="title">
|
||||
<string notr="true"/>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="proxyPlainTextWarningLabel_2">
|
||||
<property name="text">
|
||||
<string>This only applies to the launcher. Minecraft does not accept proxy settings.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="proxyTypeBox">
|
||||
<property name="title">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="proxyDefaultBtn">
|
||||
<property name="toolTip">
|
||||
<string>Uses your system's default proxy settings.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Default</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">proxyGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="proxyNoneBtn">
|
||||
<property name="text">
|
||||
<string>&None</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">proxyGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="proxySOCKS5Btn">
|
||||
<property name="text">
|
||||
<string>SOC&KS5</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">proxyGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="proxyHTTPBtn">
|
||||
<property name="text">
|
||||
<string>H&TTP</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">proxyGroup</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="proxyAddrBox">
|
||||
<property name="title">
|
||||
<string>Address and Port</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="proxyAddrEdit">
|
||||
<property name="placeholderText">
|
||||
<string notr="true">127.0.0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="proxyPortEdit">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::PlusMinus</enum>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8080</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="proxyAuthBox">
|
||||
<property name="title">
|
||||
<string>Authentication</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="proxyUserEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="proxyUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="proxyPasswordLabel">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="proxyPassEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="proxyPlainTextWarningLabel">
|
||||
<property name="text">
|
||||
<string>Note: Proxy username and password are stored in plain text inside the launcher's configuration file!</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<buttongroups>
|
||||
<buttongroup name="proxyGroup"/>
|
||||
</buttongroups>
|
||||
</ui>
|
37
launcher/ui/pages/instance/GameOptionsPage.cpp
Normal file
37
launcher/ui/pages/instance/GameOptionsPage.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "GameOptionsPage.h"
|
||||
#include "ui_GameOptionsPage.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/gameoptions/GameOptions.h"
|
||||
|
||||
GameOptionsPage::GameOptionsPage(MinecraftInstance * inst, QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::GameOptionsPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
m_model = inst->gameOptionsModel();
|
||||
ui->optionsView->setModel(m_model.get());
|
||||
auto head = ui->optionsView->header();
|
||||
if(head->count())
|
||||
{
|
||||
head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
for(int i = 1; i < head->count(); i++)
|
||||
{
|
||||
head->setSectionResizeMode(i, QHeaderView::Stretch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GameOptionsPage::~GameOptionsPage()
|
||||
{
|
||||
// m_model->save();
|
||||
}
|
||||
|
||||
void GameOptionsPage::openedImpl()
|
||||
{
|
||||
// m_model->observe();
|
||||
}
|
||||
|
||||
void GameOptionsPage::closedImpl()
|
||||
{
|
||||
// m_model->unobserve();
|
||||
}
|
63
launcher/ui/pages/instance/GameOptionsPage.h
Normal file
63
launcher/ui/pages/instance/GameOptionsPage.h
Normal file
@ -0,0 +1,63 @@
|
||||
/* 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 <QString>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class GameOptionsPage;
|
||||
}
|
||||
|
||||
class GameOptions;
|
||||
class MinecraftInstance;
|
||||
|
||||
class GameOptionsPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GameOptionsPage(MinecraftInstance *inst, QWidget *parent = 0);
|
||||
virtual ~GameOptionsPage();
|
||||
|
||||
void openedImpl() override;
|
||||
void closedImpl() override;
|
||||
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Game Options");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("settings");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "gameoptions";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Game-Options-management";
|
||||
}
|
||||
|
||||
private: // data
|
||||
Ui::GameOptionsPage *ui = nullptr;
|
||||
std::shared_ptr<GameOptions> m_model;
|
||||
};
|
88
launcher/ui/pages/instance/GameOptionsPage.ui
Normal file
88
launcher/ui/pages/instance/GameOptionsPage.ui
Normal file
@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GameOptionsPage</class>
|
||||
<widget class="QWidget" name="GameOptionsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>706</width>
|
||||
<height>575</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<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 row="0" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<attribute name="title">
|
||||
<string notr="true">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QTreeView" name="optionsView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>optionsView</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
341
launcher/ui/pages/instance/InstanceSettingsPage.cpp
Normal file
341
launcher/ui/pages/instance/InstanceSettingsPage.cpp
Normal file
@ -0,0 +1,341 @@
|
||||
#include "InstanceSettingsPage.h"
|
||||
#include "ui_InstanceSettingsPage.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QDialog>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <sys.h>
|
||||
|
||||
#include "ui/dialogs/VersionSelectDialog.h"
|
||||
#include "ui/widgets/CustomCommands.h"
|
||||
|
||||
#include "JavaCommon.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "java/JavaInstallList.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
|
||||
InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst)
|
||||
{
|
||||
m_settings = inst->settings();
|
||||
ui->setupUi(this);
|
||||
auto sysMB = Sys::getSystemRam() / Sys::mebibyte;
|
||||
ui->maxMemSpinBox->setMaximum(sysMB);
|
||||
connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked);
|
||||
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings);
|
||||
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
bool InstanceSettingsPage::shouldDisplay() const
|
||||
{
|
||||
return !m_instance->isRunning();
|
||||
}
|
||||
|
||||
InstanceSettingsPage::~InstanceSettingsPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::globalSettingsButtonClicked(bool)
|
||||
{
|
||||
switch(ui->settingsTabs->currentIndex()) {
|
||||
case 0:
|
||||
APPLICATION->ShowGlobalSettings(this, "java-settings");
|
||||
return;
|
||||
case 1:
|
||||
APPLICATION->ShowGlobalSettings(this, "minecraft-settings");
|
||||
return;
|
||||
case 2:
|
||||
APPLICATION->ShowGlobalSettings(this, "custom-commands");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool InstanceSettingsPage::apply()
|
||||
{
|
||||
applySettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::applySettings()
|
||||
{
|
||||
SettingsObject::Lock lock(m_settings);
|
||||
|
||||
// Console
|
||||
bool console = ui->consoleSettingsBox->isChecked();
|
||||
m_settings->set("OverrideConsole", console);
|
||||
if (console)
|
||||
{
|
||||
m_settings->set("ShowConsole", ui->showConsoleCheck->isChecked());
|
||||
m_settings->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked());
|
||||
m_settings->set("ShowConsoleOnError", ui->showConsoleErrorCheck->isChecked());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("ShowConsole");
|
||||
m_settings->reset("AutoCloseConsole");
|
||||
m_settings->reset("ShowConsoleOnError");
|
||||
}
|
||||
|
||||
// Window Size
|
||||
bool window = ui->windowSizeGroupBox->isChecked();
|
||||
m_settings->set("OverrideWindow", window);
|
||||
if (window)
|
||||
{
|
||||
m_settings->set("LaunchMaximized", ui->maximizedCheckBox->isChecked());
|
||||
m_settings->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
|
||||
m_settings->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("LaunchMaximized");
|
||||
m_settings->reset("MinecraftWinWidth");
|
||||
m_settings->reset("MinecraftWinHeight");
|
||||
}
|
||||
|
||||
// Memory
|
||||
bool memory = ui->memoryGroupBox->isChecked();
|
||||
m_settings->set("OverrideMemory", memory);
|
||||
if (memory)
|
||||
{
|
||||
int min = ui->minMemSpinBox->value();
|
||||
int max = ui->maxMemSpinBox->value();
|
||||
if(min < max)
|
||||
{
|
||||
m_settings->set("MinMemAlloc", min);
|
||||
m_settings->set("MaxMemAlloc", max);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->set("MinMemAlloc", max);
|
||||
m_settings->set("MaxMemAlloc", min);
|
||||
}
|
||||
m_settings->set("PermGen", ui->permGenSpinBox->value());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("MinMemAlloc");
|
||||
m_settings->reset("MaxMemAlloc");
|
||||
m_settings->reset("PermGen");
|
||||
}
|
||||
|
||||
// Java Install Settings
|
||||
bool javaInstall = ui->javaSettingsGroupBox->isChecked();
|
||||
m_settings->set("OverrideJavaLocation", javaInstall);
|
||||
if (javaInstall)
|
||||
{
|
||||
m_settings->set("JavaPath", ui->javaPathTextBox->text());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("JavaPath");
|
||||
}
|
||||
|
||||
// Java arguments
|
||||
bool javaArgs = ui->javaArgumentsGroupBox->isChecked();
|
||||
m_settings->set("OverrideJavaArgs", javaArgs);
|
||||
if(javaArgs)
|
||||
{
|
||||
m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " "));
|
||||
JavaCommon::checkJVMArgs(m_settings->get("JvmArgs").toString(), this->parentWidget());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("JvmArgs");
|
||||
}
|
||||
|
||||
// old generic 'override both' is removed.
|
||||
m_settings->reset("OverrideJava");
|
||||
|
||||
// Custom Commands
|
||||
bool custcmd = ui->customCommands->checked();
|
||||
m_settings->set("OverrideCommands", custcmd);
|
||||
if (custcmd)
|
||||
{
|
||||
m_settings->set("PreLaunchCommand", ui->customCommands->prelaunchCommand());
|
||||
m_settings->set("WrapperCommand", ui->customCommands->wrapperCommand());
|
||||
m_settings->set("PostExitCommand", ui->customCommands->postexitCommand());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("PreLaunchCommand");
|
||||
m_settings->reset("WrapperCommand");
|
||||
m_settings->reset("PostExitCommand");
|
||||
}
|
||||
|
||||
// Workarounds
|
||||
bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();
|
||||
m_settings->set("OverrideNativeWorkarounds", workarounds);
|
||||
if(workarounds)
|
||||
{
|
||||
m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
|
||||
m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("UseNativeOpenAL");
|
||||
m_settings->reset("UseNativeGLFW");
|
||||
}
|
||||
|
||||
// Game time
|
||||
bool gameTime = ui->gameTimeGroupBox->isChecked();
|
||||
m_settings->set("OverrideGameTime", gameTime);
|
||||
if (gameTime)
|
||||
{
|
||||
m_settings->set("ShowGameTime", ui->showGameTime->isChecked());
|
||||
m_settings->set("RecordGameTime", ui->recordGameTime->isChecked());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("ShowGameTime");
|
||||
m_settings->reset("RecordGameTime");
|
||||
}
|
||||
|
||||
// Join server on launch
|
||||
bool joinServerOnLaunch = ui->serverJoinGroupBox->isChecked();
|
||||
m_settings->set("JoinServerOnLaunch", joinServerOnLaunch);
|
||||
if (joinServerOnLaunch)
|
||||
{
|
||||
m_settings->set("JoinServerOnLaunchAddress", ui->serverJoinAddress->text());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->reset("JoinServerOnLaunchAddress");
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::loadSettings()
|
||||
{
|
||||
// Console
|
||||
ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool());
|
||||
ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool());
|
||||
ui->autoCloseConsoleCheck->setChecked(m_settings->get("AutoCloseConsole").toBool());
|
||||
ui->showConsoleErrorCheck->setChecked(m_settings->get("ShowConsoleOnError").toBool());
|
||||
|
||||
// Window Size
|
||||
ui->windowSizeGroupBox->setChecked(m_settings->get("OverrideWindow").toBool());
|
||||
ui->maximizedCheckBox->setChecked(m_settings->get("LaunchMaximized").toBool());
|
||||
ui->windowWidthSpinBox->setValue(m_settings->get("MinecraftWinWidth").toInt());
|
||||
ui->windowHeightSpinBox->setValue(m_settings->get("MinecraftWinHeight").toInt());
|
||||
|
||||
// Memory
|
||||
ui->memoryGroupBox->setChecked(m_settings->get("OverrideMemory").toBool());
|
||||
int min = m_settings->get("MinMemAlloc").toInt();
|
||||
int max = m_settings->get("MaxMemAlloc").toInt();
|
||||
if(min < max)
|
||||
{
|
||||
ui->minMemSpinBox->setValue(min);
|
||||
ui->maxMemSpinBox->setValue(max);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->minMemSpinBox->setValue(max);
|
||||
ui->maxMemSpinBox->setValue(min);
|
||||
}
|
||||
ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt());
|
||||
bool permGenVisible = m_settings->get("PermGenVisible").toBool();
|
||||
ui->permGenSpinBox->setVisible(permGenVisible);
|
||||
ui->labelPermGen->setVisible(permGenVisible);
|
||||
ui->labelPermgenNote->setVisible(permGenVisible);
|
||||
|
||||
|
||||
// Java Settings
|
||||
bool overrideJava = m_settings->get("OverrideJava").toBool();
|
||||
bool overrideLocation = m_settings->get("OverrideJavaLocation").toBool() || overrideJava;
|
||||
bool overrideArgs = m_settings->get("OverrideJavaArgs").toBool() || overrideJava;
|
||||
|
||||
ui->javaSettingsGroupBox->setChecked(overrideLocation);
|
||||
ui->javaPathTextBox->setText(m_settings->get("JavaPath").toString());
|
||||
|
||||
ui->javaArgumentsGroupBox->setChecked(overrideArgs);
|
||||
ui->jvmArgsTextBox->setPlainText(m_settings->get("JvmArgs").toString());
|
||||
|
||||
// Custom commands
|
||||
ui->customCommands->initialize(
|
||||
true,
|
||||
m_settings->get("OverrideCommands").toBool(),
|
||||
m_settings->get("PreLaunchCommand").toString(),
|
||||
m_settings->get("WrapperCommand").toString(),
|
||||
m_settings->get("PostExitCommand").toString()
|
||||
);
|
||||
|
||||
// Workarounds
|
||||
ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());
|
||||
ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());
|
||||
ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool());
|
||||
|
||||
// Miscellanous
|
||||
ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool());
|
||||
ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool());
|
||||
ui->recordGameTime->setChecked(m_settings->get("RecordGameTime").toBool());
|
||||
|
||||
ui->serverJoinGroupBox->setChecked(m_settings->get("JoinServerOnLaunch").toBool());
|
||||
ui->serverJoinAddress->setText(m_settings->get("JoinServerOnLaunchAddress").toString());
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::on_javaDetectBtn_clicked()
|
||||
{
|
||||
JavaInstallPtr java;
|
||||
|
||||
VersionSelectDialog vselect(APPLICATION->javalist().get(), tr("Select a Java version"), this, true);
|
||||
vselect.setResizeOn(2);
|
||||
vselect.exec();
|
||||
|
||||
if (vselect.result() == QDialog::Accepted && vselect.selectedVersion())
|
||||
{
|
||||
java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion());
|
||||
ui->javaPathTextBox->setText(java->path);
|
||||
bool visible = java->id.requiresPermGen() && m_settings->get("OverrideMemory").toBool();
|
||||
ui->permGenSpinBox->setVisible(visible);
|
||||
ui->labelPermGen->setVisible(visible);
|
||||
ui->labelPermgenNote->setVisible(visible);
|
||||
m_settings->set("PermGenVisible", visible);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::on_javaBrowseBtn_clicked()
|
||||
{
|
||||
QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"));
|
||||
|
||||
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
|
||||
if(raw_path.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
QString cooked_path = FS::NormalizePath(raw_path);
|
||||
|
||||
QFileInfo javaInfo(cooked_path);
|
||||
if(!javaInfo.exists() || !javaInfo.isExecutable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ui->javaPathTextBox->setText(cooked_path);
|
||||
|
||||
// custom Java could be anything... enable perm gen option
|
||||
ui->permGenSpinBox->setVisible(true);
|
||||
ui->labelPermGen->setVisible(true);
|
||||
ui->labelPermgenNote->setVisible(true);
|
||||
m_settings->set("PermGenVisible", true);
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::on_javaTestBtn_clicked()
|
||||
{
|
||||
if(checker)
|
||||
{
|
||||
return;
|
||||
}
|
||||
checker.reset(new JavaCommon::TestCheck(
|
||||
this, ui->javaPathTextBox->text(), ui->jvmArgsTextBox->toPlainText().replace("\n", " "),
|
||||
ui->minMemSpinBox->value(), ui->maxMemSpinBox->value(), ui->permGenSpinBox->value()));
|
||||
connect(checker.get(), SIGNAL(finished()), SLOT(checkerFinished()));
|
||||
checker->run();
|
||||
}
|
||||
|
||||
void InstanceSettingsPage::checkerFinished()
|
||||
{
|
||||
checker.reset();
|
||||
}
|
76
launcher/ui/pages/instance/InstanceSettingsPage.h
Normal file
76
launcher/ui/pages/instance/InstanceSettingsPage.h
Normal file
@ -0,0 +1,76 @@
|
||||
/* 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 "java/JavaChecker.h"
|
||||
#include "BaseInstance.h"
|
||||
#include <QObjectPtr.h>
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include "JavaCommon.h"
|
||||
#include "Application.h"
|
||||
|
||||
class JavaChecker;
|
||||
namespace Ui
|
||||
{
|
||||
class InstanceSettingsPage;
|
||||
}
|
||||
|
||||
class InstanceSettingsPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InstanceSettingsPage(BaseInstance *inst, QWidget *parent = 0);
|
||||
virtual ~InstanceSettingsPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Settings");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("instance-settings");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "settings";
|
||||
}
|
||||
virtual bool apply() override;
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Instance-settings";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
private slots:
|
||||
void on_javaDetectBtn_clicked();
|
||||
void on_javaTestBtn_clicked();
|
||||
void on_javaBrowseBtn_clicked();
|
||||
|
||||
void applySettings();
|
||||
void loadSettings();
|
||||
|
||||
void checkerFinished();
|
||||
|
||||
void globalSettingsButtonClicked(bool checked);
|
||||
|
||||
private:
|
||||
Ui::InstanceSettingsPage *ui;
|
||||
BaseInstance *m_instance;
|
||||
SettingsObjectPtr m_settings;
|
||||
unique_qobject_ptr<JavaCommon::TestCheck> checker;
|
||||
};
|
548
launcher/ui/pages/instance/InstanceSettingsPage.ui
Normal file
548
launcher/ui/pages/instance/InstanceSettingsPage.ui
Normal file
@ -0,0 +1,548 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>InstanceSettingsPage</class>
|
||||
<widget class="QWidget" name="InstanceSettingsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>691</width>
|
||||
<height>581</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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="QCommandLinkButton" name="openGlobalJavaSettingsButton">
|
||||
<property name="text">
|
||||
<string>Open Global Settings</string>
|
||||
</property>
|
||||
<property name="description">
|
||||
<string>The settings here are overrides for global settings.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="settingsTabs">
|
||||
<property name="tabShape">
|
||||
<enum>QTabWidget::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="minecraftTab">
|
||||
<attribute name="title">
|
||||
<string notr="true">Java</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="javaSettingsGroupBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Java insta&llation</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLineEdit" name="javaPathTextBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="javaDetectBtn">
|
||||
<property name="text">
|
||||
<string>Auto-detect...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="javaBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="javaTestBtn">
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="memoryGroupBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Memor&y</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelMinMem">
|
||||
<property name="text">
|
||||
<string>Minimum memory allocation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="maxMemSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>The maximum amount of memory Minecraft is allowed to use.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="minMemSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory Minecraft is started with.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="permGenSpinBox">
|
||||
<property name="toolTip">
|
||||
<string>The amount of memory available to store loaded Java classes.</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string notr="true"> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>64</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999999999</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>64</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelPermGen">
|
||||
<property name="text">
|
||||
<string notr="true">PermGen:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelMaxMem">
|
||||
<property name="text">
|
||||
<string>Maximum memory allocation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLabel" name="labelPermgenNote">
|
||||
<property name="text">
|
||||
<string>Note: Permgen is set automatically by Java 8 and later</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="javaArgumentsGroupBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Java argumen&ts</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="1" column="1">
|
||||
<widget class="QPlainTextEdit" name="jvmArgsTextBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="javaTab">
|
||||
<attribute name="title">
|
||||
<string>Game windows</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="windowSizeGroupBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Game Window</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="maximizedCheckBox">
|
||||
<property name="text">
|
||||
<string>Start Minecraft maximized?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayoutWindowSize">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelWindowHeight">
|
||||
<property name="text">
|
||||
<string>Window height:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelWindowWidth">
|
||||
<property name="text">
|
||||
<string>Window width:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="windowWidthSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>854</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="windowHeightSpinBox">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>480</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="consoleSettingsBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Conso&le Settings</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showConsoleCheck">
|
||||
<property name="text">
|
||||
<string>Show console while the game is running?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoCloseConsoleCheck">
|
||||
<property name="text">
|
||||
<string>Automatically close console when the game quits?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showConsoleErrorCheck">
|
||||
<property name="text">
|
||||
<string>Show console when the game crashes?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacerMinecraft_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>88</width>
|
||||
<height>125</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Custom commands</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="CustomCommands" name="customCommands" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="workaroundsPage">
|
||||
<attribute name="title">
|
||||
<string>Workarounds</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="nativeWorkaroundsGroupBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Native libraries</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useNativeGLFWCheck">
|
||||
<property name="text">
|
||||
<string>Use system installation of GLFW</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="useNativeOpenALCheck">
|
||||
<property name="text">
|
||||
<string>Use system installation of OpenAL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="miscellaneousPage">
|
||||
<attribute name="title">
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameTimeGroupBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Override global game time settings</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showGameTime">
|
||||
<property name="text">
|
||||
<string>Show time spent playing this instance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="recordGameTime">
|
||||
<property name="text">
|
||||
<string>Record time spent playing this instance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="serverJoinGroupBox">
|
||||
<property name="title">
|
||||
<string>Set a server to join on launch</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="serverJoinLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="serverJoinAddressLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Server address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="serverJoinAddress"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacerMiscellaneous">
|
||||
<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>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>CustomCommands</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>ui/widgets/CustomCommands.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>openGlobalJavaSettingsButton</tabstop>
|
||||
<tabstop>settingsTabs</tabstop>
|
||||
<tabstop>javaSettingsGroupBox</tabstop>
|
||||
<tabstop>javaPathTextBox</tabstop>
|
||||
<tabstop>javaDetectBtn</tabstop>
|
||||
<tabstop>javaBrowseBtn</tabstop>
|
||||
<tabstop>javaTestBtn</tabstop>
|
||||
<tabstop>memoryGroupBox</tabstop>
|
||||
<tabstop>minMemSpinBox</tabstop>
|
||||
<tabstop>maxMemSpinBox</tabstop>
|
||||
<tabstop>permGenSpinBox</tabstop>
|
||||
<tabstop>javaArgumentsGroupBox</tabstop>
|
||||
<tabstop>jvmArgsTextBox</tabstop>
|
||||
<tabstop>windowSizeGroupBox</tabstop>
|
||||
<tabstop>maximizedCheckBox</tabstop>
|
||||
<tabstop>windowWidthSpinBox</tabstop>
|
||||
<tabstop>windowHeightSpinBox</tabstop>
|
||||
<tabstop>consoleSettingsBox</tabstop>
|
||||
<tabstop>showConsoleCheck</tabstop>
|
||||
<tabstop>autoCloseConsoleCheck</tabstop>
|
||||
<tabstop>showConsoleErrorCheck</tabstop>
|
||||
<tabstop>nativeWorkaroundsGroupBox</tabstop>
|
||||
<tabstop>useNativeGLFWCheck</tabstop>
|
||||
<tabstop>useNativeOpenALCheck</tabstop>
|
||||
<tabstop>showGameTime</tabstop>
|
||||
<tabstop>recordGameTime</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
51
launcher/ui/pages/instance/LegacyUpgradePage.cpp
Normal file
51
launcher/ui/pages/instance/LegacyUpgradePage.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "LegacyUpgradePage.h"
|
||||
#include "ui_LegacyUpgradePage.h"
|
||||
|
||||
#include "InstanceList.h"
|
||||
#include "minecraft/legacy/LegacyInstance.h"
|
||||
#include "minecraft/legacy/LegacyUpgradeTask.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
||||
LegacyUpgradePage::LegacyUpgradePage(InstancePtr inst, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
LegacyUpgradePage::~LegacyUpgradePage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void LegacyUpgradePage::runModalTask(Task *task)
|
||||
{
|
||||
connect(task, &Task::failed, [this](QString reason)
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show();
|
||||
});
|
||||
ProgressDialog loadDialog(this);
|
||||
loadDialog.setSkipButton(true, tr("Abort"));
|
||||
if(loadDialog.execWithTask(task) == QDialog::Accepted)
|
||||
{
|
||||
m_container->requestClose();
|
||||
}
|
||||
}
|
||||
|
||||
void LegacyUpgradePage::on_upgradeButton_clicked()
|
||||
{
|
||||
QString newName = tr("%1 (Migrated)").arg(m_inst->name());
|
||||
auto upgradeTask = new LegacyUpgradeTask(m_inst);
|
||||
upgradeTask->setName(newName);
|
||||
upgradeTask->setGroup(APPLICATION->instances()->getInstanceGroup(m_inst->id()));
|
||||
upgradeTask->setIcon(m_inst->iconKey());
|
||||
unique_qobject_ptr<Task> task(APPLICATION->instances()->wrapInstanceTask(upgradeTask));
|
||||
runModalTask(task.get());
|
||||
}
|
||||
|
||||
bool LegacyUpgradePage::shouldDisplay() const
|
||||
{
|
||||
return !m_inst->isRunning();
|
||||
}
|
64
launcher/ui/pages/instance/LegacyUpgradePage.h
Normal file
64
launcher/ui/pages/instance/LegacyUpgradePage.h
Normal file
@ -0,0 +1,64 @@
|
||||
/* 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 "minecraft/legacy/LegacyInstance.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class LegacyUpgradePage;
|
||||
}
|
||||
|
||||
class LegacyUpgradePage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LegacyUpgradePage(InstancePtr inst, QWidget *parent = 0);
|
||||
virtual ~LegacyUpgradePage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Upgrade");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("checkupdate");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "upgrade";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Legacy-upgrade";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
private slots:
|
||||
void on_upgradeButton_clicked();
|
||||
|
||||
private:
|
||||
void runModalTask(Task *task);
|
||||
|
||||
private:
|
||||
Ui::LegacyUpgradePage *ui;
|
||||
InstancePtr m_inst;
|
||||
};
|
47
launcher/ui/pages/instance/LegacyUpgradePage.ui
Normal file
47
launcher/ui/pages/instance/LegacyUpgradePage.ui
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LegacyUpgradePage</class>
|
||||
<widget class="QWidget" name="LegacyUpgradePage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>546</width>
|
||||
<height>405</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="textBrowser">
|
||||
<property name="html">
|
||||
<string><html><body><h1>Upgrade is required</h1><p>MultiMC now supports old Minecraft versions and all the required features in the new (OneSix) instance format. As a consequence, the old (Legacy) format has been entirely disabled and old instances need to be upgraded.</p><p>The upgrade will create a new instance with the same contents as the current one, in the new format. The original instance will remain untouched, in case anything goes wrong in the process.</p><p>Please report any issues on our <a href="https://github.com/MultiMC/Launcher/issues">github issues page</a>.</p><p>There is also a <a href="https://discord.gg/GtPmv93">discord channel for testing here</a>.</p></body></html></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCommandLinkButton" name="upgradeButton">
|
||||
<property name="text">
|
||||
<string>Upgrade the instance</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
330
launcher/ui/pages/instance/LogPage.cpp
Normal file
330
launcher/ui/pages/instance/LogPage.cpp
Normal file
@ -0,0 +1,330 @@
|
||||
#include "LogPage.h"
|
||||
#include "ui_LogPage.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QScrollBar>
|
||||
#include <QShortcut>
|
||||
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "settings/Setting.h"
|
||||
|
||||
#include "ui/GuiUtil.h"
|
||||
#include "ui/ColorCache.h"
|
||||
|
||||
#include <BuildConfig.h>
|
||||
|
||||
class LogFormatProxyModel : public QIdentityProxyModel
|
||||
{
|
||||
public:
|
||||
LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent)
|
||||
{
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override
|
||||
{
|
||||
switch(role)
|
||||
{
|
||||
case Qt::FontRole:
|
||||
return m_font;
|
||||
case Qt::TextColorRole:
|
||||
{
|
||||
MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();
|
||||
return m_colors->getFront(level);
|
||||
}
|
||||
case Qt::BackgroundRole:
|
||||
{
|
||||
MessageLevel::Enum level = (MessageLevel::Enum) QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();
|
||||
return m_colors->getBack(level);
|
||||
}
|
||||
default:
|
||||
return QIdentityProxyModel::data(index, role);
|
||||
}
|
||||
}
|
||||
|
||||
void setFont(QFont font)
|
||||
{
|
||||
m_font = font;
|
||||
}
|
||||
|
||||
void setColors(LogColorCache* colors)
|
||||
{
|
||||
m_colors.reset(colors);
|
||||
}
|
||||
|
||||
QModelIndex find(const QModelIndex &start, const QString &value, bool reverse) const
|
||||
{
|
||||
QModelIndex parentIndex = parent(start);
|
||||
auto compare = [&](int r) -> QModelIndex
|
||||
{
|
||||
QModelIndex idx = index(r, start.column(), parentIndex);
|
||||
if (!idx.isValid() || idx == start)
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
QVariant v = data(idx, Qt::DisplayRole);
|
||||
QString t = v.toString();
|
||||
if (t.contains(value, Qt::CaseInsensitive))
|
||||
return idx;
|
||||
return QModelIndex();
|
||||
};
|
||||
if(reverse)
|
||||
{
|
||||
int from = start.row();
|
||||
int to = 0;
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
for (int r = from; (r >= to); --r)
|
||||
{
|
||||
auto idx = compare(r);
|
||||
if(idx.isValid())
|
||||
return idx;
|
||||
}
|
||||
// prepare for the next iteration
|
||||
from = rowCount() - 1;
|
||||
to = start.row();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int from = start.row();
|
||||
int to = rowCount(parentIndex);
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
for (int r = from; (r < to); ++r)
|
||||
{
|
||||
auto idx = compare(r);
|
||||
if(idx.isValid())
|
||||
return idx;
|
||||
}
|
||||
// prepare for the next iteration
|
||||
from = 0;
|
||||
to = start.row();
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
private:
|
||||
QFont m_font;
|
||||
std::unique_ptr<LogColorCache> m_colors;
|
||||
};
|
||||
|
||||
LogPage::LogPage(InstancePtr instance, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::LogPage), m_instance(instance)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
|
||||
m_proxy = new LogFormatProxyModel(this);
|
||||
// set up text colors in the log proxy and adapt them to the current theme foreground and background
|
||||
{
|
||||
auto origForeground = ui->text->palette().color(ui->text->foregroundRole());
|
||||
auto origBackground = ui->text->palette().color(ui->text->backgroundRole());
|
||||
m_proxy->setColors(new LogColorCache(origForeground, origBackground));
|
||||
}
|
||||
|
||||
// set up fonts in the log proxy
|
||||
{
|
||||
QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString();
|
||||
bool conversionOk = false;
|
||||
int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk);
|
||||
if(!conversionOk)
|
||||
{
|
||||
fontSize = 11;
|
||||
}
|
||||
m_proxy->setFont(QFont(fontFamily, fontSize));
|
||||
}
|
||||
|
||||
ui->text->setModel(m_proxy);
|
||||
|
||||
// set up instance and launch process recognition
|
||||
{
|
||||
auto launchTask = m_instance->getLaunchTask();
|
||||
if(launchTask)
|
||||
{
|
||||
setInstanceLaunchTaskChanged(launchTask, true);
|
||||
}
|
||||
connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged);
|
||||
}
|
||||
|
||||
auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this);
|
||||
connect(findShortcut, SIGNAL(activated()), SLOT(findActivated()));
|
||||
auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this);
|
||||
connect(findNextShortcut, SIGNAL(activated()), SLOT(findNextActivated()));
|
||||
connect(ui->searchBar, SIGNAL(returnPressed()), SLOT(on_findButton_clicked()));
|
||||
auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this);
|
||||
connect(findPreviousShortcut, SIGNAL(activated()), SLOT(findPreviousActivated()));
|
||||
}
|
||||
|
||||
LogPage::~LogPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void LogPage::modelStateToUI()
|
||||
{
|
||||
if(m_model->wrapLines())
|
||||
{
|
||||
ui->text->setWordWrap(true);
|
||||
ui->wrapCheckbox->setCheckState(Qt::Checked);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->text->setWordWrap(false);
|
||||
ui->wrapCheckbox->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
if(m_model->suspended())
|
||||
{
|
||||
ui->trackLogCheckbox->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->trackLogCheckbox->setCheckState(Qt::Checked);
|
||||
}
|
||||
}
|
||||
|
||||
void LogPage::UIToModelState()
|
||||
{
|
||||
if(!m_model)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked);
|
||||
m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked);
|
||||
}
|
||||
|
||||
void LogPage::setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial)
|
||||
{
|
||||
m_process = proc;
|
||||
if(m_process)
|
||||
{
|
||||
m_model = proc->getLogModel();
|
||||
m_proxy->setSourceModel(m_model.get());
|
||||
if(initial)
|
||||
{
|
||||
modelStateToUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
UIToModelState();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_proxy->setSourceModel(nullptr);
|
||||
m_model.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void LogPage::onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)
|
||||
{
|
||||
setInstanceLaunchTaskChanged(proc, false);
|
||||
}
|
||||
|
||||
bool LogPage::apply()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogPage::shouldDisplay() const
|
||||
{
|
||||
return m_instance->isRunning() || m_proxy->rowCount() > 0;
|
||||
}
|
||||
|
||||
void LogPage::on_btnPaste_clicked()
|
||||
{
|
||||
if(!m_model)
|
||||
return;
|
||||
|
||||
//FIXME: turn this into a proper task and move the upload logic out of GuiUtil!
|
||||
m_model->append(
|
||||
MessageLevel::Launcher,
|
||||
QString("%2: Log upload triggered at: %1").arg(
|
||||
QDateTime::currentDateTime().toString(Qt::RFC2822Date),
|
||||
BuildConfig.LAUNCHER_NAME
|
||||
)
|
||||
);
|
||||
auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this);
|
||||
if(!url.isEmpty())
|
||||
{
|
||||
m_model->append(
|
||||
MessageLevel::Launcher,
|
||||
QString("%2: Log uploaded to: %1").arg(
|
||||
url,
|
||||
BuildConfig.LAUNCHER_NAME
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_model->append(
|
||||
MessageLevel::Error,
|
||||
QString("%1: Log upload failed!").arg(BuildConfig.LAUNCHER_NAME)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void LogPage::on_btnCopy_clicked()
|
||||
{
|
||||
if(!m_model)
|
||||
return;
|
||||
m_model->append(MessageLevel::Launcher, QString("Clipboard copy at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)));
|
||||
GuiUtil::setClipboardText(m_model->toPlainText());
|
||||
}
|
||||
|
||||
void LogPage::on_btnClear_clicked()
|
||||
{
|
||||
if(!m_model)
|
||||
return;
|
||||
m_model->clear();
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
void LogPage::on_btnBottom_clicked()
|
||||
{
|
||||
ui->text->scrollToBottom();
|
||||
}
|
||||
|
||||
void LogPage::on_trackLogCheckbox_clicked(bool checked)
|
||||
{
|
||||
if(!m_model)
|
||||
return;
|
||||
m_model->suspend(!checked);
|
||||
}
|
||||
|
||||
void LogPage::on_wrapCheckbox_clicked(bool checked)
|
||||
{
|
||||
ui->text->setWordWrap(checked);
|
||||
if(!m_model)
|
||||
return;
|
||||
m_model->setLineWrap(checked);
|
||||
}
|
||||
|
||||
void LogPage::on_findButton_clicked()
|
||||
{
|
||||
auto modifiers = QApplication::keyboardModifiers();
|
||||
bool reverse = modifiers & Qt::ShiftModifier;
|
||||
ui->text->findNext(ui->searchBar->text(), reverse);
|
||||
}
|
||||
|
||||
void LogPage::findNextActivated()
|
||||
{
|
||||
ui->text->findNext(ui->searchBar->text(), false);
|
||||
}
|
||||
|
||||
void LogPage::findPreviousActivated()
|
||||
{
|
||||
ui->text->findNext(ui->searchBar->text(), true);
|
||||
}
|
||||
|
||||
void LogPage::findActivated()
|
||||
{
|
||||
// focus the search bar if it doesn't have focus
|
||||
if (!ui->searchBar->hasFocus())
|
||||
{
|
||||
ui->searchBar->setFocus();
|
||||
ui->searchBar->selectAll();
|
||||
}
|
||||
}
|
86
launcher/ui/pages/instance/LogPage.h
Normal file
86
launcher/ui/pages/instance/LogPage.h
Normal file
@ -0,0 +1,86 @@
|
||||
/* 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 "BaseInstance.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class LogPage;
|
||||
}
|
||||
class QTextCharFormat;
|
||||
class LogFormatProxyModel;
|
||||
|
||||
class LogPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LogPage(InstancePtr instance, QWidget *parent = 0);
|
||||
virtual ~LogPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Minecraft Log");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("log");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "console";
|
||||
}
|
||||
virtual bool apply() override;
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Minecraft-Logs";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
private slots:
|
||||
void on_btnPaste_clicked();
|
||||
void on_btnCopy_clicked();
|
||||
void on_btnClear_clicked();
|
||||
void on_btnBottom_clicked();
|
||||
|
||||
void on_trackLogCheckbox_clicked(bool checked);
|
||||
void on_wrapCheckbox_clicked(bool checked);
|
||||
|
||||
void on_findButton_clicked();
|
||||
void findActivated();
|
||||
void findNextActivated();
|
||||
void findPreviousActivated();
|
||||
|
||||
void onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
|
||||
|
||||
private:
|
||||
void modelStateToUI();
|
||||
void UIToModelState();
|
||||
void setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial);
|
||||
|
||||
private:
|
||||
Ui::LogPage *ui;
|
||||
InstancePtr m_instance;
|
||||
shared_qobject_ptr<LaunchTask> m_process;
|
||||
|
||||
LogFormatProxyModel * m_proxy;
|
||||
shared_qobject_ptr <LogModel> m_model;
|
||||
};
|
182
launcher/ui/pages/instance/LogPage.ui
Normal file
182
launcher/ui/pages/instance/LogPage.ui
Normal file
@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LogPage</class>
|
||||
<widget class="QWidget" name="LogPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>825</width>
|
||||
<height>782</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0" colspan="5">
|
||||
<widget class="LogView" name="text">
|
||||
<property name="undoRedoEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
<property name="centerOnScroll">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="5">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="trackLogCheckbox">
|
||||
<property name="text">
|
||||
<string>Keep updating</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="wrapCheckbox">
|
||||
<property name="text">
|
||||
<string>Wrap lines</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnCopy">
|
||||
<property name="toolTip">
|
||||
<string>Copy the whole log into the clipboard</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnPaste">
|
||||
<property name="toolTip">
|
||||
<string>Upload the log to paste.ee - it will stay online for a month</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Upload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnClear">
|
||||
<property name="toolTip">
|
||||
<string>Clear the log</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Search:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="findButton">
|
||||
<property name="text">
|
||||
<string>Find</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="searchBar"/>
|
||||
</item>
|
||||
<item row="2" column="4">
|
||||
<widget class="QPushButton" name="btnBottom">
|
||||
<property name="toolTip">
|
||||
<string>Scroll all the way to bottom</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bottom</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LogView</class>
|
||||
<extends>QPlainTextEdit</extends>
|
||||
<header>ui/widgets/LogView.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>trackLogCheckbox</tabstop>
|
||||
<tabstop>wrapCheckbox</tabstop>
|
||||
<tabstop>btnCopy</tabstop>
|
||||
<tabstop>btnPaste</tabstop>
|
||||
<tabstop>btnClear</tabstop>
|
||||
<tabstop>text</tabstop>
|
||||
<tabstop>searchBar</tabstop>
|
||||
<tabstop>findButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
366
launcher/ui/pages/instance/ModFolderPage.cpp
Normal file
366
launcher/ui/pages/instance/ModFolderPage.cpp
Normal file
@ -0,0 +1,366 @@
|
||||
/* 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 "ModFolderPage.h"
|
||||
#include "ui_ModFolderPage.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/GuiUtil.h"
|
||||
|
||||
#include "DesktopServices.h"
|
||||
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
namespace {
|
||||
// FIXME: wasteful
|
||||
void RemoveThePrefix(QString & string) {
|
||||
QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +"));
|
||||
string.remove(regex);
|
||||
string = string.trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
class ModSortProxy : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override {
|
||||
ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
|
||||
if(!model) {
|
||||
return false;
|
||||
}
|
||||
const auto &mod = model->at(source_row);
|
||||
if(mod.name().contains(filterRegExp())) {
|
||||
return true;
|
||||
}
|
||||
if(mod.description().contains(filterRegExp())) {
|
||||
return true;
|
||||
}
|
||||
for(auto & author: mod.authors()) {
|
||||
if (author.contains(filterRegExp())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override
|
||||
{
|
||||
ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
|
||||
if(
|
||||
!model ||
|
||||
!source_left.isValid() ||
|
||||
!source_right.isValid() ||
|
||||
source_left.column() != source_right.column()
|
||||
) {
|
||||
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||
}
|
||||
|
||||
// we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed.
|
||||
|
||||
auto column = (ModFolderModel::Columns) source_left.column();
|
||||
bool invert = false;
|
||||
switch(column) {
|
||||
// GH-2550 - sort by enabled/disabled
|
||||
case ModFolderModel::ActiveColumn: {
|
||||
auto dataL = source_left.data(Qt::CheckStateRole).toBool();
|
||||
auto dataR = source_right.data(Qt::CheckStateRole).toBool();
|
||||
if(dataL != dataR) {
|
||||
return dataL > dataR;
|
||||
}
|
||||
// fallthrough
|
||||
invert = sortOrder() == Qt::DescendingOrder;
|
||||
}
|
||||
// GH-2722 - sort mod names in a way that discards "The" prefixes
|
||||
case ModFolderModel::NameColumn: {
|
||||
auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString();
|
||||
RemoveThePrefix(dataL);
|
||||
auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString();
|
||||
RemoveThePrefix(dataR);
|
||||
|
||||
auto less = dataL.compare(dataR, sortCaseSensitivity());
|
||||
if(less != 0) {
|
||||
return invert ? (less > 0) : (less < 0);
|
||||
}
|
||||
// fallthrough
|
||||
invert = sortOrder() == Qt::DescendingOrder;
|
||||
}
|
||||
// GH-2762 - sort versions by parsing them as versions
|
||||
case ModFolderModel::VersionColumn: {
|
||||
auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString());
|
||||
auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString());
|
||||
return invert ? (dataL > dataR) : (dataL < dataR);
|
||||
}
|
||||
default: {
|
||||
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ModFolderPage::ModFolderPage(
|
||||
BaseInstance *inst,
|
||||
std::shared_ptr<ModFolderModel> mods,
|
||||
QString id,
|
||||
QString iconName,
|
||||
QString displayName,
|
||||
QString helpPage,
|
||||
QWidget *parent
|
||||
) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::ModFolderPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->actionsToolbar->insertSpacer(ui->actionView_configs);
|
||||
|
||||
m_inst = inst;
|
||||
on_RunningState_changed(m_inst && m_inst->isRunning());
|
||||
m_mods = mods;
|
||||
m_id = id;
|
||||
m_displayName = displayName;
|
||||
m_iconName = iconName;
|
||||
m_helpName = helpPage;
|
||||
m_fileSelectionFilter = "%1 (*.zip *.jar)";
|
||||
m_filterModel = new ModSortProxy(this);
|
||||
m_filterModel->setDynamicSortFilter(true);
|
||||
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_filterModel->setSourceModel(m_mods.get());
|
||||
m_filterModel->setFilterKeyColumn(-1);
|
||||
ui->modTreeView->setModel(m_filterModel);
|
||||
ui->modTreeView->installEventFilter(this);
|
||||
ui->modTreeView->sortByColumn(1, Qt::AscendingOrder);
|
||||
ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu);
|
||||
connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated);
|
||||
|
||||
auto smodel = ui->modTreeView->selectionModel();
|
||||
connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent);
|
||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged);
|
||||
connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed);
|
||||
}
|
||||
|
||||
void ModFolderPage::modItemActivated(const QModelIndex&)
|
||||
{
|
||||
if(!m_controlsEnabled) {
|
||||
return;
|
||||
}
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
|
||||
m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle);
|
||||
}
|
||||
|
||||
QMenu * ModFolderPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
void ModFolderPage::ShowContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu"));
|
||||
menu->exec(ui->modTreeView->mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
|
||||
void ModFolderPage::openedImpl()
|
||||
{
|
||||
m_mods->startWatching();
|
||||
}
|
||||
|
||||
void ModFolderPage::closedImpl()
|
||||
{
|
||||
m_mods->stopWatching();
|
||||
}
|
||||
|
||||
void ModFolderPage::on_filterTextChanged(const QString& newContents)
|
||||
{
|
||||
m_viewFilter = newContents;
|
||||
m_filterModel->setFilterFixedString(m_viewFilter);
|
||||
}
|
||||
|
||||
|
||||
CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods,
|
||||
QString id, QString iconName, QString displayName,
|
||||
QString helpPage, QWidget *parent)
|
||||
: ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent)
|
||||
{
|
||||
}
|
||||
|
||||
ModFolderPage::~ModFolderPage()
|
||||
{
|
||||
m_mods->stopWatching();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ModFolderPage::on_RunningState_changed(bool running)
|
||||
{
|
||||
if(m_controlsEnabled == !running) {
|
||||
return;
|
||||
}
|
||||
m_controlsEnabled = !running;
|
||||
ui->actionAdd->setEnabled(m_controlsEnabled);
|
||||
ui->actionDisable->setEnabled(m_controlsEnabled);
|
||||
ui->actionEnable->setEnabled(m_controlsEnabled);
|
||||
ui->actionRemove->setEnabled(m_controlsEnabled);
|
||||
}
|
||||
|
||||
bool ModFolderPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoreModFolderPage::shouldDisplay() const
|
||||
{
|
||||
if (ModFolderPage::shouldDisplay())
|
||||
{
|
||||
auto inst = dynamic_cast<MinecraftInstance *>(m_inst);
|
||||
if (!inst)
|
||||
return true;
|
||||
auto version = inst->getPackProfile();
|
||||
if (!version)
|
||||
return true;
|
||||
if(!version->getComponent("net.minecraftforge"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(!version->getComponent("net.minecraft"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModFolderPage::modListFilter(QKeyEvent *keyEvent)
|
||||
{
|
||||
switch (keyEvent->key())
|
||||
{
|
||||
case Qt::Key_Delete:
|
||||
on_actionRemove_triggered();
|
||||
return true;
|
||||
case Qt::Key_Plus:
|
||||
on_actionAdd_triggered();
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QWidget::eventFilter(ui->modTreeView, keyEvent);
|
||||
}
|
||||
|
||||
bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev)
|
||||
{
|
||||
if (ev->type() != QEvent::KeyPress)
|
||||
{
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
}
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
|
||||
if (obj == ui->modTreeView)
|
||||
return modListFilter(keyEvent);
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
}
|
||||
|
||||
void ModFolderPage::on_actionAdd_triggered()
|
||||
{
|
||||
if(!m_controlsEnabled) {
|
||||
return;
|
||||
}
|
||||
auto list = GuiUtil::BrowseForFiles(
|
||||
m_helpName,
|
||||
tr("Select %1",
|
||||
"Select whatever type of files the page contains. Example: 'Loader Mods'")
|
||||
.arg(m_displayName),
|
||||
m_fileSelectionFilter.arg(m_displayName), APPLICATION->settings()->get("CentralModsDir").toString(),
|
||||
this->parentWidget());
|
||||
if (!list.empty())
|
||||
{
|
||||
for (auto filename : list)
|
||||
{
|
||||
m_mods->installMod(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModFolderPage::on_actionEnable_triggered()
|
||||
{
|
||||
if(!m_controlsEnabled) {
|
||||
return;
|
||||
}
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
|
||||
m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable);
|
||||
}
|
||||
|
||||
void ModFolderPage::on_actionDisable_triggered()
|
||||
{
|
||||
if(!m_controlsEnabled) {
|
||||
return;
|
||||
}
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
|
||||
m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable);
|
||||
}
|
||||
|
||||
void ModFolderPage::on_actionRemove_triggered()
|
||||
{
|
||||
if(!m_controlsEnabled) {
|
||||
return;
|
||||
}
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
|
||||
m_mods->deleteMods(selection.indexes());
|
||||
}
|
||||
|
||||
void ModFolderPage::on_actionView_configs_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true);
|
||||
}
|
||||
|
||||
void ModFolderPage::on_actionView_Folder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(m_mods->dir().absolutePath(), true);
|
||||
}
|
||||
|
||||
void ModFolderPage::modCurrent(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
if (!current.isValid())
|
||||
{
|
||||
ui->frame->clear();
|
||||
return;
|
||||
}
|
||||
auto sourceCurrent = m_filterModel->mapToSource(current);
|
||||
int row = sourceCurrent.row();
|
||||
Mod &m = m_mods->operator[](row);
|
||||
ui->frame->updateWithMod(m);
|
||||
}
|
120
launcher/ui/pages/instance/ModFolderPage.h
Normal file
120
launcher/ui/pages/instance/ModFolderPage.h
Normal file
@ -0,0 +1,120 @@
|
||||
/* 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 <QMainWindow>
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
class ModFolderModel;
|
||||
namespace Ui
|
||||
{
|
||||
class ModFolderPage;
|
||||
}
|
||||
|
||||
class ModFolderPage : public QMainWindow, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ModFolderPage(
|
||||
BaseInstance *inst,
|
||||
std::shared_ptr<ModFolderModel> mods,
|
||||
QString id,
|
||||
QString iconName,
|
||||
QString displayName,
|
||||
QString helpPage = "",
|
||||
QWidget *parent = 0
|
||||
);
|
||||
virtual ~ModFolderPage();
|
||||
|
||||
void setFilter(const QString & filter)
|
||||
{
|
||||
m_fileSelectionFilter = filter;
|
||||
}
|
||||
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return m_displayName;
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon(m_iconName);
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return m_helpName;
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
virtual void openedImpl() override;
|
||||
virtual void closedImpl() override;
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *ev) override;
|
||||
bool modListFilter(QKeyEvent *ev);
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
protected:
|
||||
BaseInstance *m_inst = nullptr;
|
||||
|
||||
protected:
|
||||
Ui::ModFolderPage *ui = nullptr;
|
||||
std::shared_ptr<ModFolderModel> m_mods;
|
||||
QSortFilterProxyModel *m_filterModel = nullptr;
|
||||
QString m_iconName;
|
||||
QString m_id;
|
||||
QString m_displayName;
|
||||
QString m_helpName;
|
||||
QString m_fileSelectionFilter;
|
||||
QString m_viewFilter;
|
||||
bool m_controlsEnabled = true;
|
||||
|
||||
public
|
||||
slots:
|
||||
void modCurrent(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
|
||||
private
|
||||
slots:
|
||||
void modItemActivated(const QModelIndex &index);
|
||||
void on_filterTextChanged(const QString & newContents);
|
||||
void on_RunningState_changed(bool running);
|
||||
void on_actionAdd_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
void on_actionEnable_triggered();
|
||||
void on_actionDisable_triggered();
|
||||
void on_actionView_Folder_triggered();
|
||||
void on_actionView_configs_triggered();
|
||||
void ShowContextMenu(const QPoint &pos);
|
||||
};
|
||||
|
||||
class CoreModFolderPage : public ModFolderPage
|
||||
{
|
||||
public:
|
||||
explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id,
|
||||
QString iconName, QString displayName, QString helpPage = "",
|
||||
QWidget *parent = 0);
|
||||
virtual ~CoreModFolderPage()
|
||||
{
|
||||
}
|
||||
virtual bool shouldDisplay() const;
|
||||
};
|
164
launcher/ui/pages/instance/ModFolderPage.ui
Normal file
164
launcher/ui/pages/instance/ModFolderPage.ui
Normal file
@ -0,0 +1,164 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ModFolderPage</class>
|
||||
<widget class="QMainWindow" name="ModFolderPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1042</width>
|
||||
<height>501</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<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 row="4" column="1" colspan="3">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="filterEdit">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="filterLabel">
|
||||
<property name="text">
|
||||
<string>Filter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="3">
|
||||
<widget class="MCModInfoFrame" name="frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" colspan="3">
|
||||
<widget class="ModListView" name="modTreeView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DropOnly</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="WideBar" name="actionsToolbar">
|
||||
<property name="windowTitle">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>RightToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAdd"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionEnable"/>
|
||||
<addaction name="actionDisable"/>
|
||||
<addaction name="actionView_configs"/>
|
||||
<addaction name="actionView_Folder"/>
|
||||
</widget>
|
||||
<action name="actionAdd">
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add mods</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remove selected mods</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEnable">
|
||||
<property name="text">
|
||||
<string>&Enable</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Enable selected mods</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDisable">
|
||||
<property name="text">
|
||||
<string>&Disable</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Disable selected mods</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionView_configs">
|
||||
<property name="text">
|
||||
<string>View &Configs</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the 'config' folder in the system file manager.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionView_Folder">
|
||||
<property name="text">
|
||||
<string>View &Folder</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ModListView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>ui/widgets/ModListView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MCModInfoFrame</class>
|
||||
<extends>QFrame</extends>
|
||||
<header>ui/widgets/MCModInfoFrame.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>WideBar</class>
|
||||
<extends>QToolBar</extends>
|
||||
<header>ui/widgets/WideBar.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>modTreeView</tabstop>
|
||||
<tabstop>filterEdit</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
21
launcher/ui/pages/instance/NotesPage.cpp
Normal file
21
launcher/ui/pages/instance/NotesPage.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "NotesPage.h"
|
||||
#include "ui_NotesPage.h"
|
||||
#include <QTabBar>
|
||||
|
||||
NotesPage::NotesPage(BaseInstance *inst, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::NotesPage), m_inst(inst)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->noteEditor->setText(m_inst->notes());
|
||||
}
|
||||
|
||||
NotesPage::~NotesPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool NotesPage::apply()
|
||||
{
|
||||
m_inst->setNotes(ui->noteEditor->toPlainText());
|
||||
return true;
|
||||
}
|
60
launcher/ui/pages/instance/NotesPage.h
Normal file
60
launcher/ui/pages/instance/NotesPage.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* 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 "BaseInstance.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class NotesPage;
|
||||
}
|
||||
|
||||
class NotesPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NotesPage(BaseInstance *inst, QWidget *parent = 0);
|
||||
virtual ~NotesPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Notes");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
auto icon = APPLICATION->getThemedIcon("notes");
|
||||
if(icon.isNull())
|
||||
icon = APPLICATION->getThemedIcon("news");
|
||||
return icon;
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "notes";
|
||||
}
|
||||
virtual bool apply() override;
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Notes";
|
||||
}
|
||||
|
||||
private:
|
||||
Ui::NotesPage *ui;
|
||||
BaseInstance *m_inst;
|
||||
};
|
49
launcher/ui/pages/instance/NotesPage.ui
Normal file
49
launcher/ui/pages/instance/NotesPage.ui
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>NotesPage</class>
|
||||
<widget class="QWidget" name="NotesPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>731</width>
|
||||
<height>538</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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="QTextEdit" name="noteEditor">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="acceptRichText">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>noteEditor</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
314
launcher/ui/pages/instance/OtherLogsPage.cpp
Normal file
314
launcher/ui/pages/instance/OtherLogsPage.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
/* 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 "OtherLogsPage.h"
|
||||
#include "ui_OtherLogsPage.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "ui/GuiUtil.h"
|
||||
|
||||
#include "RecursiveFileSystemWatcher.h"
|
||||
#include <GZip.h>
|
||||
#include <FileSystem.h>
|
||||
#include <QShortcut>
|
||||
|
||||
OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter),
|
||||
m_watcher(new RecursiveFileSystemWatcher(this))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->tabBar()->hide();
|
||||
|
||||
m_watcher->setMatcher(fileFilter);
|
||||
m_watcher->setRootDir(QDir::current().absoluteFilePath(m_path));
|
||||
|
||||
connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox);
|
||||
populateSelectLogBox();
|
||||
|
||||
auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this);
|
||||
connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated);
|
||||
|
||||
auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this);
|
||||
connect(findNextShortcut, &QShortcut::activated, this, &OtherLogsPage::findNextActivated);
|
||||
|
||||
auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this);
|
||||
connect(findPreviousShortcut, &QShortcut::activated, this, &OtherLogsPage::findPreviousActivated);
|
||||
|
||||
connect(ui->searchBar, &QLineEdit::returnPressed, this, &OtherLogsPage::on_findButton_clicked);
|
||||
}
|
||||
|
||||
OtherLogsPage::~OtherLogsPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void OtherLogsPage::openedImpl()
|
||||
{
|
||||
m_watcher->enable();
|
||||
}
|
||||
void OtherLogsPage::closedImpl()
|
||||
{
|
||||
m_watcher->disable();
|
||||
}
|
||||
|
||||
void OtherLogsPage::populateSelectLogBox()
|
||||
{
|
||||
ui->selectLogBox->clear();
|
||||
ui->selectLogBox->addItems(m_watcher->files());
|
||||
if (m_currentFile.isEmpty())
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
ui->selectLogBox->setCurrentIndex(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int index = ui->selectLogBox->findText(m_currentFile);
|
||||
if (index != -1)
|
||||
{
|
||||
ui->selectLogBox->setCurrentIndex(index);
|
||||
setControlsEnabled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OtherLogsPage::on_selectLogBox_currentIndexChanged(const int index)
|
||||
{
|
||||
QString file;
|
||||
if (index != -1)
|
||||
{
|
||||
file = ui->selectLogBox->itemText(index);
|
||||
}
|
||||
|
||||
if (file.isEmpty() || !QFile::exists(FS::PathCombine(m_path, file)))
|
||||
{
|
||||
m_currentFile = QString();
|
||||
ui->text->clear();
|
||||
setControlsEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentFile = file;
|
||||
on_btnReload_clicked();
|
||||
setControlsEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void OtherLogsPage::on_btnReload_clicked()
|
||||
{
|
||||
if(m_currentFile.isEmpty())
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
return;
|
||||
}
|
||||
QFile file(FS::PathCombine(m_path, m_currentFile));
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
ui->btnReload->setEnabled(true); // allow reload
|
||||
m_currentFile = QString();
|
||||
QMessageBox::critical(this, tr("Error"), tr("Unable to open %1 for reading: %2")
|
||||
.arg(m_currentFile, file.errorString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto setPlainText = [&](const QString & text)
|
||||
{
|
||||
QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString();
|
||||
bool conversionOk = false;
|
||||
int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk);
|
||||
if(!conversionOk)
|
||||
{
|
||||
fontSize = 11;
|
||||
}
|
||||
QTextDocument *doc = ui->text->document();
|
||||
doc->setDefaultFont(QFont(fontFamily, fontSize));
|
||||
ui->text->setPlainText(text);
|
||||
};
|
||||
auto showTooBig = [&]()
|
||||
{
|
||||
setPlainText(
|
||||
tr("The file (%1) is too big. You may want to open it in a viewer optimized "
|
||||
"for large files.").arg(file.fileName()));
|
||||
};
|
||||
if(file.size() > (1024ll * 1024ll * 12ll))
|
||||
{
|
||||
showTooBig();
|
||||
return;
|
||||
}
|
||||
QString content;
|
||||
if(file.fileName().endsWith(".gz"))
|
||||
{
|
||||
QByteArray temp;
|
||||
if(!GZip::unzip(file.readAll(), temp))
|
||||
{
|
||||
setPlainText(
|
||||
tr("The file (%1) is not readable.").arg(file.fileName()));
|
||||
return;
|
||||
}
|
||||
content = QString::fromUtf8(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
content = QString::fromUtf8(file.readAll());
|
||||
}
|
||||
if (content.size() >= 50000000ll)
|
||||
{
|
||||
showTooBig();
|
||||
return;
|
||||
}
|
||||
setPlainText(content);
|
||||
}
|
||||
}
|
||||
|
||||
void OtherLogsPage::on_btnPaste_clicked()
|
||||
{
|
||||
GuiUtil::uploadPaste(ui->text->toPlainText(), this);
|
||||
}
|
||||
|
||||
void OtherLogsPage::on_btnCopy_clicked()
|
||||
{
|
||||
GuiUtil::setClipboardText(ui->text->toPlainText());
|
||||
}
|
||||
|
||||
void OtherLogsPage::on_btnDelete_clicked()
|
||||
{
|
||||
if(m_currentFile.isEmpty())
|
||||
{
|
||||
setControlsEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (QMessageBox::question(this, tr("Delete"),
|
||||
tr("Do you really want to delete %1?").arg(m_currentFile),
|
||||
QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
QFile file(FS::PathCombine(m_path, m_currentFile));
|
||||
if (!file.remove())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Unable to delete %1: %2")
|
||||
.arg(m_currentFile, file.errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void OtherLogsPage::on_btnClean_clicked()
|
||||
{
|
||||
auto toDelete = m_watcher->files();
|
||||
if(toDelete.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
QMessageBox *messageBox = new QMessageBox(this);
|
||||
messageBox->setWindowTitle(tr("Clean up"));
|
||||
if(toDelete.size() > 5)
|
||||
{
|
||||
messageBox->setText(tr("Do you really want to delete all log files?"));
|
||||
messageBox->setDetailedText(toDelete.join('\n'));
|
||||
}
|
||||
else
|
||||
{
|
||||
messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n')));
|
||||
}
|
||||
messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||
messageBox->setDefaultButton(QMessageBox::Ok);
|
||||
messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
messageBox->setIcon(QMessageBox::Question);
|
||||
messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
|
||||
if (messageBox->exec() != QMessageBox::Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
QStringList failed;
|
||||
for(auto item: toDelete)
|
||||
{
|
||||
QFile file(FS::PathCombine(m_path, item));
|
||||
if (!file.remove())
|
||||
{
|
||||
failed.push_back(item);
|
||||
}
|
||||
}
|
||||
if(!failed.empty())
|
||||
{
|
||||
QMessageBox *messageBox = new QMessageBox(this);
|
||||
messageBox->setWindowTitle(tr("Error"));
|
||||
if(failed.size() > 5)
|
||||
{
|
||||
messageBox->setText(tr("Couldn't delete some files!"));
|
||||
messageBox->setDetailedText(failed.join('\n'));
|
||||
}
|
||||
else
|
||||
{
|
||||
messageBox->setText(tr("Couldn't delete some files:\n%1").arg(failed.join('\n')));
|
||||
}
|
||||
messageBox->setStandardButtons(QMessageBox::Ok);
|
||||
messageBox->setDefaultButton(QMessageBox::Ok);
|
||||
messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
messageBox->setIcon(QMessageBox::Critical);
|
||||
messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
messageBox->exec();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OtherLogsPage::setControlsEnabled(const bool enabled)
|
||||
{
|
||||
ui->btnReload->setEnabled(enabled);
|
||||
ui->btnDelete->setEnabled(enabled);
|
||||
ui->btnCopy->setEnabled(enabled);
|
||||
ui->btnPaste->setEnabled(enabled);
|
||||
ui->text->setEnabled(enabled);
|
||||
ui->btnClean->setEnabled(enabled);
|
||||
}
|
||||
|
||||
// FIXME: HACK, use LogView instead?
|
||||
static void findNext(QPlainTextEdit * _this, const QString& what, bool reverse)
|
||||
{
|
||||
_this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0));
|
||||
}
|
||||
|
||||
void OtherLogsPage::on_findButton_clicked()
|
||||
{
|
||||
auto modifiers = QApplication::keyboardModifiers();
|
||||
bool reverse = modifiers & Qt::ShiftModifier;
|
||||
findNext(ui->text, ui->searchBar->text(), reverse);
|
||||
}
|
||||
|
||||
void OtherLogsPage::findNextActivated()
|
||||
{
|
||||
findNext(ui->text, ui->searchBar->text(), false);
|
||||
}
|
||||
|
||||
void OtherLogsPage::findPreviousActivated()
|
||||
{
|
||||
findNext(ui->text, ui->searchBar->text(), true);
|
||||
}
|
||||
|
||||
void OtherLogsPage::findActivated()
|
||||
{
|
||||
// focus the search bar if it doesn't have focus
|
||||
if (!ui->searchBar->hasFocus())
|
||||
{
|
||||
ui->searchBar->setFocus();
|
||||
ui->searchBar->selectAll();
|
||||
}
|
||||
}
|
81
launcher/ui/pages/instance/OtherLogsPage.h
Normal file
81
launcher/ui/pages/instance/OtherLogsPage.h
Normal file
@ -0,0 +1,81 @@
|
||||
/* 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 <pathmatcher/IPathMatcher.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class OtherLogsPage;
|
||||
}
|
||||
|
||||
class RecursiveFileSystemWatcher;
|
||||
|
||||
class OtherLogsPage : public QWidget, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent = 0);
|
||||
~OtherLogsPage();
|
||||
|
||||
QString id() const override
|
||||
{
|
||||
return "logs";
|
||||
}
|
||||
QString displayName() const override
|
||||
{
|
||||
return tr("Other logs");
|
||||
}
|
||||
QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("log");
|
||||
}
|
||||
QString helpPage() const override
|
||||
{
|
||||
return "Minecraft-Logs";
|
||||
}
|
||||
void openedImpl() override;
|
||||
void closedImpl() override;
|
||||
|
||||
private slots:
|
||||
void populateSelectLogBox();
|
||||
void on_selectLogBox_currentIndexChanged(const int index);
|
||||
void on_btnReload_clicked();
|
||||
void on_btnPaste_clicked();
|
||||
void on_btnCopy_clicked();
|
||||
void on_btnDelete_clicked();
|
||||
void on_btnClean_clicked();
|
||||
|
||||
void on_findButton_clicked();
|
||||
void findActivated();
|
||||
void findNextActivated();
|
||||
void findPreviousActivated();
|
||||
|
||||
private:
|
||||
void setControlsEnabled(const bool enabled);
|
||||
|
||||
private:
|
||||
Ui::OtherLogsPage *ui;
|
||||
QString m_path;
|
||||
QString m_currentFile;
|
||||
IPathMatcher::Ptr m_fileFilter;
|
||||
RecursiveFileSystemWatcher *m_watcher;
|
||||
};
|
150
launcher/ui/pages/instance/OtherLogsPage.ui
Normal file
150
launcher/ui/pages/instance/OtherLogsPage.ui
Normal file
@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OtherLogsPage</class>
|
||||
<widget class="QWidget" name="OtherLogsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>657</width>
|
||||
<height>538</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<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">Tab 1</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="searchBar"/>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="findButton">
|
||||
<property name="text">
|
||||
<string>Find</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="4">
|
||||
<widget class="QPlainTextEdit" name="text">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="btnCopy">
|
||||
<property name="toolTip">
|
||||
<string>Copy the whole log into the clipboard</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QPushButton" name="btnDelete">
|
||||
<property name="toolTip">
|
||||
<string>Clear the log</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="btnPaste">
|
||||
<property name="toolTip">
|
||||
<string>Upload the log to paste.ee - it will stay online for a month</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Upload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QPushButton" name="btnClean">
|
||||
<property name="toolTip">
|
||||
<string>Clear the log</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clean</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="btnReload">
|
||||
<property name="text">
|
||||
<string>Reload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="5">
|
||||
<widget class="QComboBox" name="selectLogBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Search:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
<tabstop>selectLogBox</tabstop>
|
||||
<tabstop>btnReload</tabstop>
|
||||
<tabstop>btnCopy</tabstop>
|
||||
<tabstop>btnPaste</tabstop>
|
||||
<tabstop>btnDelete</tabstop>
|
||||
<tabstop>btnClean</tabstop>
|
||||
<tabstop>text</tabstop>
|
||||
<tabstop>searchBar</tabstop>
|
||||
<tabstop>findButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
23
launcher/ui/pages/instance/ResourcePackPage.h
Normal file
23
launcher/ui/pages/instance/ResourcePackPage.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModFolderPage.h"
|
||||
#include "ui_ModFolderPage.h"
|
||||
|
||||
class ResourcePackPage : public ModFolderPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0)
|
||||
: ModFolderPage(instance, instance->resourcePackList(), "resourcepacks",
|
||||
"resourcepacks", tr("Resource packs"), "Resource-packs", parent)
|
||||
{
|
||||
ui->actionView_configs->setVisible(false);
|
||||
}
|
||||
virtual ~ResourcePackPage() {}
|
||||
|
||||
virtual bool shouldDisplay() const override
|
||||
{
|
||||
return !m_inst->traits().contains("no-texturepacks") &&
|
||||
!m_inst->traits().contains("texturepacks");
|
||||
}
|
||||
};
|
423
launcher/ui/pages/instance/ScreenshotsPage.cpp
Normal file
423
launcher/ui/pages/instance/ScreenshotsPage.cpp
Normal file
@ -0,0 +1,423 @@
|
||||
#include "ScreenshotsPage.h"
|
||||
#include "ui_ScreenshotsPage.h"
|
||||
|
||||
#include <QModelIndex>
|
||||
#include <QMutableListIterator>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QFileIconProvider>
|
||||
#include <QFileSystemModel>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QLineEdit>
|
||||
#include <QEvent>
|
||||
#include <QPainter>
|
||||
#include <QClipboard>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
#include "net/NetJob.h"
|
||||
#include "screenshots/ImgurUpload.h"
|
||||
#include "screenshots/ImgurAlbumCreation.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
|
||||
#include "RWStorage.h"
|
||||
#include <FileSystem.h>
|
||||
#include <DesktopServices.h>
|
||||
|
||||
typedef RWStorage<QString, QIcon> SharedIconCache;
|
||||
typedef std::shared_ptr<SharedIconCache> SharedIconCachePtr;
|
||||
|
||||
class ThumbnailingResult : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
inline void emitResultsReady(const QString &path) { emit resultsReady(path); }
|
||||
inline void emitResultsFailed(const QString &path) { emit resultsFailed(path); }
|
||||
signals:
|
||||
void resultsReady(const QString &path);
|
||||
void resultsFailed(const QString &path);
|
||||
};
|
||||
|
||||
class ThumbnailRunnable : public QRunnable
|
||||
{
|
||||
public:
|
||||
ThumbnailRunnable(QString path, SharedIconCachePtr cache)
|
||||
{
|
||||
m_path = path;
|
||||
m_cache = cache;
|
||||
}
|
||||
void run()
|
||||
{
|
||||
QFileInfo info(m_path);
|
||||
if (info.isDir())
|
||||
return;
|
||||
if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0))
|
||||
return;
|
||||
int tries = 5;
|
||||
while (tries)
|
||||
{
|
||||
if (!m_cache->stale(m_path))
|
||||
return;
|
||||
QImage image(m_path);
|
||||
if (image.isNull())
|
||||
{
|
||||
QThread::msleep(500);
|
||||
tries--;
|
||||
continue;
|
||||
}
|
||||
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();
|
||||
|
||||
QIcon icon(QPixmap::fromImage(square));
|
||||
m_cache->add(m_path, icon);
|
||||
m_resultEmitter.emitResultsReady(m_path);
|
||||
return;
|
||||
}
|
||||
m_resultEmitter.emitResultsFailed(m_path);
|
||||
}
|
||||
QString m_path;
|
||||
SharedIconCachePtr m_cache;
|
||||
ThumbnailingResult m_resultEmitter;
|
||||
};
|
||||
|
||||
// this is about as elegant and well written as a bag of bricks with scribbles done by insane
|
||||
// asylum patients.
|
||||
class FilterModel : public QIdentityProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FilterModel(QObject *parent = 0) : QIdentityProxyModel(parent)
|
||||
{
|
||||
m_thumbnailingPool.setMaxThreadCount(4);
|
||||
m_thumbnailCache = std::make_shared<SharedIconCache>();
|
||||
m_thumbnailCache->add("placeholder", APPLICATION->getThemedIcon("screenshot-placeholder"));
|
||||
connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
// FIXME: the watched file set is not updated when files are removed
|
||||
}
|
||||
virtual ~FilterModel() { m_thumbnailingPool.waitForDone(500); }
|
||||
virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const
|
||||
{
|
||||
auto model = sourceModel();
|
||||
if (!model)
|
||||
return QVariant();
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||
{
|
||||
QVariant result = sourceModel()->data(mapToSource(proxyIndex), role);
|
||||
return result.toString().remove(QRegExp("\\.png$"));
|
||||
}
|
||||
if (role == Qt::DecorationRole)
|
||||
{
|
||||
QVariant result =
|
||||
sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole);
|
||||
QString filePath = result.toString();
|
||||
QIcon temp;
|
||||
if (!watched.contains(filePath))
|
||||
{
|
||||
((QFileSystemWatcher &)watcher).addPath(filePath);
|
||||
((QSet<QString> &)watched).insert(filePath);
|
||||
}
|
||||
if (m_thumbnailCache->get(filePath, temp))
|
||||
{
|
||||
return temp;
|
||||
}
|
||||
if (!m_failed.contains(filePath))
|
||||
{
|
||||
((FilterModel *)this)->thumbnailImage(filePath);
|
||||
}
|
||||
return (m_thumbnailCache->get("placeholder"));
|
||||
}
|
||||
return sourceModel()->data(mapToSource(proxyIndex), role);
|
||||
}
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value,
|
||||
int role = Qt::EditRole)
|
||||
{
|
||||
auto model = sourceModel();
|
||||
if (!model)
|
||||
return false;
|
||||
if (role != Qt::EditRole)
|
||||
return false;
|
||||
// FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't
|
||||
// sort after renames
|
||||
{
|
||||
((QFileSystemModel *)model)->setNameFilterDisables(true);
|
||||
((QFileSystemModel *)model)->setNameFilterDisables(false);
|
||||
}
|
||||
return model->setData(mapToSource(index), value.toString() + ".png", role);
|
||||
}
|
||||
|
||||
private:
|
||||
void thumbnailImage(QString path)
|
||||
{
|
||||
auto runnable = new ThumbnailRunnable(path, m_thumbnailCache);
|
||||
connect(&(runnable->m_resultEmitter), SIGNAL(resultsReady(QString)),
|
||||
SLOT(thumbnailReady(QString)));
|
||||
connect(&(runnable->m_resultEmitter), SIGNAL(resultsFailed(QString)),
|
||||
SLOT(thumbnailFailed(QString)));
|
||||
((QThreadPool &)m_thumbnailingPool).start(runnable);
|
||||
}
|
||||
private slots:
|
||||
void thumbnailReady(QString path) { emit layoutChanged(); }
|
||||
void thumbnailFailed(QString path) { m_failed.insert(path); }
|
||||
void fileChanged(QString filepath)
|
||||
{
|
||||
m_thumbnailCache->setStale(filepath);
|
||||
thumbnailImage(filepath);
|
||||
// reinsert the path...
|
||||
watcher.removePath(filepath);
|
||||
watcher.addPath(filepath);
|
||||
}
|
||||
|
||||
private:
|
||||
SharedIconCachePtr m_thumbnailCache;
|
||||
QThreadPool m_thumbnailingPool;
|
||||
QSet<QString> m_failed;
|
||||
QSet<QString> watched;
|
||||
QFileSystemWatcher watcher;
|
||||
};
|
||||
|
||||
class CenteredEditingDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit CenteredEditingDelegate(QObject *parent = 0) : QStyledItemDelegate(parent) {}
|
||||
virtual ~CenteredEditingDelegate() {}
|
||||
virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
auto widget = QStyledItemDelegate::createEditor(parent, option, index);
|
||||
auto foo = dynamic_cast<QLineEdit *>(widget);
|
||||
if (foo)
|
||||
{
|
||||
foo->setAlignment(Qt::AlignHCenter);
|
||||
foo->setFrame(true);
|
||||
foo->setMaximumWidth(192);
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
};
|
||||
|
||||
ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)
|
||||
: QMainWindow(parent), ui(new Ui::ScreenshotsPage)
|
||||
{
|
||||
m_model.reset(new QFileSystemModel());
|
||||
m_filterModel.reset(new FilterModel());
|
||||
m_filterModel->setSourceModel(m_model.get());
|
||||
m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable);
|
||||
m_model->setReadOnly(false);
|
||||
m_model->setNameFilters({"*.png"});
|
||||
m_model->setNameFilterDisables(false);
|
||||
m_folder = path;
|
||||
m_valid = FS::ensureFolderPathExists(m_folder);
|
||||
|
||||
ui->setupUi(this);
|
||||
ui->toolBar->insertSpacer(ui->actionView_Folder);
|
||||
|
||||
ui->listView->setIconSize(QSize(128, 128));
|
||||
ui->listView->setGridSize(QSize(192, 160));
|
||||
ui->listView->setSpacing(9);
|
||||
// ui->listView->setUniformItemSizes(true);
|
||||
ui->listView->setLayoutMode(QListView::Batched);
|
||||
ui->listView->setViewMode(QListView::IconMode);
|
||||
ui->listView->setResizeMode(QListView::Adjust);
|
||||
ui->listView->installEventFilter(this);
|
||||
ui->listView->setEditTriggers(0);
|
||||
ui->listView->setItemDelegate(new CenteredEditingDelegate(this));
|
||||
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu);
|
||||
connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex)));
|
||||
}
|
||||
|
||||
bool ScreenshotsPage::eventFilter(QObject *obj, QEvent *evt)
|
||||
{
|
||||
if (obj != ui->listView)
|
||||
return QWidget::eventFilter(obj, evt);
|
||||
if (evt->type() != QEvent::KeyPress)
|
||||
{
|
||||
return QWidget::eventFilter(obj, evt);
|
||||
}
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(evt);
|
||||
switch (keyEvent->key())
|
||||
{
|
||||
case Qt::Key_Delete:
|
||||
on_actionDelete_triggered();
|
||||
return true;
|
||||
case Qt::Key_F2:
|
||||
on_actionRename_triggered();
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QWidget::eventFilter(obj, evt);
|
||||
}
|
||||
|
||||
ScreenshotsPage::~ScreenshotsPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ScreenshotsPage::ShowContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
menu->exec(ui->listView->mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
|
||||
QMenu * ScreenshotsPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
void ScreenshotsPage::onItemActivated(QModelIndex index)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return;
|
||||
auto info = m_model->fileInfo(index);
|
||||
QString fileName = info.absoluteFilePath();
|
||||
DesktopServices::openFile(info.absoluteFilePath());
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionView_Folder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(m_folder, true);
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionUpload_triggered()
|
||||
{
|
||||
auto selection = ui->listView->selectionModel()->selectedRows();
|
||||
if (selection.isEmpty())
|
||||
return;
|
||||
|
||||
QList<ScreenShot::Ptr> uploaded;
|
||||
auto job = NetJob::Ptr(new NetJob("Screenshot Upload"));
|
||||
if(selection.size() < 2)
|
||||
{
|
||||
auto item = selection.at(0);
|
||||
auto info = m_model->fileInfo(item);
|
||||
auto screenshot = std::make_shared<ScreenShot>(info);
|
||||
job->addNetAction(ImgurUpload::make(screenshot));
|
||||
|
||||
m_uploadActive = true;
|
||||
ProgressDialog dialog(this);
|
||||
if(dialog.execWithTask(job.get()) != QDialog::Accepted)
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"),
|
||||
tr("Unknown error"), QMessageBox::Warning)->exec();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto link = screenshot->m_url;
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setText(link);
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Upload finished"),
|
||||
tr("The <a href=\"%1\">link to the uploaded screenshot</a> has been placed in your clipboard.")
|
||||
.arg(link),
|
||||
QMessageBox::Information
|
||||
)->exec();
|
||||
}
|
||||
|
||||
m_uploadActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto item : selection)
|
||||
{
|
||||
auto info = m_model->fileInfo(item);
|
||||
auto screenshot = std::make_shared<ScreenShot>(info);
|
||||
uploaded.push_back(screenshot);
|
||||
job->addNetAction(ImgurUpload::make(screenshot));
|
||||
}
|
||||
SequentialTask task;
|
||||
auto albumTask = NetJob::Ptr(new NetJob("Imgur Album Creation"));
|
||||
auto imgurAlbum = ImgurAlbumCreation::make(uploaded);
|
||||
albumTask->addNetAction(imgurAlbum);
|
||||
task.addTask(job);
|
||||
task.addTask(albumTask);
|
||||
m_uploadActive = true;
|
||||
ProgressDialog prog(this);
|
||||
if (prog.execWithTask(&task) != QDialog::Accepted)
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"),
|
||||
tr("Unknown error"), QMessageBox::Warning)->exec();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto link = QString("https://imgur.com/a/%1").arg(imgurAlbum->id());
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setText(link);
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Upload finished"),
|
||||
tr("The <a href=\"%1\">link to the uploaded album</a> has been placed in your clipboard.") .arg(link),
|
||||
QMessageBox::Information
|
||||
)->exec();
|
||||
}
|
||||
m_uploadActive = false;
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionDelete_triggered()
|
||||
{
|
||||
auto mbox = CustomMessageBox::selectable(
|
||||
this, tr("Are you sure?"), tr("This will delete all selected screenshots."),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No);
|
||||
std::unique_ptr<QMessageBox> box(mbox);
|
||||
|
||||
if (box->exec() != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
auto selected = ui->listView->selectionModel()->selectedIndexes();
|
||||
for (auto item : selected)
|
||||
{
|
||||
m_model->remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenshotsPage::on_actionRename_triggered()
|
||||
{
|
||||
auto selection = ui->listView->selectionModel()->selectedIndexes();
|
||||
if (selection.isEmpty())
|
||||
return;
|
||||
ui->listView->edit(selection[0]);
|
||||
// TODO: mass renaming
|
||||
}
|
||||
|
||||
void ScreenshotsPage::openedImpl()
|
||||
{
|
||||
if(!m_valid)
|
||||
{
|
||||
m_valid = FS::ensureFolderPathExists(m_folder);
|
||||
}
|
||||
if (m_valid)
|
||||
{
|
||||
QString path = QDir(m_folder).absolutePath();
|
||||
auto idx = m_model->setRootPath(path);
|
||||
if(idx.isValid())
|
||||
{
|
||||
ui->listView->setModel(m_filterModel.get());
|
||||
ui->listView->setRootIndex(m_filterModel->mapFromSource(idx));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->listView->setModel(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "ScreenshotsPage.moc"
|
89
launcher/ui/pages/instance/ScreenshotsPage.h
Normal file
89
launcher/ui/pages/instance/ScreenshotsPage.h
Normal file
@ -0,0 +1,89 @@
|
||||
/* 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 <QMainWindow>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
class QFileSystemModel;
|
||||
class QIdentityProxyModel;
|
||||
namespace Ui
|
||||
{
|
||||
class ScreenshotsPage;
|
||||
}
|
||||
|
||||
struct ScreenShot;
|
||||
class ScreenshotList;
|
||||
class ImgurAlbumCreation;
|
||||
|
||||
class ScreenshotsPage : public QMainWindow, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ScreenshotsPage(QString path, QWidget *parent = 0);
|
||||
virtual ~ScreenshotsPage();
|
||||
|
||||
virtual void openedImpl() override;
|
||||
|
||||
enum
|
||||
{
|
||||
NothingDone = 0x42
|
||||
};
|
||||
|
||||
virtual bool eventFilter(QObject *, QEvent *) override;
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Screenshots");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("screenshots");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "screenshots";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Screenshots-management";
|
||||
}
|
||||
virtual bool apply() override
|
||||
{
|
||||
return !m_uploadActive;
|
||||
}
|
||||
|
||||
protected:
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
private slots:
|
||||
void on_actionUpload_triggered();
|
||||
void on_actionDelete_triggered();
|
||||
void on_actionRename_triggered();
|
||||
void on_actionView_Folder_triggered();
|
||||
void onItemActivated(QModelIndex);
|
||||
void ShowContextMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
Ui::ScreenshotsPage *ui;
|
||||
std::shared_ptr<QFileSystemModel> m_model;
|
||||
std::shared_ptr<QIdentityProxyModel> m_filterModel;
|
||||
QString m_folder;
|
||||
bool m_valid = false;
|
||||
bool m_uploadActive = false;
|
||||
};
|
87
launcher/ui/pages/instance/ScreenshotsPage.ui
Normal file
87
launcher/ui/pages/instance/ScreenshotsPage.ui
Normal file
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ScreenshotsPage</class>
|
||||
<widget class="QMainWindow" name="ScreenshotsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<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="QListView" name="listView">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="WideBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>RightToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionUpload"/>
|
||||
<addaction name="actionDelete"/>
|
||||
<addaction name="actionRename"/>
|
||||
<addaction name="actionView_Folder"/>
|
||||
</widget>
|
||||
<action name="actionUpload">
|
||||
<property name="text">
|
||||
<string>Upload</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDelete">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRename">
|
||||
<property name="text">
|
||||
<string>Rename</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionView_Folder">
|
||||
<property name="text">
|
||||
<string>View Folder</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>WideBar</class>
|
||||
<extends>QToolBar</extends>
|
||||
<header>ui/widgets/WideBar.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
768
launcher/ui/pages/instance/ServersPage.cpp
Normal file
768
launcher/ui/pages/instance/ServersPage.cpp
Normal file
@ -0,0 +1,768 @@
|
||||
#include "ServersPage.h"
|
||||
#include "ui_ServersPage.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <sstream>
|
||||
#include <io/stream_reader.h>
|
||||
#include <tag_string.h>
|
||||
#include <tag_primitive.h>
|
||||
#include <tag_list.h>
|
||||
#include <tag_compound.h>
|
||||
#include <minecraft/MinecraftInstance.h>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMenu>
|
||||
|
||||
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
|
||||
|
||||
struct Server
|
||||
{
|
||||
// Types
|
||||
enum class AcceptsTextures : int
|
||||
{
|
||||
ASK = 0,
|
||||
ALWAYS = 1,
|
||||
NEVER = 2
|
||||
};
|
||||
|
||||
// Methods
|
||||
Server()
|
||||
{
|
||||
m_name = QObject::tr("Minecraft Server");
|
||||
}
|
||||
Server(const QString & name, const QString & address)
|
||||
{
|
||||
m_name = name;
|
||||
m_address = address;
|
||||
}
|
||||
Server(nbt::tag_compound& server)
|
||||
{
|
||||
std::string addressStr(server["ip"]);
|
||||
m_address = QString::fromUtf8(addressStr.c_str());
|
||||
|
||||
std::string nameStr(server["name"]);
|
||||
m_name = QString::fromUtf8(nameStr.c_str());
|
||||
|
||||
if(server["icon"])
|
||||
{
|
||||
std::string base64str(server["icon"]);
|
||||
m_icon = QByteArray::fromBase64(base64str.c_str());
|
||||
}
|
||||
|
||||
if(server.has_key("acceptTextures", nbt::tag_type::Byte))
|
||||
{
|
||||
bool value = server["acceptTextures"].as<nbt::tag_byte>().get();
|
||||
if(value)
|
||||
{
|
||||
m_acceptsTextures = AcceptsTextures::ALWAYS;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_acceptsTextures = AcceptsTextures::NEVER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void serialize(nbt::tag_compound& server)
|
||||
{
|
||||
server.insert("name", m_name.trimmed().toUtf8().toStdString());
|
||||
server.insert("ip", m_address.trimmed().toUtf8().toStdString());
|
||||
if(m_icon.size())
|
||||
{
|
||||
server.insert("icon", m_icon.toBase64().toStdString());
|
||||
}
|
||||
if(m_acceptsTextures != AcceptsTextures::ASK)
|
||||
{
|
||||
server.insert("acceptTextures", nbt::tag_byte(m_acceptsTextures == AcceptsTextures::ALWAYS));
|
||||
}
|
||||
}
|
||||
|
||||
// Data - persistent and user changeable
|
||||
QString m_name;
|
||||
QString m_address;
|
||||
AcceptsTextures m_acceptsTextures = AcceptsTextures::ASK;
|
||||
|
||||
// Data - persistent and automatically updated
|
||||
QByteArray m_icon;
|
||||
|
||||
// Data - temporary
|
||||
bool m_checked = false;
|
||||
bool m_up = false;
|
||||
QString m_motd; // https://mctools.org/motd-creator
|
||||
int m_ping = 0;
|
||||
int m_currentPlayers = 0;
|
||||
int m_maxPlayers = 0;
|
||||
};
|
||||
|
||||
static std::unique_ptr <nbt::tag_compound> parseServersDat(const QString& filename)
|
||||
{
|
||||
try
|
||||
{
|
||||
QByteArray input = FS::read(filename);
|
||||
std::istringstream foo(std::string(input.constData(), input.size()));
|
||||
auto pair = nbt::io::read_compound(foo);
|
||||
|
||||
if(pair.first != "")
|
||||
return nullptr;
|
||||
|
||||
if(pair.second == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return std::move(pair.second);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static bool serializeServerDat(const QString& filename, nbt::tag_compound * levelInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!FS::ensureFilePathExists(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::ostringstream s;
|
||||
nbt::io::write_tag("", *levelInfo, s);
|
||||
QByteArray val(s.str().data(), (int) s.str().size() );
|
||||
FS::write(filename, val);
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ServersModel: public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
ServerPtrRole = Qt::UserRole,
|
||||
};
|
||||
explicit ServersModel(const QString &path, QObject *parent = 0)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
m_path = path;
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &ServersModel::fileChanged);
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &ServersModel::dirChanged);
|
||||
m_saveTimer.setSingleShot(true);
|
||||
m_saveTimer.setInterval(5000);
|
||||
connect(&m_saveTimer, &QTimer::timeout, this, &ServersModel::save_internal);
|
||||
}
|
||||
virtual ~ServersModel() {};
|
||||
|
||||
void observe()
|
||||
{
|
||||
if(m_observed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_observed = true;
|
||||
|
||||
if(!m_loaded)
|
||||
{
|
||||
load();
|
||||
}
|
||||
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
void unobserve()
|
||||
{
|
||||
if(!m_observed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_observed = false;
|
||||
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
void lock()
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
saveNow();
|
||||
|
||||
m_locked = true;
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
if(!m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_locked = false;
|
||||
|
||||
updateFSObserver();
|
||||
}
|
||||
|
||||
int addEmptyRow(int position)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if(position < 0 || position >= rowCount())
|
||||
{
|
||||
position = rowCount();
|
||||
}
|
||||
beginInsertRows(QModelIndex(), position, position);
|
||||
m_servers.insert(position, Server());
|
||||
endInsertRows();
|
||||
scheduleSave();
|
||||
return position;
|
||||
}
|
||||
|
||||
bool removeRow(int row)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(row < 0 || row >= rowCount())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_servers.removeAt(row);
|
||||
endRemoveRows(); // does absolutely nothing, the selected server stays as the next line...
|
||||
scheduleSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool moveUp(int row)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(row <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
beginMoveRows(QModelIndex(), row, row, QModelIndex(), row - 1);
|
||||
m_servers.swap(row-1, row);
|
||||
endMoveRows();
|
||||
scheduleSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool moveDown(int row)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int count = rowCount();
|
||||
if(row + 1 >= count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
beginMoveRows(QModelIndex(), row, row, QModelIndex(), row + 2);
|
||||
m_servers.swap(row+1, row);
|
||||
endMoveRows();
|
||||
scheduleSave();
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
|
||||
{
|
||||
if (section < 0 || section >= COLUMN_COUNT)
|
||||
return QVariant();
|
||||
|
||||
if(role == Qt::DisplayRole)
|
||||
{
|
||||
switch(section)
|
||||
{
|
||||
case 0:
|
||||
return tr("Name");
|
||||
case 1:
|
||||
return tr("Address");
|
||||
case 2:
|
||||
return tr("Latency");
|
||||
}
|
||||
}
|
||||
|
||||
return QAbstractListModel::headerData(section, orientation, role);
|
||||
}
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
int row = index.row();
|
||||
int column = index.column();
|
||||
if(column < 0 || column >= COLUMN_COUNT)
|
||||
return QVariant();
|
||||
|
||||
if (row < 0 || row >= m_servers.size())
|
||||
return QVariant();
|
||||
|
||||
switch(column)
|
||||
{
|
||||
case 0:
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DecorationRole:
|
||||
{
|
||||
auto & bytes = m_servers[row].m_icon;
|
||||
if(bytes.size())
|
||||
{
|
||||
QPixmap px;
|
||||
if(px.loadFromData(bytes))
|
||||
return QIcon(px);
|
||||
}
|
||||
return APPLICATION->getThemedIcon("unknown_server");
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return m_servers[row].m_name;
|
||||
case ServerPtrRole:
|
||||
return QVariant::fromValue<void *>((void *)&m_servers[row]);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case 1:
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return m_servers[row].m_address;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case 2:
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return m_servers[row].m_ping;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
return m_servers.size();
|
||||
}
|
||||
int columnCount(const QModelIndex & parent) const override
|
||||
{
|
||||
return COLUMN_COUNT;
|
||||
}
|
||||
|
||||
Server * at(int index)
|
||||
{
|
||||
if(index < 0 || index >= rowCount())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &m_servers[index];
|
||||
}
|
||||
|
||||
void setName(int row, const QString & name)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto server = at(row);
|
||||
if(!server || server->m_name == name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server->m_name = name;
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
void setAddress(int row, const QString & address)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto server = at(row);
|
||||
if(!server || server->m_address == address)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server->m_address = address;
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
void setAcceptsTextures(int row, Server::AcceptsTextures textures)
|
||||
{
|
||||
if(m_locked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto server = at(row);
|
||||
if(!server || server->m_acceptsTextures == textures)
|
||||
{
|
||||
return;
|
||||
}
|
||||
server->m_acceptsTextures = textures;
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
cancelSave();
|
||||
beginResetModel();
|
||||
QList<Server> servers;
|
||||
auto serversDat = parseServersDat(serversPath());
|
||||
if(serversDat)
|
||||
{
|
||||
auto &serversList = serversDat->at("servers").as<nbt::tag_list>();
|
||||
for(auto iter = serversList.begin(); iter != serversList.end(); iter++)
|
||||
{
|
||||
auto & serverTag = (*iter).as<nbt::tag_compound>();
|
||||
Server s(serverTag);
|
||||
servers.append(s);
|
||||
}
|
||||
}
|
||||
m_servers.swap(servers);
|
||||
m_loaded = true;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void saveNow()
|
||||
{
|
||||
if(saveIsScheduled())
|
||||
{
|
||||
save_internal();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public slots:
|
||||
void dirChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Changed:" << path;
|
||||
load();
|
||||
}
|
||||
void fileChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Changed:" << path;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void save_internal()
|
||||
{
|
||||
cancelSave();
|
||||
QString path = serversPath();
|
||||
qDebug() << "Server list about to be saved to" << path;
|
||||
|
||||
nbt::tag_compound out;
|
||||
nbt::tag_list list;
|
||||
for(auto & server: m_servers)
|
||||
{
|
||||
nbt::tag_compound serverNbt;
|
||||
server.serialize(serverNbt);
|
||||
list.push_back(std::move(serverNbt));
|
||||
}
|
||||
out.insert("servers", nbt::value(std::move(list)));
|
||||
|
||||
if(!serializeServerDat(path, &out))
|
||||
{
|
||||
qDebug() << "Failed to save server list:" << path << "Will try again.";
|
||||
scheduleSave();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void scheduleSave()
|
||||
{
|
||||
if(!m_loaded)
|
||||
{
|
||||
qDebug() << "Server list should never save if it didn't successfully load, path:" << m_path;
|
||||
return;
|
||||
}
|
||||
if(!m_dirty)
|
||||
{
|
||||
m_dirty = true;
|
||||
qDebug() << "Server list save is scheduled for" << m_path;
|
||||
}
|
||||
m_saveTimer.start();
|
||||
}
|
||||
|
||||
void cancelSave()
|
||||
{
|
||||
m_dirty = false;
|
||||
m_saveTimer.stop();
|
||||
}
|
||||
|
||||
bool saveIsScheduled() const
|
||||
{
|
||||
return m_dirty;
|
||||
}
|
||||
|
||||
void updateFSObserver()
|
||||
{
|
||||
bool observingFS = m_watcher->directories().contains(m_path);
|
||||
if(m_observed && m_locked)
|
||||
{
|
||||
if(!observingFS)
|
||||
{
|
||||
qWarning() << "Will watch" << m_path;
|
||||
if(!m_watcher->addPath(m_path))
|
||||
{
|
||||
qWarning() << "Failed to start watching" << m_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(observingFS)
|
||||
{
|
||||
qWarning() << "Will stop watching" << m_path;
|
||||
if(!m_watcher->removePath(m_path))
|
||||
{
|
||||
qWarning() << "Failed to stop watching" << m_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString serversPath()
|
||||
{
|
||||
QFileInfo foo(FS::PathCombine(m_path, "servers.dat"));
|
||||
return foo.filePath();
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_loaded = false;
|
||||
bool m_locked = false;
|
||||
bool m_observed = false;
|
||||
bool m_dirty = false;
|
||||
QString m_path;
|
||||
QList<Server> m_servers;
|
||||
QFileSystemWatcher *m_watcher = nullptr;
|
||||
QTimer m_saveTimer;
|
||||
};
|
||||
|
||||
ServersPage::ServersPage(InstancePtr inst, QWidget* parent)
|
||||
: QMainWindow(parent), ui(new Ui::ServersPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
m_inst = inst;
|
||||
m_model = new ServersModel(inst->gameRoot(), this);
|
||||
ui->serversView->setIconSize(QSize(64,64));
|
||||
ui->serversView->setModel(m_model);
|
||||
ui->serversView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->serversView, &QTreeView::customContextMenuRequested, this, &ServersPage::ShowContextMenu);
|
||||
|
||||
auto head = ui->serversView->header();
|
||||
if(head->count())
|
||||
{
|
||||
head->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
for(int i = 1; i < head->count(); i++)
|
||||
{
|
||||
head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||
}
|
||||
}
|
||||
|
||||
auto selectionModel = ui->serversView->selectionModel();
|
||||
connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged);
|
||||
connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed);
|
||||
connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited);
|
||||
connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited);
|
||||
connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int)));
|
||||
connect(m_model, &QAbstractItemModel::rowsRemoved, this, &ServersPage::rowsRemoved);
|
||||
|
||||
m_locked = m_inst->isRunning();
|
||||
if(m_locked)
|
||||
{
|
||||
m_model->lock();
|
||||
}
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
ServersPage::~ServersPage()
|
||||
{
|
||||
m_model->saveNow();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ServersPage::ShowContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
menu->exec(ui->serversView->mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
|
||||
QMenu * ServersPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
void ServersPage::on_RunningState_changed(bool running)
|
||||
{
|
||||
if(m_locked == running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_locked = running;
|
||||
if(m_locked)
|
||||
{
|
||||
m_model->lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_model->unlock();
|
||||
}
|
||||
updateState();
|
||||
}
|
||||
|
||||
void ServersPage::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
int nextServer = -1;
|
||||
if (!current.isValid())
|
||||
{
|
||||
nextServer = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextServer = current.row();
|
||||
}
|
||||
currentServer = nextServer;
|
||||
updateState();
|
||||
}
|
||||
|
||||
// WARNING: this is here because currentChanged is not accurate when removing rows. the current item needs to be fixed up after removal.
|
||||
void ServersPage::rowsRemoved(const QModelIndex& parent, int first, int last)
|
||||
{
|
||||
if(currentServer < first)
|
||||
{
|
||||
// current was before the removal
|
||||
return;
|
||||
}
|
||||
else if(currentServer >= first && currentServer <= last)
|
||||
{
|
||||
// current got removed...
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// current was past the removal
|
||||
int count = last - first + 1;
|
||||
currentServer -= count;
|
||||
}
|
||||
}
|
||||
|
||||
void ServersPage::nameEdited(const QString& name)
|
||||
{
|
||||
m_model->setName(currentServer, name);
|
||||
}
|
||||
|
||||
void ServersPage::addressEdited(const QString& address)
|
||||
{
|
||||
m_model->setAddress(currentServer, address);
|
||||
}
|
||||
|
||||
void ServersPage::resourceIndexChanged(int index)
|
||||
{
|
||||
auto acceptsTextures = Server::AcceptsTextures(index);
|
||||
m_model->setAcceptsTextures(currentServer, acceptsTextures);
|
||||
}
|
||||
|
||||
void ServersPage::updateState()
|
||||
{
|
||||
auto server = m_model->at(currentServer);
|
||||
|
||||
bool serverEditEnabled = server && !m_locked;
|
||||
ui->addressLine->setEnabled(serverEditEnabled);
|
||||
ui->nameLine->setEnabled(serverEditEnabled);
|
||||
ui->resourceComboBox->setEnabled(serverEditEnabled);
|
||||
ui->actionMove_Down->setEnabled(serverEditEnabled);
|
||||
ui->actionMove_Up->setEnabled(serverEditEnabled);
|
||||
ui->actionRemove->setEnabled(serverEditEnabled);
|
||||
ui->actionJoin->setEnabled(serverEditEnabled);
|
||||
|
||||
if(server)
|
||||
{
|
||||
ui->addressLine->setText(server->m_address);
|
||||
ui->nameLine->setText(server->m_name);
|
||||
ui->resourceComboBox->setCurrentIndex(int(server->m_acceptsTextures));
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->addressLine->setText(QString());
|
||||
ui->nameLine->setText(QString());
|
||||
ui->resourceComboBox->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
ui->actionAdd->setDisabled(m_locked);
|
||||
}
|
||||
|
||||
void ServersPage::openedImpl()
|
||||
{
|
||||
m_model->observe();
|
||||
}
|
||||
|
||||
void ServersPage::closedImpl()
|
||||
{
|
||||
m_model->unobserve();
|
||||
}
|
||||
|
||||
void ServersPage::on_actionAdd_triggered()
|
||||
{
|
||||
int position = m_model->addEmptyRow(currentServer + 1);
|
||||
if(position < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// select the new row
|
||||
ui->serversView->selectionModel()->setCurrentIndex(
|
||||
m_model->index(position),
|
||||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear | QItemSelectionModel::Rows
|
||||
);
|
||||
currentServer = position;
|
||||
}
|
||||
|
||||
void ServersPage::on_actionRemove_triggered()
|
||||
{
|
||||
m_model->removeRow(currentServer);
|
||||
}
|
||||
|
||||
void ServersPage::on_actionMove_Up_triggered()
|
||||
{
|
||||
if(m_model->moveUp(currentServer))
|
||||
{
|
||||
currentServer --;
|
||||
}
|
||||
}
|
||||
|
||||
void ServersPage::on_actionMove_Down_triggered()
|
||||
{
|
||||
if(m_model->moveDown(currentServer))
|
||||
{
|
||||
currentServer ++;
|
||||
}
|
||||
}
|
||||
|
||||
void ServersPage::on_actionJoin_triggered()
|
||||
{
|
||||
const auto &address = m_model->at(currentServer)->m_address;
|
||||
APPLICATION->launch(m_inst, true, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
|
||||
}
|
||||
|
||||
#include "ServersPage.moc"
|
94
launcher/ui/pages/instance/ServersPage.h
Normal file
94
launcher/ui/pages/instance/ServersPage.h
Normal file
@ -0,0 +1,94 @@
|
||||
/* 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 <QMainWindow>
|
||||
#include <QString>
|
||||
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ServersPage;
|
||||
}
|
||||
|
||||
struct Server;
|
||||
class ServersModel;
|
||||
class MinecraftInstance;
|
||||
|
||||
class ServersPage : public QMainWindow, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ServersPage(InstancePtr inst, QWidget *parent = 0);
|
||||
virtual ~ServersPage();
|
||||
|
||||
void openedImpl() override;
|
||||
void closedImpl() override;
|
||||
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Servers");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("unknown_server");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "servers";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Servers-management";
|
||||
}
|
||||
|
||||
protected:
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
private:
|
||||
void updateState();
|
||||
void scheduleSave();
|
||||
bool saveIsScheduled() const;
|
||||
|
||||
private slots:
|
||||
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void rowsRemoved(const QModelIndex &parent, int first, int last);
|
||||
|
||||
void on_actionAdd_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
void on_actionMove_Up_triggered();
|
||||
void on_actionMove_Down_triggered();
|
||||
void on_actionJoin_triggered();
|
||||
|
||||
void on_RunningState_changed(bool running);
|
||||
|
||||
void nameEdited(const QString & name);
|
||||
void addressEdited(const QString & address);
|
||||
void resourceIndexChanged(int index);\
|
||||
|
||||
void ShowContextMenu(const QPoint &pos);
|
||||
|
||||
private: // data
|
||||
int currentServer = -1;
|
||||
bool m_locked = true;
|
||||
Ui::ServersPage *ui = nullptr;
|
||||
ServersModel * m_model = nullptr;
|
||||
InstancePtr m_inst = nullptr;
|
||||
};
|
||||
|
194
launcher/ui/pages/instance/ServersPage.ui
Normal file
194
launcher/ui/pages/instance/ServersPage.ui
Normal file
@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ServersPage</class>
|
||||
<widget class="QMainWindow" name="ServersPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1318</width>
|
||||
<height>879</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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="QTreeView" name="serversView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="nameLabel">
|
||||
<property name="text">
|
||||
<string>&Name</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>nameLine</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="nameLine"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="addressLabel">
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>addressLine</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="addressLine"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="resourcesLabel">
|
||||
<property name="text">
|
||||
<string>Reso&urces</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>resourceComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="resourceComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ask to download</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always download</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Never download</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="WideBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
<property name="allowedAreas">
|
||||
<set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
<property name="floatable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>RightToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAdd"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionMove_Up"/>
|
||||
<addaction name="actionMove_Down"/>
|
||||
<addaction name="actionJoin"/>
|
||||
</widget>
|
||||
<action name="actionAdd">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMove_Up">
|
||||
<property name="text">
|
||||
<string>Move Up</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMove_Down">
|
||||
<property name="text">
|
||||
<string>Move Down</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionJoin">
|
||||
<property name="text">
|
||||
<string>Join</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>WideBar</class>
|
||||
<extends>QToolBar</extends>
|
||||
<header>ui/widgets/WideBar.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>serversView</tabstop>
|
||||
<tabstop>nameLine</tabstop>
|
||||
<tabstop>addressLine</tabstop>
|
||||
<tabstop>resourceComboBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
22
launcher/ui/pages/instance/ShaderPackPage.h
Normal file
22
launcher/ui/pages/instance/ShaderPackPage.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModFolderPage.h"
|
||||
#include "ui_ModFolderPage.h"
|
||||
|
||||
class ShaderPackPage : public ModFolderPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ShaderPackPage(MinecraftInstance *instance, QWidget *parent = 0)
|
||||
: ModFolderPage(instance, instance->shaderPackList(), "shaderpacks",
|
||||
"shaderpacks", tr("Shader packs"), "Resource-packs", parent)
|
||||
{
|
||||
ui->actionView_configs->setVisible(false);
|
||||
}
|
||||
virtual ~ShaderPackPage() {}
|
||||
|
||||
virtual bool shouldDisplay() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
22
launcher/ui/pages/instance/TexturePackPage.h
Normal file
22
launcher/ui/pages/instance/TexturePackPage.h
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "ModFolderPage.h"
|
||||
#include "ui_ModFolderPage.h"
|
||||
|
||||
class TexturePackPage : public ModFolderPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0)
|
||||
: ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks",
|
||||
tr("Texture packs"), "Texture-packs", parent)
|
||||
{
|
||||
ui->actionView_configs->setVisible(false);
|
||||
}
|
||||
virtual ~TexturePackPage() {}
|
||||
|
||||
virtual bool shouldDisplay() const override
|
||||
{
|
||||
return m_inst->traits().contains("texturepacks");
|
||||
}
|
||||
};
|
641
launcher/ui/pages/instance/VersionPage.cpp
Normal file
641
launcher/ui/pages/instance/VersionPage.cpp
Normal file
@ -0,0 +1,641 @@
|
||||
/* 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 "Application.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QLabel>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QMenu>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QMessageBox>
|
||||
#include <QListView>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "VersionPage.h"
|
||||
#include "ui_VersionPage.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/VersionSelectDialog.h"
|
||||
#include "ui/dialogs/NewComponentDialog.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
||||
#include "ui/GuiUtil.h"
|
||||
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "minecraft/auth/AccountList.h"
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "icons/IconList.h"
|
||||
#include "Exception.h"
|
||||
#include "Version.h"
|
||||
#include "DesktopServices.h"
|
||||
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
class IconProxy : public QIdentityProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget)
|
||||
{
|
||||
connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone);
|
||||
m_parentWidget = parentWidget;
|
||||
}
|
||||
|
||||
virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override
|
||||
{
|
||||
QVariant var = QIdentityProxyModel::data(proxyIndex, role);
|
||||
int column = proxyIndex.column();
|
||||
if(column == 0 && role == Qt::DecorationRole && m_parentWidget)
|
||||
{
|
||||
if(!var.isNull())
|
||||
{
|
||||
auto string = var.toString();
|
||||
if(string == "warning")
|
||||
{
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
}
|
||||
else if(string == "error")
|
||||
{
|
||||
return APPLICATION->getThemedIcon("status-bad");
|
||||
}
|
||||
}
|
||||
return APPLICATION->getThemedIcon("status-good");
|
||||
}
|
||||
return var;
|
||||
}
|
||||
private slots:
|
||||
void widgetGone()
|
||||
{
|
||||
m_parentWidget = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
QWidget *m_parentWidget = nullptr;
|
||||
};
|
||||
|
||||
QIcon VersionPage::icon() const
|
||||
{
|
||||
return APPLICATION->icons()->getIcon(m_inst->iconKey());
|
||||
}
|
||||
bool VersionPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QMenu * VersionPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
|
||||
: QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->toolBar->insertSpacer(ui->actionReload);
|
||||
|
||||
m_profile = m_inst->getPackProfile();
|
||||
|
||||
reloadPackProfile();
|
||||
|
||||
auto proxy = new IconProxy(ui->packageView);
|
||||
proxy->setSourceModel(m_profile.get());
|
||||
|
||||
m_filterModel = new QSortFilterProxyModel();
|
||||
m_filterModel->setDynamicSortFilter(true);
|
||||
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
m_filterModel->setSourceModel(proxy);
|
||||
m_filterModel->setFilterKeyColumn(-1);
|
||||
|
||||
ui->packageView->setModel(m_filterModel);
|
||||
ui->packageView->installEventFilter(this);
|
||||
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
|
||||
auto smodel = ui->packageView->selectionModel();
|
||||
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
|
||||
|
||||
connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
|
||||
controlsEnabled = !m_inst->isRunning();
|
||||
updateVersionControls();
|
||||
preselect(0);
|
||||
connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus);
|
||||
connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
|
||||
connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
|
||||
}
|
||||
|
||||
VersionPage::~VersionPage()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void VersionPage::showContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
menu->exec(ui->packageView->mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
|
||||
void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
if (!current.isValid())
|
||||
{
|
||||
ui->frame->clear();
|
||||
return;
|
||||
}
|
||||
int row = current.row();
|
||||
auto patch = m_profile->getComponent(row);
|
||||
auto severity = patch->getProblemSeverity();
|
||||
switch(severity)
|
||||
{
|
||||
case ProblemSeverity::Warning:
|
||||
ui->frame->setModText(tr("%1 possibly has issues.").arg(patch->getName()));
|
||||
break;
|
||||
case ProblemSeverity::Error:
|
||||
ui->frame->setModText(tr("%1 has issues!").arg(patch->getName()));
|
||||
break;
|
||||
default:
|
||||
case ProblemSeverity::None:
|
||||
ui->frame->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto &problems = patch->getProblems();
|
||||
QString problemOut;
|
||||
for (auto &problem: problems)
|
||||
{
|
||||
if(problem.m_severity == ProblemSeverity::Error)
|
||||
{
|
||||
problemOut += tr("Error: ");
|
||||
}
|
||||
else if(problem.m_severity == ProblemSeverity::Warning)
|
||||
{
|
||||
problemOut += tr("Warning: ");
|
||||
}
|
||||
problemOut += problem.m_description;
|
||||
problemOut += "\n";
|
||||
}
|
||||
ui->frame->setModDescription(problemOut);
|
||||
}
|
||||
|
||||
void VersionPage::updateRunningStatus(bool running)
|
||||
{
|
||||
if(controlsEnabled == running) {
|
||||
controlsEnabled = !running;
|
||||
updateVersionControls();
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::updateVersionControls()
|
||||
{
|
||||
// FIXME: this is a dirty hack
|
||||
auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft"));
|
||||
|
||||
bool supportsFabric = minecraftVersion >= Version("1.14");
|
||||
ui->actionInstall_Fabric->setEnabled(controlsEnabled && supportsFabric);
|
||||
|
||||
bool supportsForge = minecraftVersion <= Version("1.16.5");
|
||||
ui->actionInstall_Forge->setEnabled(controlsEnabled && supportsForge);
|
||||
|
||||
bool supportsLiteLoader = minecraftVersion <= Version("1.12.2");
|
||||
ui->actionInstall_LiteLoader->setEnabled(controlsEnabled && supportsLiteLoader);
|
||||
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void VersionPage::updateButtons(int row)
|
||||
{
|
||||
if(row == -1)
|
||||
row = currentRow();
|
||||
auto patch = m_profile->getComponent(row);
|
||||
ui->actionRemove->setEnabled(controlsEnabled && patch && patch->isRemovable());
|
||||
ui->actionMove_down->setEnabled(controlsEnabled && patch && patch->isMoveable());
|
||||
ui->actionMove_up->setEnabled(controlsEnabled && patch && patch->isMoveable());
|
||||
ui->actionChange_version->setEnabled(controlsEnabled && patch && patch->isVersionChangeable());
|
||||
ui->actionEdit->setEnabled(controlsEnabled && patch && patch->isCustom());
|
||||
ui->actionCustomize->setEnabled(controlsEnabled && patch && patch->isCustomizable());
|
||||
ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible());
|
||||
ui->actionDownload_All->setEnabled(controlsEnabled);
|
||||
ui->actionAdd_Empty->setEnabled(controlsEnabled);
|
||||
ui->actionReload->setEnabled(controlsEnabled);
|
||||
ui->actionInstall_mods->setEnabled(controlsEnabled);
|
||||
ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled);
|
||||
ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled);
|
||||
}
|
||||
|
||||
bool VersionPage::reloadPackProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_profile->reload(Net::Mode::Online);
|
||||
return true;
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), e.cause());
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Couldn't load the instance profile."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::on_actionReload_triggered()
|
||||
{
|
||||
reloadPackProfile();
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionRemove_triggered()
|
||||
{
|
||||
if (ui->packageView->currentIndex().isValid())
|
||||
{
|
||||
// FIXME: use actual model, not reloading.
|
||||
if (!m_profile->remove(ui->packageView->currentIndex().row()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Couldn't remove file"));
|
||||
}
|
||||
}
|
||||
updateButtons();
|
||||
reloadPackProfile();
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionInstall_mods_triggered()
|
||||
{
|
||||
if(m_container)
|
||||
{
|
||||
m_container->selectPage("mods");
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
|
||||
{
|
||||
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
|
||||
if(!list.empty())
|
||||
{
|
||||
m_profile->installJarMods(list);
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionReplace_Minecraft_jar_triggered()
|
||||
{
|
||||
auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), APPLICATION->settings()->get("CentralModsDir").toString(), this->parentWidget());
|
||||
if(!jarPath.isEmpty())
|
||||
{
|
||||
m_profile->installCustomJar(jarPath);
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionMove_up_triggered()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_profile->move(currentRow(), PackProfile::MoveUp);
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), e.cause());
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionMove_down_triggered()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_profile->move(currentRow(), PackProfile::MoveDown);
|
||||
}
|
||||
catch (const Exception &e)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), e.cause());
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionChange_version_triggered()
|
||||
{
|
||||
auto versionRow = currentRow();
|
||||
if(versionRow == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto patch = m_profile->getComponent(versionRow);
|
||||
auto name = patch->getName();
|
||||
auto list = patch->getVersionList();
|
||||
if(!list)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto uid = list->uid();
|
||||
// FIXME: this is a horrible HACK. Get version filtering information from the actual metadata...
|
||||
if(uid == "net.minecraftforge")
|
||||
{
|
||||
on_actionInstall_Forge_triggered();
|
||||
return;
|
||||
}
|
||||
else if (uid == "com.mumfrey.liteloader")
|
||||
{
|
||||
on_actionInstall_LiteLoader_triggered();
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
|
||||
if (uid == "net.fabricmc.intermediary")
|
||||
{
|
||||
vselect.setEmptyString(tr("No intermediary mappings versions are currently available."));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the intermediary mappings version lists!"));
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
}
|
||||
auto currentVersion = patch->getVersion();
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
if (!vselect.exec() || !vselect.selectedVersion())
|
||||
return;
|
||||
|
||||
qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor();
|
||||
bool important = false;
|
||||
if(uid == "net.minecraft")
|
||||
{
|
||||
important = true;
|
||||
}
|
||||
m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important);
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionDownload_All_triggered()
|
||||
{
|
||||
if (!APPLICATION->accounts()->anyAccountIsValid())
|
||||
{
|
||||
CustomMessageBox::selectable(
|
||||
this, tr("Error"),
|
||||
tr("MultiMC cannot download Minecraft or update instances unless you have at least "
|
||||
"one account added.\nPlease add your Mojang or Minecraft account."),
|
||||
QMessageBox::Warning)->show();
|
||||
return;
|
||||
}
|
||||
|
||||
auto updateTask = m_inst->createUpdateTask(Net::Mode::Online);
|
||||
if (!updateTask)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ProgressDialog tDialog(this);
|
||||
connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
|
||||
// FIXME: unused return value
|
||||
tDialog.execWithTask(updateTask.get());
|
||||
updateButtons();
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionInstall_Forge_triggered()
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge");
|
||||
if(!vlist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this);
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!"));
|
||||
|
||||
auto currentVersion = m_profile->getComponentVersion("net.minecraftforge");
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
auto vsn = vselect.selectedVersion();
|
||||
m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor());
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
// m_profile->installVersion();
|
||||
preselect(m_profile->rowCount(QModelIndex())-1);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::on_actionInstall_Fabric_triggered()
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("net.fabricmc.fabric-loader");
|
||||
if(!vlist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this);
|
||||
vselect.setEmptyString(tr("No Fabric Loader versions are currently available."));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!"));
|
||||
|
||||
auto currentVersion = m_profile->getComponentVersion("net.fabricmc.fabric-loader");
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
auto vsn = vselect.selectedVersion();
|
||||
m_profile->setComponentVersion("net.fabricmc.fabric-loader", vsn->descriptor());
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
preselect(m_profile->rowCount(QModelIndex())-1);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::on_actionAdd_Empty_triggered()
|
||||
{
|
||||
NewComponentDialog compdialog(QString(), QString(), this);
|
||||
QStringList blacklist;
|
||||
for(int i = 0; i < m_profile->rowCount(); i++)
|
||||
{
|
||||
auto comp = m_profile->getComponent(i);
|
||||
blacklist.push_back(comp->getID());
|
||||
}
|
||||
compdialog.setBlacklist(blacklist);
|
||||
if (compdialog.exec())
|
||||
{
|
||||
qDebug() << "name:" << compdialog.name();
|
||||
qDebug() << "uid:" << compdialog.uid();
|
||||
m_profile->installEmpty(compdialog.uid(), compdialog.name());
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::on_actionInstall_LiteLoader_triggered()
|
||||
{
|
||||
auto vlist = APPLICATION->metadataIndex()->get("com.mumfrey.liteloader");
|
||||
if(!vlist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this);
|
||||
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
|
||||
vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!"));
|
||||
|
||||
auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader");
|
||||
if(!currentVersion.isEmpty())
|
||||
{
|
||||
vselect.setCurrentVersion(currentVersion);
|
||||
}
|
||||
|
||||
if (vselect.exec() && vselect.selectedVersion())
|
||||
{
|
||||
auto vsn = vselect.selectedVersion();
|
||||
m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
|
||||
m_profile->resolve(Net::Mode::Online);
|
||||
// m_profile->installVersion(vselect.selectedVersion());
|
||||
preselect(m_profile->rowCount(QModelIndex())-1);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
}
|
||||
|
||||
void VersionPage::on_actionLibrariesFolder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true);
|
||||
}
|
||||
|
||||
void VersionPage::on_actionMinecraftFolder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(m_inst->gameRoot(), true);
|
||||
}
|
||||
|
||||
void VersionPage::versionCurrent(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
currentIdx = current.row();
|
||||
updateButtons(currentIdx);
|
||||
}
|
||||
|
||||
void VersionPage::preselect(int row)
|
||||
{
|
||||
if(row < 0)
|
||||
{
|
||||
row = 0;
|
||||
}
|
||||
if(row >= m_profile->rowCount(QModelIndex()))
|
||||
{
|
||||
row = m_profile->rowCount(QModelIndex()) - 1;
|
||||
}
|
||||
if(row < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto model_index = m_profile->index(row);
|
||||
ui->packageView->selectionModel()->select(model_index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
updateButtons(row);
|
||||
}
|
||||
|
||||
void VersionPage::onGameUpdateError(QString error)
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Error updating instance"), error, QMessageBox::Warning)->show();
|
||||
}
|
||||
|
||||
Component * VersionPage::current()
|
||||
{
|
||||
auto row = currentRow();
|
||||
if(row < 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return m_profile->getComponent(row);
|
||||
}
|
||||
|
||||
int VersionPage::currentRow()
|
||||
{
|
||||
if (ui->packageView->selectionModel()->selectedRows().isEmpty())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return ui->packageView->selectionModel()->selectedRows().first().row();
|
||||
}
|
||||
|
||||
void VersionPage::on_actionCustomize_triggered()
|
||||
{
|
||||
auto version = currentRow();
|
||||
if(version == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto patch = m_profile->getComponent(version);
|
||||
if(!patch->getVersionFile())
|
||||
{
|
||||
// TODO: wait for the update task to finish here...
|
||||
return;
|
||||
}
|
||||
if(!m_profile->customize(version))
|
||||
{
|
||||
// TODO: some error box here
|
||||
}
|
||||
updateButtons();
|
||||
preselect(currentIdx);
|
||||
}
|
||||
|
||||
void VersionPage::on_actionEdit_triggered()
|
||||
{
|
||||
auto version = current();
|
||||
if(!version)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto filename = version->getFilename();
|
||||
if(!QFileInfo::exists(filename))
|
||||
{
|
||||
qWarning() << "file" << filename << "can't be opened for editing, doesn't exist!";
|
||||
return;
|
||||
}
|
||||
APPLICATION->openJsonEditor(filename);
|
||||
}
|
||||
|
||||
void VersionPage::on_actionRevert_triggered()
|
||||
{
|
||||
auto version = currentRow();
|
||||
if(version == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!m_profile->revertToBase(version))
|
||||
{
|
||||
// TODO: some error box here
|
||||
}
|
||||
updateButtons();
|
||||
preselect(currentIdx);
|
||||
m_container->refreshContainer();
|
||||
}
|
||||
|
||||
void VersionPage::onFilterTextChanged(const QString &newContents)
|
||||
{
|
||||
m_filterModel->setFilterFixedString(newContents);
|
||||
}
|
||||
|
||||
#include "VersionPage.moc"
|
||||
|
104
launcher/ui/pages/instance/VersionPage.h
Normal file
104
launcher/ui/pages/instance/VersionPage.h
Normal file
@ -0,0 +1,104 @@
|
||||
/* 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 <QMainWindow>
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class VersionPage;
|
||||
}
|
||||
|
||||
class VersionPage : public QMainWindow, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0);
|
||||
virtual ~VersionPage();
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Version");
|
||||
}
|
||||
virtual QIcon icon() const override;
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "version";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Instance-Version";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
private slots:
|
||||
void on_actionChange_version_triggered();
|
||||
void on_actionInstall_Forge_triggered();
|
||||
void on_actionInstall_Fabric_triggered();
|
||||
void on_actionAdd_Empty_triggered();
|
||||
void on_actionInstall_LiteLoader_triggered();
|
||||
void on_actionReload_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
void on_actionMove_up_triggered();
|
||||
void on_actionMove_down_triggered();
|
||||
void on_actionAdd_to_Minecraft_jar_triggered();
|
||||
void on_actionReplace_Minecraft_jar_triggered();
|
||||
void on_actionRevert_triggered();
|
||||
void on_actionEdit_triggered();
|
||||
void on_actionInstall_mods_triggered();
|
||||
void on_actionCustomize_triggered();
|
||||
void on_actionDownload_All_triggered();
|
||||
|
||||
void on_actionMinecraftFolder_triggered();
|
||||
void on_actionLibrariesFolder_triggered();
|
||||
|
||||
void updateVersionControls();
|
||||
|
||||
private:
|
||||
Component * current();
|
||||
int currentRow();
|
||||
void updateButtons(int row = -1);
|
||||
void preselect(int row = 0);
|
||||
int doUpdate();
|
||||
|
||||
protected:
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
/// FIXME: this shouldn't be necessary!
|
||||
bool reloadPackProfile();
|
||||
|
||||
private:
|
||||
Ui::VersionPage *ui;
|
||||
QSortFilterProxyModel *m_filterModel;
|
||||
std::shared_ptr<PackProfile> m_profile;
|
||||
MinecraftInstance *m_inst;
|
||||
int currentIdx = 0;
|
||||
bool controlsEnabled = false;
|
||||
|
||||
public slots:
|
||||
void versionCurrent(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
|
||||
private slots:
|
||||
void updateRunningStatus(bool running);
|
||||
void onGameUpdateError(QString error);
|
||||
void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void showContextMenu(const QPoint &pos);
|
||||
void onFilterTextChanged(const QString & newContents);
|
||||
};
|
285
launcher/ui/pages/instance/VersionPage.ui
Normal file
285
launcher/ui/pages/instance/VersionPage.ui
Normal file
@ -0,0 +1,285 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>VersionPage</class>
|
||||
<widget class="QMainWindow" name="VersionPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>961</width>
|
||||
<height>1091</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<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>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="ModListView" name="packageView">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOn</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="filterEdit">
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="filterLabel">
|
||||
<property name="text">
|
||||
<string>Filter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="MCModInfoFrame" name="frame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="WideBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
<property name="allowedAreas">
|
||||
<set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
<property name="floatable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>RightToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionChange_version"/>
|
||||
<addaction name="actionMove_up"/>
|
||||
<addaction name="actionMove_down"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCustomize"/>
|
||||
<addaction name="actionEdit"/>
|
||||
<addaction name="actionRevert"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionInstall_Forge"/>
|
||||
<addaction name="actionInstall_Fabric"/>
|
||||
<addaction name="actionInstall_LiteLoader"/>
|
||||
<addaction name="actionInstall_mods"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAdd_to_Minecraft_jar"/>
|
||||
<addaction name="actionReplace_Minecraft_jar"/>
|
||||
<addaction name="actionAdd_Empty"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionMinecraftFolder"/>
|
||||
<addaction name="actionLibrariesFolder"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionReload"/>
|
||||
<addaction name="actionDownload_All"/>
|
||||
</widget>
|
||||
<action name="actionChange_version">
|
||||
<property name="text">
|
||||
<string>Change version</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Change version of the selected package.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMove_up">
|
||||
<property name="text">
|
||||
<string>Move up</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Make the selected package apply sooner.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMove_down">
|
||||
<property name="text">
|
||||
<string>Move down</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Make the selected package apply later.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remove selected package from the instance.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCustomize">
|
||||
<property name="text">
|
||||
<string>Customize</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Customize selected package.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEdit">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Edit selected package.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRevert">
|
||||
<property name="text">
|
||||
<string>Revert</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Revert the selected package to default.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionInstall_Forge">
|
||||
<property name="text">
|
||||
<string>Install Forge</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Install the Minecraft Forge package.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionInstall_Fabric">
|
||||
<property name="text">
|
||||
<string>Install Fabric</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Install the Fabric Loader package.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionInstall_LiteLoader">
|
||||
<property name="text">
|
||||
<string>Install LiteLoader</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Install the LiteLoader package.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionInstall_mods">
|
||||
<property name="text">
|
||||
<string>Install mods</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Install normal mods.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAdd_to_Minecraft_jar">
|
||||
<property name="text">
|
||||
<string>Add to Minecraft.jar</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add a mod into the Minecraft jar file.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReplace_Minecraft_jar">
|
||||
<property name="text">
|
||||
<string>Replace Minecraft.jar</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAdd_Empty">
|
||||
<property name="text">
|
||||
<string>Add Empty</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Add an empty custom package.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReload">
|
||||
<property name="text">
|
||||
<string>Reload</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Reload all packages.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDownload_All">
|
||||
<property name="text">
|
||||
<string>Download All</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Download the files needed to launch the instance now.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMinecraftFolder">
|
||||
<property name="text">
|
||||
<string>Open .minecraft</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the instance's .minecraft folder.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionLibrariesFolder">
|
||||
<property name="text">
|
||||
<string>Open libraries</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open the instance's local libraries folder.</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ModListView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>ui/widgets/ModListView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MCModInfoFrame</class>
|
||||
<extends>QFrame</extends>
|
||||
<header>ui/widgets/MCModInfoFrame.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>WideBar</class>
|
||||
<extends>QToolBar</extends>
|
||||
<header>ui/widgets/WideBar.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
412
launcher/ui/pages/instance/WorldListPage.cpp
Normal file
412
launcher/ui/pages/instance/WorldListPage.cpp
Normal file
@ -0,0 +1,412 @@
|
||||
/* Copyright 2015-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 "WorldListPage.h"
|
||||
#include "ui_WorldListPage.h"
|
||||
#include "minecraft/WorldList.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QMenu>
|
||||
#include <QKeyEvent>
|
||||
#include <QClipboard>
|
||||
#include <QMessageBox>
|
||||
#include <QTreeView>
|
||||
#include <QInputDialog>
|
||||
#include <QProcess>
|
||||
|
||||
#include "tools/MCEditTool.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include "ui/GuiUtil.h"
|
||||
#include "DesktopServices.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
|
||||
class WorldListProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WorldListProxyModel(QObject *parent) : QSortFilterProxyModel(parent) {}
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
|
||||
{
|
||||
QModelIndex sourceIndex = mapToSource(index);
|
||||
|
||||
if (index.column() == 0 && role == Qt::DecorationRole)
|
||||
{
|
||||
WorldList *worlds = qobject_cast<WorldList *>(sourceModel());
|
||||
auto iconFile = worlds->data(sourceIndex, WorldList::IconFileRole).toString();
|
||||
if(iconFile.isNull()) {
|
||||
// NOTE: Minecraft uses the same placeholder for servers AND worlds
|
||||
return APPLICATION->getThemedIcon("unknown_server");
|
||||
}
|
||||
return QIcon(iconFile);
|
||||
}
|
||||
|
||||
return sourceIndex.data(role);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QWidget *parent)
|
||||
: QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->toolBar->insertSpacer(ui->actionRefresh);
|
||||
|
||||
WorldListProxyModel * proxy = new WorldListProxyModel(this);
|
||||
proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
proxy->setSourceModel(m_worlds.get());
|
||||
ui->worldTreeView->setSortingEnabled(true);
|
||||
ui->worldTreeView->setModel(proxy);
|
||||
ui->worldTreeView->installEventFilter(this);
|
||||
ui->worldTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
ui->worldTreeView->setIconSize(QSize(64,64));
|
||||
connect(ui->worldTreeView, &QTreeView::customContextMenuRequested, this, &WorldListPage::ShowContextMenu);
|
||||
|
||||
auto head = ui->worldTreeView->header();
|
||||
head->setSectionResizeMode(0, QHeaderView::Stretch);
|
||||
head->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
|
||||
connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged);
|
||||
worldChanged(QModelIndex(), QModelIndex());
|
||||
}
|
||||
|
||||
void WorldListPage::openedImpl()
|
||||
{
|
||||
m_worlds->startWatching();
|
||||
}
|
||||
|
||||
void WorldListPage::closedImpl()
|
||||
{
|
||||
m_worlds->stopWatching();
|
||||
}
|
||||
|
||||
WorldListPage::~WorldListPage()
|
||||
{
|
||||
m_worlds->stopWatching();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void WorldListPage::ShowContextMenu(const QPoint& pos)
|
||||
{
|
||||
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
|
||||
menu->exec(ui->worldTreeView->mapToGlobal(pos));
|
||||
delete menu;
|
||||
}
|
||||
|
||||
QMenu * WorldListPage::createPopupMenu()
|
||||
{
|
||||
QMenu* filteredMenu = QMainWindow::createPopupMenu();
|
||||
filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
|
||||
return filteredMenu;
|
||||
}
|
||||
|
||||
bool WorldListPage::shouldDisplay() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WorldListPage::worldListFilter(QKeyEvent *keyEvent)
|
||||
{
|
||||
switch (keyEvent->key())
|
||||
{
|
||||
case Qt::Key_Delete:
|
||||
on_actionRemove_triggered();
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QWidget::eventFilter(ui->worldTreeView, keyEvent);
|
||||
}
|
||||
|
||||
bool WorldListPage::eventFilter(QObject *obj, QEvent *ev)
|
||||
{
|
||||
if (ev->type() != QEvent::KeyPress)
|
||||
{
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
}
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
|
||||
if (obj == ui->worldTreeView)
|
||||
return worldListFilter(keyEvent);
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionRemove_triggered()
|
||||
{
|
||||
auto proxiedIndex = getSelectedWorld();
|
||||
|
||||
if(!proxiedIndex.isValid())
|
||||
return;
|
||||
|
||||
auto result = QMessageBox::question(this,
|
||||
tr("Are you sure?"),
|
||||
tr("This will remove the selected world permenantly.\n"
|
||||
"The world will be gone forever (A LONG TIME).\n"
|
||||
"\n"
|
||||
"Do you want to continue?"));
|
||||
if(result != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_worlds->stopWatching();
|
||||
m_worlds->deleteWorld(proxiedIndex.row());
|
||||
m_worlds->startWatching();
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionView_Folder_triggered()
|
||||
{
|
||||
DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true);
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionDatapacks_triggered()
|
||||
{
|
||||
QModelIndex index = getSelectedWorld();
|
||||
|
||||
if (!index.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
return;
|
||||
|
||||
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
|
||||
|
||||
DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true);
|
||||
}
|
||||
|
||||
|
||||
void WorldListPage::on_actionReset_Icon_triggered()
|
||||
{
|
||||
auto proxiedIndex = getSelectedWorld();
|
||||
|
||||
if(!proxiedIndex.isValid())
|
||||
return;
|
||||
|
||||
if(m_worlds->resetIcon(proxiedIndex.row())) {
|
||||
ui->actionReset_Icon->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QModelIndex WorldListPage::getSelectedWorld()
|
||||
{
|
||||
auto index = ui->worldTreeView->selectionModel()->currentIndex();
|
||||
|
||||
auto proxy = (QSortFilterProxyModel *) ui->worldTreeView->model();
|
||||
return proxy->mapToSource(index);
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionCopy_Seed_triggered()
|
||||
{
|
||||
QModelIndex index = getSelectedWorld();
|
||||
|
||||
if (!index.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
int64_t seed = m_worlds->data(index, WorldList::SeedRole).toLongLong();
|
||||
APPLICATION->clipboard()->setText(QString::number(seed));
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionMCEdit_triggered()
|
||||
{
|
||||
if(m_mceditStarting)
|
||||
return;
|
||||
|
||||
auto mcedit = APPLICATION->mcedit();
|
||||
|
||||
const QString mceditPath = mcedit->path();
|
||||
|
||||
QModelIndex index = getSelectedWorld();
|
||||
|
||||
if (!index.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
return;
|
||||
|
||||
auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
|
||||
|
||||
auto program = mcedit->getProgramPath();
|
||||
if(program.size())
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
if(!QProcess::startDetached(program, {fullPath}, mceditPath))
|
||||
{
|
||||
mceditError();
|
||||
}
|
||||
#else
|
||||
m_mceditProcess.reset(new LoggedProcess());
|
||||
m_mceditProcess->setDetachable(true);
|
||||
connect(m_mceditProcess.get(), &LoggedProcess::stateChanged, this, &WorldListPage::mceditState);
|
||||
m_mceditProcess->start(program, {fullPath});
|
||||
m_mceditProcess->setWorkingDirectory(mceditPath);
|
||||
m_mceditStarting = true;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::warning(
|
||||
this->parentWidget(),
|
||||
tr("No MCEdit found or set up!"),
|
||||
tr("You do not have MCEdit set up or it was moved.\nYou can set it up in the global settings.")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldListPage::mceditError()
|
||||
{
|
||||
QMessageBox::warning(
|
||||
this->parentWidget(),
|
||||
tr("MCEdit failed to start!"),
|
||||
tr("MCEdit failed to start.\nIt may be necessary to reinstall it.")
|
||||
);
|
||||
}
|
||||
|
||||
void WorldListPage::mceditState(LoggedProcess::State state)
|
||||
{
|
||||
bool failed = false;
|
||||
switch(state)
|
||||
{
|
||||
case LoggedProcess::NotRunning:
|
||||
case LoggedProcess::Starting:
|
||||
return;
|
||||
case LoggedProcess::FailedToStart:
|
||||
case LoggedProcess::Crashed:
|
||||
case LoggedProcess::Aborted:
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
case LoggedProcess::Running:
|
||||
case LoggedProcess::Finished:
|
||||
{
|
||||
m_mceditStarting = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(failed)
|
||||
{
|
||||
mceditError();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldListPage::worldChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
QModelIndex index = getSelectedWorld();
|
||||
bool enable = index.isValid();
|
||||
ui->actionCopy_Seed->setEnabled(enable);
|
||||
ui->actionMCEdit->setEnabled(enable);
|
||||
ui->actionRemove->setEnabled(enable);
|
||||
ui->actionCopy->setEnabled(enable);
|
||||
ui->actionRename->setEnabled(enable);
|
||||
ui->actionDatapacks->setEnabled(enable);
|
||||
bool hasIcon = !index.data(WorldList::IconFileRole).isNull();
|
||||
ui->actionReset_Icon->setEnabled(enable && hasIcon);
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionAdd_triggered()
|
||||
{
|
||||
auto list = GuiUtil::BrowseForFiles(
|
||||
displayName(),
|
||||
tr("Select a Minecraft world zip"),
|
||||
tr("Minecraft World Zip File (*.zip)"), QString(), this->parentWidget());
|
||||
if (!list.empty())
|
||||
{
|
||||
m_worlds->stopWatching();
|
||||
for (auto filename : list)
|
||||
{
|
||||
m_worlds->installWorld(QFileInfo(filename));
|
||||
}
|
||||
m_worlds->startWatching();
|
||||
}
|
||||
}
|
||||
|
||||
bool WorldListPage::isWorldSafe(QModelIndex)
|
||||
{
|
||||
return !m_inst->isRunning();
|
||||
}
|
||||
|
||||
bool WorldListPage::worldSafetyNagQuestion()
|
||||
{
|
||||
if(!isWorldSafe(getSelectedWorld()))
|
||||
{
|
||||
auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?"));
|
||||
if(result == QMessageBox::No)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void WorldListPage::on_actionCopy_triggered()
|
||||
{
|
||||
QModelIndex index = getSelectedWorld();
|
||||
if (!index.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
return;
|
||||
|
||||
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
|
||||
auto world = (World *) worldVariant.value<void *>();
|
||||
bool ok = false;
|
||||
QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok);
|
||||
|
||||
if (ok && name.length() > 0)
|
||||
{
|
||||
world->install(m_worlds->dir().absolutePath(), name);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionRename_triggered()
|
||||
{
|
||||
QModelIndex index = getSelectedWorld();
|
||||
if (!index.isValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(!worldSafetyNagQuestion())
|
||||
return;
|
||||
|
||||
auto worldVariant = m_worlds->data(index, WorldList::ObjectRole);
|
||||
auto world = (World *) worldVariant.value<void *>();
|
||||
|
||||
bool ok = false;
|
||||
QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new world name."), QLineEdit::Normal, world->name(), &ok);
|
||||
|
||||
if (ok && name.length() > 0)
|
||||
{
|
||||
world->rename(name);
|
||||
}
|
||||
}
|
||||
|
||||
void WorldListPage::on_actionRefresh_triggered()
|
||||
{
|
||||
m_worlds->update();
|
||||
}
|
||||
|
||||
#include "WorldListPage.moc"
|
99
launcher/ui/pages/instance/WorldListPage.h
Normal file
99
launcher/ui/pages/instance/WorldListPage.h
Normal file
@ -0,0 +1,99 @@
|
||||
/* Copyright 2015-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 <QMainWindow>
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "ui/pages/BasePage.h"
|
||||
#include <Application.h>
|
||||
#include <LoggedProcess.h>
|
||||
|
||||
class WorldList;
|
||||
namespace Ui
|
||||
{
|
||||
class WorldListPage;
|
||||
}
|
||||
|
||||
class WorldListPage : public QMainWindow, public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WorldListPage(
|
||||
BaseInstance *inst,
|
||||
std::shared_ptr<WorldList> worlds,
|
||||
QWidget *parent = 0
|
||||
);
|
||||
virtual ~WorldListPage();
|
||||
|
||||
virtual QString displayName() const override
|
||||
{
|
||||
return tr("Worlds");
|
||||
}
|
||||
virtual QIcon icon() const override
|
||||
{
|
||||
return APPLICATION->getThemedIcon("worlds");
|
||||
}
|
||||
virtual QString id() const override
|
||||
{
|
||||
return "worlds";
|
||||
}
|
||||
virtual QString helpPage() const override
|
||||
{
|
||||
return "Worlds";
|
||||
}
|
||||
virtual bool shouldDisplay() const override;
|
||||
|
||||
virtual void openedImpl() override;
|
||||
virtual void closedImpl() override;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *ev) override;
|
||||
bool worldListFilter(QKeyEvent *ev);
|
||||
QMenu * createPopupMenu() override;
|
||||
|
||||
protected:
|
||||
BaseInstance *m_inst;
|
||||
|
||||
private:
|
||||
QModelIndex getSelectedWorld();
|
||||
bool isWorldSafe(QModelIndex index);
|
||||
bool worldSafetyNagQuestion();
|
||||
void mceditError();
|
||||
|
||||
private:
|
||||
Ui::WorldListPage *ui;
|
||||
std::shared_ptr<WorldList> m_worlds;
|
||||
unique_qobject_ptr<LoggedProcess> m_mceditProcess;
|
||||
bool m_mceditStarting = false;
|
||||
|
||||
private slots:
|
||||
void on_actionCopy_Seed_triggered();
|
||||
void on_actionMCEdit_triggered();
|
||||
void on_actionRemove_triggered();
|
||||
void on_actionAdd_triggered();
|
||||
void on_actionCopy_triggered();
|
||||
void on_actionRename_triggered();
|
||||
void on_actionRefresh_triggered();
|
||||
void on_actionView_Folder_triggered();
|
||||
void on_actionDatapacks_triggered();
|
||||
void on_actionReset_Icon_triggered();
|
||||
void worldChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void mceditState(LoggedProcess::State state);
|
||||
|
||||
void ShowContextMenu(const QPoint &pos);
|
||||
};
|
161
launcher/ui/pages/instance/WorldListPage.ui
Normal file
161
launcher/ui/pages/instance/WorldListPage.ui
Normal file
@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>WorldListPage</class>
|
||||
<widget class="QMainWindow" name="WorldListPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<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="QTreeView" name="worldTreeView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DragDrop</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="allColumnsShowFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="WideBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
<property name="allowedAreas">
|
||||
<set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextOnly</enum>
|
||||
</property>
|
||||
<property name="floatable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>RightToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<addaction name="actionAdd"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionRename"/>
|
||||
<addaction name="actionCopy"/>
|
||||
<addaction name="actionRemove"/>
|
||||
<addaction name="actionMCEdit"/>
|
||||
<addaction name="actionDatapacks"/>
|
||||
<addaction name="actionReset_Icon"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCopy_Seed"/>
|
||||
<addaction name="actionRefresh"/>
|
||||
<addaction name="actionView_Folder"/>
|
||||
</widget>
|
||||
<action name="actionAdd">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRename">
|
||||
<property name="text">
|
||||
<string>Rename</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCopy">
|
||||
<property name="text">
|
||||
<string>Copy</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRemove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMCEdit">
|
||||
<property name="text">
|
||||
<string>MCEdit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCopy_Seed">
|
||||
<property name="text">
|
||||
<string>Copy Seed</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRefresh">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionView_Folder">
|
||||
<property name="text">
|
||||
<string>View Folder</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReset_Icon">
|
||||
<property name="text">
|
||||
<string>Reset Icon</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remove world icon to make the game re-generate it on next load.</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDatapacks">
|
||||
<property name="text">
|
||||
<string>Datapacks</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Manage datapacks inside the world.</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>WideBar</class>
|
||||
<extends>QToolBar</extends>
|
||||
<header>ui/widgets/WideBar.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
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;
|
||||
};
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user