Merge remote-tracking branch 'upstream/develop' into data-packs

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
TheKodeToad
2025-03-24 21:11:46 +00:00
686 changed files with 15261 additions and 9719 deletions

View File

@ -34,14 +34,12 @@ DataPackPage::DataPackPage(MinecraftInstance* instance, std::shared_ptr<DataPack
ui->actionViewConfigs->setVisible(false);
}
bool DataPackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
void DataPackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
auto& dp = static_cast<DataPack&>(m_model->at(row));
ui->frame->updateWithDataPack(dp);
return true;
}
void DataPackPage::downloadDataPacks()
@ -52,7 +50,7 @@ void DataPackPage::downloadDataPacks()
ResourceDownload::DataPackDownloadDialog mdownload(this, std::static_pointer_cast<DataPackFolderModel>(m_model), m_instance);
if (mdownload.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Data Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
new ConcurrentTask("Download Data Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();

View File

@ -34,6 +34,6 @@ class DataPackPage : public ExternalResourcesPage {
bool shouldDisplay() const override { return true; }
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
void downloadDataPacks();
};

View File

@ -74,6 +74,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(ui->actionRemoveItem, &QAction::triggered, this, &ExternalResourcesPage::removeItem);
connect(ui->actionEnableItem, &QAction::triggered, this, &ExternalResourcesPage::enableItem);
connect(ui->actionDisableItem, &QAction::triggered, this, &ExternalResourcesPage::disableItem);
connect(ui->actionViewHomepage, &QAction::triggered, this, &ExternalResourcesPage::viewHomepage);
connect(ui->actionViewConfigs, &QAction::triggered, this, &ExternalResourcesPage::viewConfigs);
connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder);
@ -81,16 +82,28 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated);
auto selection_model = ui->treeView->selectionModel();
connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current);
connect(selection_model, &QItemSelectionModel::currentChanged, this, [this](const QModelIndex& current, const QModelIndex& previous) {
if (!current.isValid()) {
ui->frame->clear();
return;
}
updateFrame(current, previous);
});
auto updateExtra = [this]() {
if (updateExtraInfo)
updateExtraInfo(id(), extraHeaderInfoString());
};
connect(selection_model, &QItemSelectionModel::selectionChanged, this, updateExtra);
connect(model.get(), &ResourceFolderModel::updateFinished, this, updateExtra);
connect(model.get(), &ResourceFolderModel::parseFinished, this, updateExtra);
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
connect(selection_model, &QItemSelectionModel::selectionChanged, this, [this] { updateActions(); });
connect(m_model.get(), &ResourceFolderModel::rowsInserted, this, [this] { updateActions(); });
connect(m_model.get(), &ResourceFolderModel::rowsRemoved, this, [this] { updateActions(); });
auto viewHeader = ui->treeView->header();
viewHeader->setContextMenuPolicy(Qt::CustomContextMenu);
@ -99,6 +112,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared
m_model->loadColumns(ui->treeView);
connect(ui->treeView->header(), &QHeaderView::sectionResized, this, [this] { m_model->saveColumns(ui->treeView); });
connect(ui->filterEdit, &QLineEdit::textChanged, this, &ExternalResourcesPage::filterTextChanged);
}
ExternalResourcesPage::~ExternalResourcesPage()
@ -289,6 +303,16 @@ void ExternalResourcesPage::disableItem()
m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE);
}
void ExternalResourcesPage::viewHomepage()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
for (auto resource : m_model->selectedResources(selection)) {
auto url = resource->homepage();
if (!url.isEmpty())
DesktopServices::openUrl(url);
}
}
void ExternalResourcesPage::viewConfigs()
{
DesktopServices::openPath(m_instance->instanceConfigFolder(), true);
@ -299,23 +323,32 @@ void ExternalResourcesPage::viewFolder()
DesktopServices::openPath(m_model->dir().absolutePath(), true);
}
bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous)
void ExternalResourcesPage::updateActions()
{
if (!current.isValid()) {
ui->frame->clear();
return false;
}
const bool hasSelection = ui->treeView->selectionModel()->hasSelection();
const QModelIndexList selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
const QList<Resource*> selectedResources = m_model->selectedResources(selection);
return onSelectionChanged(current, previous);
ui->actionUpdateItem->setEnabled(!m_model->empty());
ui->actionResetItemMetadata->setEnabled(hasSelection);
ui->actionChangeVersion->setEnabled(selectedResources.size() == 1 && selectedResources[0]->metadata() != nullptr);
ui->actionRemoveItem->setEnabled(hasSelection);
ui->actionEnableItem->setEnabled(hasSelection);
ui->actionDisableItem->setEnabled(hasSelection);
ui->actionViewHomepage->setEnabled(hasSelection && std::any_of(selectedResources.begin(), selectedResources.end(),
[](Resource* resource) { return !resource->homepage().isEmpty(); }));
ui->actionExportMetadata->setEnabled(!m_model->empty());
}
bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
void ExternalResourcesPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
Resource const& resource = m_model->at(row);
ui->frame->updateWithResource(resource);
return true;
}
QString ExternalResourcesPage::extraHeaderInfoString()

View File

@ -43,9 +43,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
QMenu* createPopupMenu() override;
public slots:
bool current(const QModelIndex& current, const QModelIndex& previous);
virtual bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous);
virtual void updateActions();
virtual void updateFrame(const QModelIndex& current, const QModelIndex& previous);
protected slots:
void itemActivated(const QModelIndex& index);
@ -58,6 +57,8 @@ class ExternalResourcesPage : public QMainWindow, public BasePage {
virtual void enableItem();
virtual void disableItem();
virtual void viewHomepage();
virtual void viewFolder();
virtual void viewConfigs();

View File

@ -74,7 +74,7 @@
<string>Actions</string>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextOnly</enum>
<enum>Qt::ToolButtonIconOnly</enum>
</property>
<property name="useDefaultAction" stdset="0">
<bool>true</bool>
@ -90,39 +90,50 @@
<addaction name="actionRemoveItem"/>
<addaction name="actionEnableItem"/>
<addaction name="actionDisableItem"/>
<addaction name="separator"/>
<addaction name="actionViewHomepage"/>
<addaction name="actionViewConfigs"/>
<addaction name="actionViewFolder"/>
</widget>
<action name="actionAddItem">
<property name="text">
<string>&amp;Add</string>
<string>&amp;Add File</string>
</property>
<property name="toolTip">
<string>Add</string>
<string>Add a locally downloaded file.</string>
</property>
</action>
<action name="actionRemoveItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Remove</string>
</property>
<property name="toolTip">
<string>Remove selected item</string>
<string>Remove all selected items.</string>
</property>
</action>
<action name="actionEnableItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Enable</string>
</property>
<property name="toolTip">
<string>Enable selected item</string>
<string>Enable all selected items.</string>
</property>
</action>
<action name="actionDisableItem">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>&amp;Disable</string>
</property>
<property name="toolTip">
<string>Disable selected item</string>
<string>Disable all selected items.</string>
</property>
</action>
<action name="actionViewConfigs">
@ -137,6 +148,9 @@
<property name="text">
<string>View &amp;Folder</string>
</property>
<property name="toolTip">
<string>Open the folder in the system file manager.</string>
</property>
</action>
<action name="actionDownloadItem">
<property name="enabled">
@ -146,40 +160,70 @@
<string>&amp;Download</string>
</property>
<property name="toolTip">
<string>Download a new resource</string>
</property>
</action>
<action name="actionVisitItemPage">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Visit mod's page</string>
</property>
<property name="toolTip">
<string>Go to mods home page</string>
<string>Download resources from online mod platforms.</string>
</property>
</action>
<action name="actionUpdateItem">
<property name="enabled">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="text">
<string>Check for &amp;Updates</string>
</property>
<property name="toolTip">
<string>Try to check or update all selected resources (all resources if none are selected)</string>
<string>Try to check or update all selected resources (all resources if none are selected).</string>
</property>
</action>
<action name="actionResetItemMetadata">
<property name="text">
<string>Reset Update Metadata</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionVerifyItemDependencies">
<property name="text">
<string>Verify Dependencies</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionExportMetadata">
<property name="enabled">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="text">
<string>Export modlist</string>
<string>Export List</string>
</property>
<property name="toolTip">
<string>Export mod's metadata to text</string>
<string>Export resource's metadata to text.</string>
</property>
</action>
<action name="actionChangeVersion">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Change Version</string>
</property>
<property name="toolTip">
<string>Change a resource's version.</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionViewHomepage">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>View Homepage</string>
</property>
<property name="toolTip">
<string>View the homepages of all selected items.</string>
</property>
</action>
</widget>

View File

@ -1,589 +0,0 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* 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 "InstanceSettingsPage.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/WorldList.h"
#include "ui_InstanceSettingsPage.h"
#include <QDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <sys.h>
#include "ui/dialogs/VersionSelectDialog.h"
#include "ui/widgets/CustomCommands.h"
#include "Application.h"
#include "BuildConfig.h"
#include "JavaCommon.h"
#include "minecraft/auth/AccountList.h"
#include "FileSystem.h"
#include "java/JavaInstallList.h"
#include "java/JavaUtils.h"
InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent)
: QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst)
{
m_settings = inst->settings();
ui->setupUi(this);
connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked);
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings);
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&InstanceSettingsPage::changeInstanceAccount);
connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged);
connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged);
auto mInst = dynamic_cast<MinecraftInstance*>(inst);
m_world_quickplay_supported = mInst && mInst->traits().contains("feature:is_quick_play_singleplayer");
if (m_world_quickplay_supported) {
auto worlds = mInst->worldList();
worlds->update();
for (const auto& world : worlds->allWorlds()) {
ui->worldsCb->addItem(world.folderName());
}
} else {
ui->worldsCb->hide();
ui->worldJoinButton->hide();
ui->serverJoinAddressButton->setChecked(true);
ui->serverJoinAddress->setEnabled(true);
ui->serverJoinAddressButton->setStyleSheet("QRadioButton::indicator { width: 0px; height: 0px; }");
}
loadSettings();
updateThresholds();
}
InstanceSettingsPage::~InstanceSettingsPage()
{
delete ui;
}
void InstanceSettingsPage::globalSettingsButtonClicked(bool)
{
switch (ui->settingsTabs->currentIndex()) {
case 0:
APPLICATION->ShowGlobalSettings(this, "java-settings");
return;
case 2:
APPLICATION->ShowGlobalSettings(this, "custom-commands");
return;
case 3:
APPLICATION->ShowGlobalSettings(this, "environment-variables");
return;
default:
APPLICATION->ShowGlobalSettings(this, "minecraft-settings");
return;
}
}
bool InstanceSettingsPage::apply()
{
applySettings();
return true;
}
void InstanceSettingsPage::applySettings()
{
SettingsObject::Lock lock(m_settings);
// Miscellaneous
bool miscellaneous = ui->miscellaneousSettingsBox->isChecked();
m_settings->set("OverrideMiscellaneous", miscellaneous);
if (miscellaneous) {
m_settings->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
m_settings->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
} else {
m_settings->reset("CloseAfterLaunch");
m_settings->reset("QuitAfterGameStop");
}
// 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());
m_settings->set("IgnoreJavaCompatibility", ui->skipCompatibilityCheckbox->isChecked());
} else {
m_settings->reset("JavaPath");
m_settings->reset("IgnoreJavaCompatibility");
}
// Java arguments
bool javaArgs = ui->javaArgumentsGroupBox->isChecked();
m_settings->set("OverrideJavaArgs", javaArgs);
if (javaArgs) {
m_settings->set("JvmArgs", ui->jvmArgsTextBox->toPlainText().replace("\n", " "));
} 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");
}
// Environment Variables
auto env = ui->environmentVariables->override();
m_settings->set("OverrideEnv", env);
if (env)
m_settings->set("Env", ui->environmentVariables->value());
else
m_settings->reset("Env");
// Workarounds
bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();
m_settings->set("OverrideNativeWorkarounds", workarounds);
if (workarounds) {
m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
m_settings->set("CustomGLFWPath", ui->lineEditGLFWPath->text());
m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
m_settings->set("CustomOpenALPath", ui->lineEditOpenALPath->text());
} else {
m_settings->reset("UseNativeGLFW");
m_settings->reset("CustomGLFWPath");
m_settings->reset("UseNativeOpenAL");
m_settings->reset("CustomOpenALPath");
}
// Performance
bool performance = ui->perfomanceGroupBox->isChecked();
m_settings->set("OverridePerformance", performance);
if (performance) {
m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked());
m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked());
m_settings->set("UseZink", ui->useZink->isChecked());
} else {
m_settings->reset("EnableFeralGamemode");
m_settings->reset("EnableMangoHud");
m_settings->reset("UseDiscreteGpu");
m_settings->reset("UseZink");
}
// 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) {
if (ui->serverJoinAddressButton->isChecked() || !m_world_quickplay_supported) {
m_settings->set("JoinServerOnLaunchAddress", ui->serverJoinAddress->text());
m_settings->reset("JoinWorldOnLaunch");
} else {
m_settings->set("JoinWorldOnLaunch", ui->worldsCb->currentText());
m_settings->reset("JoinServerOnLaunchAddress");
}
} else {
m_settings->reset("JoinServerOnLaunchAddress");
m_settings->reset("JoinWorldOnLaunch");
}
// Use an account for this instance
bool useAccountForInstance = ui->instanceAccountGroupBox->isChecked();
m_settings->set("UseAccountForInstance", useAccountForInstance);
if (!useAccountForInstance) {
m_settings->reset("InstanceAccountId");
}
bool overrideLegacySettings = ui->legacySettingsGroupBox->isChecked();
m_settings->set("OverrideLegacySettings", overrideLegacySettings);
if (overrideLegacySettings) {
m_settings->set("OnlineFixes", ui->onlineFixes->isChecked());
} else {
m_settings->reset("OnlineFixes");
}
// FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext();
}
void InstanceSettingsPage::loadSettings()
{
// Miscellaneous
ui->miscellaneousSettingsBox->setChecked(m_settings->get("OverrideMiscellaneous").toBool());
ui->closeAfterLaunchCheck->setChecked(m_settings->get("CloseAfterLaunch").toBool());
ui->quitAfterGameStopCheck->setChecked(m_settings->get("QuitAfterGameStop").toBool());
// 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->skipCompatibilityCheckbox->setChecked(m_settings->get("IgnoreJavaCompatibility").toBool());
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());
// Environment variables
ui->environmentVariables->initialize(true, m_settings->get("OverrideEnv").toBool(), m_settings->get("Env").toMap());
// Workarounds
ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());
ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());
ui->lineEditGLFWPath->setText(m_settings->get("CustomGLFWPath").toString());
#ifdef Q_OS_LINUX
ui->lineEditGLFWPath->setPlaceholderText(APPLICATION->m_detectedGLFWPath);
#else
ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME));
#endif
ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool());
ui->lineEditOpenALPath->setText(m_settings->get("CustomOpenALPath").toString());
#ifdef Q_OS_LINUX
ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath);
#else
ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
#endif
// Performance
ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool());
ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool());
ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool());
ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool());
ui->useZink->setChecked(m_settings->get("UseZink").toBool());
#if !defined(Q_OS_LINUX)
ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false);
#endif
if (!(APPLICATION->capabilities() & Application::SupportsGameMode)) {
ui->enableFeralGamemodeCheck->setDisabled(true);
ui->enableFeralGamemodeCheck->setToolTip(tr("Feral Interactive's GameMode could not be found on your system."));
}
if (!(APPLICATION->capabilities() & Application::SupportsMangoHud)) {
ui->enableMangoHud->setDisabled(true);
ui->enableMangoHud->setToolTip(tr("MangoHud could not be found on your system."));
}
// 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());
if (auto server = m_settings->get("JoinServerOnLaunchAddress").toString(); !server.isEmpty()) {
ui->serverJoinAddress->setText(server);
ui->serverJoinAddressButton->setChecked(true);
ui->worldJoinButton->setChecked(false);
ui->serverJoinAddress->setEnabled(true);
ui->worldsCb->setEnabled(false);
} else if (auto world = m_settings->get("JoinWorldOnLaunch").toString(); !world.isEmpty() && m_world_quickplay_supported) {
ui->worldsCb->setCurrentText(world);
ui->serverJoinAddressButton->setChecked(false);
ui->worldJoinButton->setChecked(true);
ui->serverJoinAddress->setEnabled(false);
ui->worldsCb->setEnabled(true);
} else {
ui->serverJoinAddressButton->setChecked(true);
ui->worldJoinButton->setChecked(false);
ui->serverJoinAddress->setEnabled(true);
ui->worldsCb->setEnabled(false);
}
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
updateAccountsMenu();
ui->legacySettingsGroupBox->setChecked(m_settings->get("OverrideLegacySettings").toBool());
ui->onlineFixes->setChecked(m_settings->get("OnlineFixes").toBool());
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()
{
if (JavaUtils::getJavaCheckPath().isEmpty()) {
JavaCommon::javaCheckNotFound(this);
return;
}
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::onUseNativeGLFWChanged(bool checked)
{
ui->lineEditGLFWPath->setEnabled(checked);
}
void InstanceSettingsPage::onUseNativeOpenALChanged(bool checked)
{
ui->lineEditOpenALPath->setEnabled(checked);
}
void InstanceSettingsPage::updateAccountsMenu()
{
ui->instanceAccountSelector->clear();
auto accounts = APPLICATION->accounts();
int accountIndex = accounts->findAccountByProfileId(m_settings->get("InstanceAccountId").toString());
for (int i = 0; i < accounts->count(); i++) {
MinecraftAccountPtr account = accounts->at(i);
ui->instanceAccountSelector->addItem(getFaceForAccount(account), account->profileName(), i);
if (i == accountIndex)
ui->instanceAccountSelector->setCurrentIndex(i);
}
}
QIcon InstanceSettingsPage::getFaceForAccount(MinecraftAccountPtr account)
{
if (auto face = account->getFace(); !face.isNull()) {
return face;
}
return APPLICATION->getThemedIcon("noaccount");
}
void InstanceSettingsPage::changeInstanceAccount(int index)
{
auto accounts = APPLICATION->accounts();
if (index != -1 && accounts->at(index) && ui->instanceAccountGroupBox->isChecked()) {
auto account = accounts->at(index);
m_settings->set("InstanceAccountId", account->profileId());
}
}
void InstanceSettingsPage::on_maxMemSpinBox_valueChanged([[maybe_unused]] int i)
{
updateThresholds();
}
void InstanceSettingsPage::checkerFinished()
{
checker.reset();
}
void InstanceSettingsPage::retranslate()
{
ui->retranslateUi(this);
ui->customCommands->retranslate(); // TODO: why is this seperate from the others?
ui->environmentVariables->retranslate();
}
void InstanceSettingsPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
if (maxMem >= sysMiB) {
iconName = "status-bad";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation exceeds your system memory capacity."));
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
} else if (maxMem < minMem) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");
}
{
auto height = ui->labelMaxMemIcon->fontInfo().pixelSize();
QIcon icon = APPLICATION->getThemedIcon(iconName);
QPixmap pix = icon.pixmap(height, height);
ui->labelMaxMemIcon->setPixmap(pix);
}
}
void InstanceSettingsPage::on_serverJoinAddressButton_toggled(bool checked)
{
ui->serverJoinAddress->setEnabled(checked);
}
void InstanceSettingsPage::on_worldJoinButton_toggled(bool checked)
{
ui->worldsCb->setEnabled(checked);
}

View File

@ -35,62 +35,29 @@
#pragma once
#include <QWidget>
#include <QObjectPtr.h>
#include <QMenu>
#include "Application.h"
#include "BaseInstance.h"
#include "JavaCommon.h"
#include "java/JavaChecker.h"
#include "ui/pages/BasePage.h"
#include "ui/widgets/MinecraftSettingsWidget.h"
#include <QWidget>
class JavaChecker;
namespace Ui {
class InstanceSettingsPage;
}
class InstanceSettingsPage : public QWidget, public BasePage {
class InstanceSettingsPage : public MinecraftSettingsWidget, 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"; }
void retranslate() override;
void updateThresholds();
private slots:
void on_javaDetectBtn_clicked();
void on_javaTestBtn_clicked();
void on_javaBrowseBtn_clicked();
void on_maxMemSpinBox_valueChanged(int i);
void on_serverJoinAddressButton_toggled(bool checked);
void on_worldJoinButton_toggled(bool checked);
void onUseNativeGLFWChanged(bool checked);
void onUseNativeOpenALChanged(bool checked);
void applySettings();
void loadSettings();
void checkerFinished();
void globalSettingsButtonClicked(bool checked);
void updateAccountsMenu();
QIcon getFaceForAccount(MinecraftAccountPtr account);
void changeInstanceAccount(int index);
private:
Ui::InstanceSettingsPage* ui;
BaseInstance* m_instance;
SettingsObjectPtr m_settings;
unique_qobject_ptr<JavaCommon::TestCheck> checker;
bool m_world_quickplay_supported;
explicit InstanceSettingsPage(MinecraftInstancePtr instance, QWidget* parent = nullptr) : MinecraftSettingsWidget(std::move(instance), parent)
{
connect(APPLICATION, &Application::globalSettingsAboutToOpen, this, &InstanceSettingsPage::saveSettings);
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
}
~InstanceSettingsPage() override {}
QString displayName() const override { return tr("Settings"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("instance-settings"); }
QString id() const override { return "settings"; }
bool apply() override
{
saveSettings();
return true;
}
QString helpPage() const override { return "Instance-settings"; }
};

View File

@ -1,789 +0,0 @@
<?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="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="minecraftPage">
<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&amp;llation</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
<property name="text">
<string>Skip Java compatibility checks</string>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="javaBrowseBtn">
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="javaDetectBtn">
<property name="text">
<string>Auto-detect...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="javaTestBtn">
<property name="text">
<string>Test</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="memoryGroupBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Memor&amp;y</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="1,0,0,0">
<item row="2" column="0">
<widget class="QLabel" name="labelPermGen">
<property name="text">
<string>PermGen:</string>
</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="3" column="0" colspan="3">
<widget class="QLabel" name="labelPermgenNote">
<property name="text">
<string>Note: Permgen is set automatically by Java 8 and later</string>
</property>
</widget>
</item>
<item row="0" column="2">
<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>8</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>256</number>
</property>
</widget>
</item>
<item row="1" column="2">
<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>8</number>
</property>
<property name="maximum">
<number>1048576</number>
</property>
<property name="singleStep">
<number>128</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item row="2" column="2">
<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>4</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="1" column="3">
<widget class="QLabel" name="labelMaxMemIcon">
<property name="text">
<string notr="true"/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="buddy">
<cstring>maxMemSpinBox</cstring>
</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&amp;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="javaPage">
<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&amp;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>
<widget class="QGroupBox" name="miscellaneousSettingsBox">
<property name="title">
<string>Miscellaneous</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<widget class="QCheckBox" name="closeAfterLaunchCheck">
<property name="text">
<string>Close the launcher after game window opens</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="quitAfterGameStopCheck">
<property name="text">
<string>Quit the launcher after game window closes</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="customCommandsPage">
<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="environmentVariablesPage">
<attribute name="title">
<string>Environment variables</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_16">
<item>
<widget class="EnvironmentVariables" name="environmentVariables" 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="QGridLayout" name="gridLayout_3">
<item row="2" column="0">
<widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
<string>Use system installation of OpenAL</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelGLFWPath">
<property name="text">
<string>&amp;GLFW library path</string>
</property>
<property name="buddy">
<cstring>lineEditGLFWPath</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
<string>Use system installation of GLFW</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEditGLFWPath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelOpenALPath">
<property name="text">
<string>&amp;OpenAL library path</string>
</property>
<property name="buddy">
<cstring>lineEditOpenALPath</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="lineEditOpenALPath">
<property name="enabled">
<bool>false</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 class="QWidget" name="performancePage">
<attribute name="title">
<string>Performance</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QGroupBox" name="perfomanceGroupBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Performance</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<widget class="QCheckBox" name="enableFeralGamemodeCheck">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable Feral GameMode</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enableMangoHud">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable MangoHud</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useDiscreteGpuCheck">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use discrete GPU</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useZink">
<property name="toolTip">
<string>Use Zink, a Mesa OpenGL driver that implements OpenGL on top of Vulkan. Performance may vary depending on the situation. Note: If no suitable Vulkan driver is found, software rendering will be used.</string>
</property>
<property name="text">
<string>Use Zink</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<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="legacySettingsGroupBox">
<property name="title">
<string>Legacy settings</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<widget class="QCheckBox" name="onlineFixes">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Emulates usages of old online services which are no longer operating.&lt;/p&gt;&lt;p&gt;Current fixes include: skin and online mode support.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable online fixes (experimental)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<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 target to join on launch</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QRadioButton" name="serverJoinAddressButton">
<property name="text">
<string>Server address:</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="serverJoinAddress"/>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="worldJoinButton">
<property name="text">
<string>Singleplayer world</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="worldsCb"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="instanceAccountGroupBox">
<property name="title">
<string>Override default account</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<layout class="QGridLayout" name="instanceAccountLayout">
<item row="0" column="0">
<widget class="QLabel" name="instanceAccountNameLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Account:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="instanceAccountSelector"/>
</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>
<customwidget>
<class>EnvironmentVariables</class>
<extends>QWidget</extends>
<header>ui/widgets/EnvironmentVariables.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>openGlobalJavaSettingsButton</tabstop>
<tabstop>settingsTabs</tabstop>
<tabstop>javaSettingsGroupBox</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>

View File

@ -3,7 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2024 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -47,8 +47,8 @@
#include "launch/LaunchTask.h"
#include "settings/Setting.h"
#include "ui/ColorCache.h"
#include "ui/GuiUtil.h"
#include "ui/themes/ThemeManager.h"
#include <BuildConfig.h>
@ -57,30 +57,40 @@ class LogFormatProxyModel : public QIdentityProxyModel {
LogFormatProxyModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) {}
QVariant data(const QModelIndex& index, int role) const override
{
const LogColors& colors = APPLICATION->themeManager()->getLogColors();
switch (role) {
case Qt::FontRole:
return m_font;
case Qt::ForegroundRole: {
MessageLevel::Enum level = (MessageLevel::Enum)QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();
return m_colors->getFront(level);
auto level = static_cast<MessageLevel::Enum>(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt());
QColor result = colors.foreground.value(level);
if (result.isValid())
return result;
break;
}
case Qt::BackgroundRole: {
MessageLevel::Enum level = (MessageLevel::Enum)QIdentityProxyModel::data(index, LogModel::LevelRole).toInt();
return m_colors->getBack(level);
auto level = static_cast<MessageLevel::Enum>(QIdentityProxyModel::data(index, LogModel::LevelRole).toInt());
QColor result = colors.background.value(level);
if (result.isValid())
return result;
break;
}
default:
return QIdentityProxyModel::data(index, role);
}
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 {
auto compare = [this, start, parentIndex, value](int r) -> QModelIndex {
QModelIndex idx = index(r, start.column(), parentIndex);
if (!idx.isValid() || idx == start) {
return QModelIndex();
@ -125,7 +135,6 @@ class LogFormatProxyModel : public QIdentityProxyModel {
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)
@ -134,12 +143,6 @@ LogPage::LogPage(InstancePtr instance, QWidget* parent) : QWidget(parent), ui(ne
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
{
@ -231,7 +234,7 @@ bool LogPage::apply()
bool LogPage::shouldDisplay() const
{
return m_instance->isRunning() || m_proxy->rowCount() > 0;
return true;
}
void LogPage::on_btnPaste_clicked()

View File

@ -0,0 +1,164 @@
#include <QObject>
#include <QTcpSocket>
#include <QJsonDocument>
#include <QJsonObject>
#include <qtconcurrentrun.h>
#include <Exception.h>
#include "McClient.h"
#include "Json.h"
// 7 first bits
#define SEGMENT_BITS 0x7F
// last bit
#define CONTINUE_BIT 0x80
McClient::McClient(QObject *parent, QString domain, QString ip, short port): QObject(parent), m_domain(domain), m_ip(ip), m_port(port) {}
void McClient::getStatusData() {
qDebug() << "Connecting to socket..";
connect(&m_socket, &QTcpSocket::connected, this, [this]() {
qDebug() << "Connected to socket successfully";
sendRequest();
connect(&m_socket, &QTcpSocket::readyRead, this, &McClient::readRawResponse);
});
connect(&m_socket, &QTcpSocket::errorOccurred, this, [this]() {
emitFail("Socket disconnected: " + m_socket.errorString());
});
m_socket.connectToHost(m_ip, m_port);
}
void McClient::sendRequest() {
QByteArray data;
writeVarInt(data, 0x00); // packet ID
writeVarInt(data, 763); // hardcoded protocol version (763 = 1.20.1)
writeVarInt(data, m_domain.size()); // server address length
writeString(data, m_domain.toStdString()); // server address
writeFixedInt(data, m_port, 2); // server port
writeVarInt(data, 0x01); // next state
writePacketToSocket(data); // send handshake packet
writeVarInt(data, 0x00); // packet ID
writePacketToSocket(data); // send status packet
}
void McClient::readRawResponse() {
if (m_responseReadState == 2) {
return;
}
m_resp.append(m_socket.readAll());
if (m_responseReadState == 0 && m_resp.size() >= 5) {
m_wantedRespLength = readVarInt(m_resp);
m_responseReadState = 1;
}
if (m_responseReadState == 1 && m_resp.size() >= m_wantedRespLength) {
if (m_resp.size() > m_wantedRespLength) {
qDebug() << "Warning: Packet length doesn't match actual packet size (" << m_wantedRespLength << " expected vs " << m_resp.size() << " received)";
}
parseResponse();
m_responseReadState = 2;
}
}
void McClient::parseResponse() {
qDebug() << "Received response successfully";
int packetID = readVarInt(m_resp);
if (packetID != 0x00) {
throw Exception(
QString("Packet ID doesn't match expected value (0x00 vs 0x%1)")
.arg(packetID, 0, 16)
);
}
Q_UNUSED(readVarInt(m_resp)); // json length
// 'resp' should now be the JSON string
QJsonDocument doc = QJsonDocument::fromJson(m_resp);
emitSucceed(doc.object());
}
// From https://wiki.vg/Protocol#VarInt_and_VarLong
void McClient::writeVarInt(QByteArray &data, int value) {
while ((value & ~SEGMENT_BITS)) { // check if the value is too big to fit in 7 bits
// Write 7 bits
data.append((value & SEGMENT_BITS) | CONTINUE_BIT);
// Erase theses 7 bits from the value to write
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
value >>= 7;
}
data.append(value);
}
// From https://wiki.vg/Protocol#VarInt_and_VarLong
int McClient::readVarInt(QByteArray &data) {
int value = 0;
int position = 0;
char currentByte;
while (position < 32) {
currentByte = readByte(data);
value |= (currentByte & SEGMENT_BITS) << position;
if ((currentByte & CONTINUE_BIT) == 0) break;
position += 7;
}
if (position >= 32) throw Exception("VarInt is too big");
return value;
}
char McClient::readByte(QByteArray &data) {
if (data.isEmpty()) {
throw Exception("No more bytes to read");
}
char byte = data.at(0);
data.remove(0, 1);
return byte;
}
// write number with specified size in big endian format
void McClient::writeFixedInt(QByteArray &data, int value, int size) {
for (int i = size - 1; i >= 0; i--) {
data.append((value >> (i * 8)) & 0xFF);
}
}
void McClient::writeString(QByteArray &data, const std::string &value) {
data.append(value.c_str());
}
void McClient::writePacketToSocket(QByteArray &data) {
// we prefix the packet with its length
QByteArray dataWithSize;
writeVarInt(dataWithSize, data.size());
dataWithSize.append(data);
// write it to the socket
m_socket.write(dataWithSize);
m_socket.flush();
data.clear();
}
void McClient::emitFail(QString error) {
qDebug() << "Minecraft server ping for status error:" << error;
emit failed(error);
emit finished();
}
void McClient::emitSucceed(QJsonObject data) {
emit succeeded(data);
emit finished();
}

View File

@ -0,0 +1,51 @@
#include <QObject>
#include <QTcpSocket>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFuture>
#include <Exception.h>
// Client for the Minecraft protocol
class McClient : public QObject {
Q_OBJECT
QString m_domain;
QString m_ip;
short m_port;
QTcpSocket m_socket;
// 0: did not start reading the response yet
// 1: read the response length, still reading the response
// 2: finished reading the response
unsigned m_responseReadState = 0;
unsigned m_wantedRespLength = 0;
QByteArray m_resp;
public:
explicit McClient(QObject *parent, QString domain, QString ip, short port);
//! Read status data of the server, and calls the succeeded() signal with the parsed JSON data
void getStatusData();
private:
void sendRequest();
//! Accumulate data until we have a full response, then call parseResponse() once
void readRawResponse();
void parseResponse();
void writeVarInt(QByteArray &data, int value);
int readVarInt(QByteArray &data);
char readByte(QByteArray &data);
//! write number with specified size in big endian format
void writeFixedInt(QByteArray &data, int value, int size);
void writeString(QByteArray &data, const std::string &value);
void writePacketToSocket(QByteArray &data);
void emitFail(QString error);
void emitSucceed(QJsonObject data);
signals:
void succeeded(QJsonObject data);
void failed(QString error);
void finished();
};

View File

@ -0,0 +1,73 @@
#include <QObject>
#include <QDnsLookup>
#include <QtNetwork/qtcpsocket.h>
#include <QHostInfo>
#include "McResolver.h"
McResolver::McResolver(QObject *parent, QString domain, int port): QObject(parent), m_constrDomain(domain), m_constrPort(port) {}
void McResolver::ping() {
pingWithDomainSRV(m_constrDomain, m_constrPort);
}
void McResolver::pingWithDomainSRV(QString domain, int port) {
QDnsLookup *lookup = new QDnsLookup(this);
lookup->setName(QString("_minecraft._tcp.%1").arg(domain));
lookup->setType(QDnsLookup::SRV);
connect(lookup, &QDnsLookup::finished, this, [this, domain, port]() {
QDnsLookup *lookup = qobject_cast<QDnsLookup *>(sender());
lookup->deleteLater();
if (lookup->error() != QDnsLookup::NoError) {
qDebug() << QString("Warning: SRV record lookup failed (%1), trying A record lookup").arg(lookup->errorString());
pingWithDomainA(domain, port);
return;
}
auto records = lookup->serviceRecords();
if (records.isEmpty()) {
qDebug() << "Warning: no SRV entries found for domain, trying A record lookup";
pingWithDomainA(domain, port);
return;
}
const auto& firstRecord = records.at(0);
QString domain = firstRecord.target();
int port = firstRecord.port();
pingWithDomainA(domain, port);
});
lookup->lookup();
}
void McResolver::pingWithDomainA(QString domain, int port) {
QHostInfo::lookupHost(domain, this, [this, port](const QHostInfo &hostInfo){
if (hostInfo.error() != QHostInfo::NoError) {
emitFail("A record lookup failed");
return;
}
auto records = hostInfo.addresses();
if (records.isEmpty()) {
emitFail("No A entries found for domain");
return;
}
const auto& firstRecord = records.at(0);
emitSucceed(firstRecord.toString(), port);
});
}
void McResolver::emitFail(QString error) {
qDebug() << "DNS resolver error:" << error;
emit failed(error);
emit finished();
}
void McResolver::emitSucceed(QString ip, int port) {
emit succeeded(ip, port);
emit finished();
}

View File

@ -0,0 +1,28 @@
#include <QObject>
#include <QString>
#include <QDnsLookup>
#include <QtNetwork/qtcpsocket.h>
#include <QHostInfo>
// resolve the IP and port of a Minecraft server
class McResolver : public QObject {
Q_OBJECT
QString m_constrDomain;
int m_constrPort;
public:
explicit McResolver(QObject *parent, QString domain, int port);
void ping();
private:
void pingWithDomainSRV(QString domain, int port);
void pingWithDomainA(QString domain, int port);
void emitFail(QString error);
void emitSucceed(QString ip, int port);
signals:
void succeeded(QString ip, int port);
void failed(QString error);
void finished();
};

View File

@ -51,117 +51,60 @@
#include "Application.h"
#include "ui/GuiUtil.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ModUpdateDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "DesktopServices.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
#include "minecraft/PackProfile.h"
#include "minecraft/VersionFilterData.h"
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/ModFolderModel.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "Version.h"
#include "tasks/ConcurrentTask.h"
#include "tasks/Task.h"
#include "ui/dialogs/ProgressDialog.h"
ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ExternalResourcesPage(inst, mods, parent), m_model(mods)
ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> model, QWidget* parent)
: ExternalResourcesPage(inst, model, parent), m_model(model)
{
// This is structured like that so that these changes
// do not affect the Resource pack and Shader pack tabs
{
ui->actionDownloadItem->setText(tr("Download mods"));
ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
ui->actionAddItem->setText(tr("Add file"));
ui->actionAddItem->setToolTip(tr("Add a locally downloaded file"));
ui->actionDownloadItem->setText(tr("Download Mods"));
ui->actionDownloadItem->setToolTip(tr("Download mods from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::downloadMods);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
// update menu
auto updateMenu = ui->actionUpdateItem->menu();
if (updateMenu) {
updateMenu->clear();
} else {
updateMenu = new QMenu(this);
}
auto updateMenu = new QMenu(this);
auto update = updateMenu->addAction(tr("Check for Updates"));
update->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)"));
connect(update, &QAction::triggered, this, &ModFolderPage::updateMods);
auto update = updateMenu->addAction(tr("Check for Updates"));
connect(update, &QAction::triggered, this, &ModFolderPage::updateMods);
auto updateWithDeps = updateMenu->addAction(tr("Verify Dependencies"));
updateWithDeps->setToolTip(
tr("Try to update and check for missing dependencies all selected mods (all mods if none are selected)"));
connect(updateWithDeps, &QAction::triggered, this, [this] { updateMods(true); });
updateMenu->addAction(ui->actionVerifyItemDependencies);
connect(ui->actionVerifyItemDependencies, &QAction::triggered, this, [this] { updateMods(true); });
auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled");
updateWithDeps->setVisible(!depsDisabled->get().toBool());
connect(depsDisabled.get(), &Setting::SettingChanged, this,
[updateWithDeps](const Setting& setting, QVariant value) { updateWithDeps->setVisible(!value.toBool()); });
auto depsDisabled = APPLICATION->settings()->getSetting("ModDependenciesDisabled");
ui->actionVerifyItemDependencies->setVisible(!depsDisabled->get().toBool());
connect(depsDisabled.get(), &Setting::SettingChanged, this,
[this](const Setting& setting, const QVariant& value) { ui->actionVerifyItemDependencies->setVisible(!value.toBool()); });
auto actionRemoveItemMetadata = updateMenu->addAction(tr("Reset update metadata"));
actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
connect(actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
actionRemoveItemMetadata->setEnabled(false);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected mods (all mods if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
ui->actionChangeVersion->setToolTip(tr("Change a mod's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
ui->actionViewHomepage->setToolTip(tr("View the homepages of all selected mods."));
auto changeVersion = new QAction(tr("Change Version"));
changeVersion->setToolTip(tr("Change mod version"));
changeVersion->setEnabled(false);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, changeVersion);
connect(changeVersion, &QAction::triggered, this, &ModFolderPage::changeModVersion);
ui->actionsToolbar->insertActionAfter(ui->actionVisitItemPage, ui->actionExportMetadata);
connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata);
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this, check_allow_update, actionRemoveItemMetadata, changeVersion] {
ui->actionUpdateItem->setEnabled(check_allow_update());
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
auto selected = std::count_if(mods_list.cbegin(), mods_list.cend(),
[](Mod* v) { return v->metadata() != nullptr || v->homeurl().size() != 0; });
if (selected <= 1) {
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
} else {
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
}
changeVersion->setEnabled(mods_list.length() == 1 && mods_list[0]->metadata() != nullptr);
ui->actionVisitItemPage->setEnabled(selected != 0);
actionRemoveItemMetadata->setEnabled(selected != 0);
});
auto updateButtons = [this, check_allow_update] { ui->actionUpdateItem->setEnabled(check_allow_update()); };
connect(mods.get(), &ModFolderModel::rowsInserted, this, updateButtons);
connect(mods.get(), &ModFolderModel::rowsRemoved, this, updateButtons);
connect(mods.get(), &ModFolderModel::updateFinished, this, updateButtons);
}
ui->actionExportMetadata->setToolTip(tr("Export mod's metadata to text."));
connect(ui->actionExportMetadata, &QAction::triggered, this, &ModFolderPage::exportModMetadata);
ui->actionsToolbar->insertActionAfter(ui->actionViewHomepage, ui->actionExportMetadata);
}
bool ModFolderPage::shouldDisplay() const
@ -169,15 +112,12 @@ bool ModFolderPage::shouldDisplay() const
return true;
}
bool ModFolderPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
void ModFolderPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
Mod const* m = m_model->at(row);
if (m)
ui->frame->updateWithMod(*m);
return true;
const Mod& mod = m_model->at(row);
ui->frame->updateWithMod(mod);
}
void ModFolderPage::removeItems(const QItemSelection& selection)
@ -192,10 +132,10 @@ void ModFolderPage::removeItems(const QItemSelection& selection)
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMods(selection.indexes());
m_model->deleteResources(selection.indexes());
}
void ModFolderPage::installMods()
void ModFolderPage::downloadMods()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
@ -208,7 +148,7 @@ void ModFolderPage::installMods()
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@ -265,12 +205,12 @@ void ModFolderPage::updateMods(bool includeDeps)
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allMods();
mods_list = m_model->allResources();
ModUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps);
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, true);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {
@ -291,7 +231,7 @@ void ModFolderPage::updateMods(bool includeDeps)
}
if (update_dialog.exec()) {
auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@ -320,50 +260,6 @@ void ModFolderPage::updateMods(bool includeDeps)
}
}
CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ModFolderPage(inst, mods, parent)
{}
bool CoreModFolderPage::shouldDisplay() const
{
if (ModFolderPage::shouldDisplay()) {
auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
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;
}
NilModFolderPage::NilModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ModFolderPage(inst, mods, parent)
{}
bool NilModFolderPage::shouldDisplay() const
{
return m_model->dir().exists();
}
void ModFolderPage::visitModPages()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
for (auto mod : m_model->selectedMods(selection)) {
auto url = mod->metaurl();
if (!url.isEmpty())
DesktopServices::openUrl(url);
}
}
void ModFolderPage::deleteModMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
@ -382,7 +278,7 @@ void ModFolderPage::deleteModMetadata()
return;
}
m_model->deleteModsMetadata(selection);
m_model->deleteMetadata(selection);
}
void ModFolderPage::changeModVersion()
@ -405,9 +301,9 @@ void ModFolderPage::changeModVersion()
return;
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setModMetadata((*mods_list.begin())->metadata());
mdownload.setResourceMetadata((*mods_list.begin())->metadata());
if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@ -447,3 +343,52 @@ void ModFolderPage::exportModMetadata()
ExportToModListDialog dlg(m_instance->name(), selectedMods, this);
dlg.exec();
}
CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ModFolderPage(inst, mods, parent)
{
auto mcInst = dynamic_cast<MinecraftInstance*>(m_instance);
if (mcInst) {
auto version = mcInst->getPackProfile();
if (version && version->getComponent("net.minecraftforge") && version->getComponent("net.minecraft")) {
auto minecraftCmp = version->getComponent("net.minecraft");
if (!minecraftCmp->m_loaded) {
version->reload(Net::Mode::Offline);
auto update = version->getCurrentTask();
if (update) {
connect(update.get(), &Task::finished, this, [this] {
if (m_container) {
m_container->refreshContainer();
}
});
update->start();
}
}
}
}
}
bool CoreModFolderPage::shouldDisplay() const
{
if (ModFolderPage::shouldDisplay()) {
auto inst = dynamic_cast<MinecraftInstance*>(m_instance);
if (!inst)
return true;
auto version = inst->getPackProfile();
if (!version || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft"))
return false;
auto minecraftCmp = version->getComponent("net.minecraft");
return minecraftCmp->m_loaded && minecraftCmp->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate;
}
return false;
}
NilModFolderPage::NilModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent)
: ModFolderPage(inst, mods, parent)
{}
bool NilModFolderPage::shouldDisplay() const
{
return m_model->dir().exists();
}

View File

@ -44,7 +44,7 @@ class ModFolderPage : public ExternalResourcesPage {
Q_OBJECT
public:
explicit ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = nullptr);
explicit ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> model, QWidget* parent = nullptr);
virtual ~ModFolderPage() = default;
void setFilter(const QString& filter) { m_fileSelectionFilter = filter; }
@ -57,16 +57,15 @@ class ModFolderPage : public ExternalResourcesPage {
virtual bool shouldDisplay() const override;
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
private slots:
void removeItems(const QItemSelection& selection) override;
void downloadMods();
void updateMods(bool includeDeps = false);
void deleteModMetadata();
void exportModMetadata();
void installMods();
void updateMods(bool includeDeps = false);
void visitModPages();
void changeModVersion();
protected:
@ -74,6 +73,7 @@ class ModFolderPage : public ExternalResourcesPage {
};
class CoreModFolderPage : public ModFolderPage {
Q_OBJECT
public:
explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
virtual ~CoreModFolderPage() = default;
@ -87,6 +87,7 @@ class CoreModFolderPage : public ModFolderPage {
};
class NilModFolderPage : public ModFolderPage {
Q_OBJECT
public:
explicit NilModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel> mods, QWidget* parent = 0);
virtual ~NilModFolderPage() = default;

View File

@ -138,7 +138,7 @@ void OtherLogsPage::on_btnReload_clicked()
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) {
auto setPlainText = [this](const QString& text) {
QString fontFamily = APPLICATION->settings()->get("ConsoleFont").toString();
bool conversionOk = false;
int fontSize = APPLICATION->settings()->get("ConsoleFontSize").toInt(&conversionOk);
@ -149,7 +149,7 @@ void OtherLogsPage::on_btnReload_clicked()
doc->setDefaultFont(QFont(fontFamily, fontSize));
ui->text->setPlainText(text);
};
auto showTooBig = [&]() {
auto showTooBig = [setPlainText, &file]() {
setPlainText(tr("The file (%1) is too big. You may want to open it in a viewer optimized "
"for large files.")
.arg(file.fileName()));

View File

@ -37,43 +37,56 @@
#include "ResourcePackPage.h"
#include "ResourceDownloadTask.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, std::shared_ptr<ResourcePackFolderModel> model, QWidget* parent)
: ExternalResourcesPage(instance, model, parent)
: ExternalResourcesPage(instance, model, parent), m_model(model)
{
ui->actionDownloadItem->setText(tr("Download packs"));
ui->actionDownloadItem->setToolTip(tr("Download resource packs from online platforms"));
ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download resource packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadRPs);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionViewConfigs->setVisible(false);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ResourcePackPage::downloadResourcePacks);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected resource packs (all resource packs if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
auto updateMenu = new QMenu(this);
auto update = updateMenu->addAction(ui->actionUpdateItem->text());
connect(update, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ResourcePackPage::deleteResourcePackMetadata);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionChangeVersion->setToolTip(tr("Change a mod's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &ResourcePackPage::changeResourcePackVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
}
bool ResourcePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
void ResourcePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
auto& rp = static_cast<ResourcePack&>(m_model->at(row));
ui->frame->updateWithResourcePack(rp);
return true;
}
void ResourcePackPage::downloadRPs()
void ResourcePackPage::downloadResourcePacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast<ResourcePackFolderModel>(m_model), m_instance);
ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@ -101,3 +114,155 @@ void ResourcePackPage::downloadRPs()
m_model->update();
}
}
void ResourcePackPage::updateResourcePacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!"));
return;
}
if (m_instance != nullptr && m_instance->isRunning()) {
auto response = CustomMessageBox::selectable(
this, tr("Confirm Update"),
tr("Updating resource packs while the game is running may cause pack duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allResources();
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {
CustomMessageBox::selectable(this, tr("Aborted"), tr("The resource pack updater was aborted!"), QMessageBox::Warning)->show();
return;
}
if (update_dialog.noUpdates()) {
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
if (mods_list.size() > 1) {
if (use_all) {
message = tr("All resource packs are up-to-date! :)");
} else {
message = tr("All selected resource packs are up-to-date! :)");
}
}
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
return;
}
if (update_dialog.exec()) {
auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
tasks->deleteLater();
});
for (auto task : update_dialog.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ResourcePackPage::deleteResourcePackMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedResourcePacks(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 resource packs.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMetadata(selection);
}
void ResourcePackPage::changeResourcePackVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!"));
return;
}
const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows();
if (rows.count() != 1)
return;
Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row());
if (resource.metadata() == nullptr)
return;
ResourceDownload::ResourcePackDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setResourceMetadata(resource.metadata());
if (mdownload.exec()) {
auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}

View File

@ -58,6 +58,14 @@ class ResourcePackPage : public ExternalResourcesPage {
}
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void downloadRPs();
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
private slots:
void downloadResourcePacks();
void updateResourcePacks();
void deleteResourcePackMetadata();
void changeResourcePackVersion();
protected:
std::shared_ptr<ResourcePackFolderModel> m_model;
};

View File

@ -242,6 +242,11 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa
m_model->setReadOnly(false);
m_model->setNameFilters({ "*.png" });
m_model->setNameFilterDisables(false);
// Sorts by modified date instead of creation date because that column is not available and would require subclassing, this should work
// considering screenshots aren't modified after creation.
constexpr int file_modified_column_index = 3;
m_model->sort(file_modified_column_index, Qt::DescendingOrder);
m_folder = path;
m_valid = FS::ensureFolderPathExists(m_folder);

View File

@ -0,0 +1,47 @@
#include <QFutureWatcher>
#include "ServerPingTask.h"
#include "McResolver.h"
#include "McClient.h"
#include <Json.h>
unsigned getOnlinePlayers(QJsonObject data) {
return Json::requireInteger(Json::requireObject(data, "players"), "online");
}
void ServerPingTask::executeTask() {
qDebug() << "Querying status of " << QString("%1:%2").arg(m_domain).arg(m_port);
// Resolve the actual IP and port for the server
McResolver *resolver = new McResolver(nullptr, m_domain, m_port);
QObject::connect(resolver, &McResolver::succeeded, this, [this, resolver](QString ip, int port) {
qDebug() << "Resolved Address for" << m_domain << ": " << ip << ":" << port;
// Now that we have the IP and port, query the server
McClient *client = new McClient(nullptr, m_domain, ip, port);
QObject::connect(client, &McClient::succeeded, this, [this](QJsonObject data) {
m_outputOnlinePlayers = getOnlinePlayers(data);
qDebug() << "Online players: " << m_outputOnlinePlayers;
emitSucceeded();
});
QObject::connect(client, &McClient::failed, this, [this](QString error) {
emitFailed(error);
});
// Delete McClient object when done
QObject::connect(client, &McClient::finished, this, [this, client]() {
client->deleteLater();
});
client->getStatusData();
});
QObject::connect(resolver, &McResolver::failed, this, [this](QString error) {
emitFailed(error);
});
// Delete McResolver object when done
QObject::connect(resolver, &McResolver::finished, [resolver]() {
resolver->deleteLater();
});
resolver->ping();
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <QObject>
#include <QString>
#include <tasks/Task.h>
class ServerPingTask : public Task {
Q_OBJECT
public:
explicit ServerPingTask(QString domain, int port) : Task(), m_domain(domain), m_port(port) {}
~ServerPingTask() override = default;
int m_outputOnlinePlayers = -1;
private:
QString m_domain;
int m_port;
protected:
virtual void executeTask() override;
};

View File

@ -38,6 +38,7 @@
#include "ServersPage.h"
#include "ui/dialogs/CustomMessageBox.h"
#include "ui_ServersPage.h"
#include "ServerPingTask.h"
#include <FileSystem.h>
#include <io/stream_reader.h>
@ -51,8 +52,9 @@
#include <QFileSystemWatcher>
#include <QMenu>
#include <QTimer>
#include <tasks/ConcurrentTask.h>
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things.
struct Server {
// Types
@ -112,8 +114,7 @@ struct Server {
bool m_checked = false;
bool m_up = false;
QString m_motd; // https://mctools.org/motd-creator
int m_ping = 0;
int m_currentPlayers = 0;
std::optional<int> m_currentPlayers; // nullopt if not calculated/calculating
int m_maxPlayers = 0;
};
@ -296,7 +297,7 @@ class ServersModel : public QAbstractListModel {
case 1:
return tr("Address");
case 2:
return tr("Latency");
return tr("Online");
}
}
@ -345,7 +346,11 @@ class ServersModel : public QAbstractListModel {
case 2:
switch (role) {
case Qt::DisplayRole:
return m_servers[row].m_ping;
if (m_servers[row].m_currentPlayers) {
return *m_servers[row].m_currentPlayers;
} else {
return "...";
}
default:
return QVariant();
}
@ -433,6 +438,40 @@ class ServersModel : public QAbstractListModel {
}
}
void queryServersStatus()
{
// Abort the currently running task if present
if (m_currentQueryTask != nullptr) {
m_currentQueryTask->abort();
qDebug() << "Aborted previous server query task";
}
m_currentQueryTask = ConcurrentTask::Ptr(
new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())
);
int row = 0;
for (Server &server : m_servers) {
// reset current players
server.m_currentPlayers = {};
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
// Start task to query server status
auto target = MinecraftTarget::parse(server.m_address, false);
auto *task = new ServerPingTask(target.address, target.port);
m_currentQueryTask->addTask(Task::Ptr(task));
// Update the model when the task is done
connect(task, &Task::finished, this, [this, task, row]() {
if (m_servers.size() < row) return;
m_servers[row].m_currentPlayers = task->m_outputOnlinePlayers;
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
});
row++;
}
m_currentQueryTask->start();
}
public slots:
void dirChanged(const QString& path)
{
@ -520,6 +559,7 @@ class ServersModel : public QAbstractListModel {
QList<Server> m_servers;
QFileSystemWatcher* m_watcher = nullptr;
QTimer m_saveTimer;
ConcurrentTask::Ptr m_currentQueryTask = nullptr;
};
ServersPage::ServersPage(InstancePtr inst, QWidget* parent) : QMainWindow(parent), ui(new Ui::ServersPage)
@ -676,6 +716,9 @@ void ServersPage::openedImpl()
m_wide_bar_setting = APPLICATION->settings()->getSetting(setting_name);
ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray());
// ping servers
m_model->queryServersStatus();
}
void ServersPage::closedImpl()
@ -734,4 +777,9 @@ void ServersPage::on_actionJoin_triggered()
APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftTarget>(MinecraftTarget::parse(address, false)));
}
void ServersPage::on_actionRefresh_triggered()
{
m_model->queryServersStatus();
}
#include "ServersPage.moc"

View File

@ -85,6 +85,7 @@ class ServersPage : public QMainWindow, public BasePage {
void on_actionMove_Up_triggered();
void on_actionMove_Down_triggered();
void on_actionJoin_triggered();
void on_actionRefresh_triggered();
void runningStateChanged(bool running);

View File

@ -149,6 +149,8 @@
<addaction name="actionMove_Up"/>
<addaction name="actionMove_Down"/>
<addaction name="actionJoin"/>
<addaction name="separator"/>
<addaction name="actionRefresh"/>
</widget>
<action name="actionAdd">
<property name="text">
@ -175,6 +177,11 @@
<string>Join</string>
</property>
</action>
<action name="actionRefresh">
<property name="text">
<string>Refresh</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -45,27 +45,197 @@
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, std::shared_ptr<ShaderPackFolderModel> model, QWidget* parent)
: ExternalResourcesPage(instance, model, parent)
: ExternalResourcesPage(instance, model, parent), m_model(model)
{
ui->actionDownloadItem->setText(tr("Download shaders"));
ui->actionDownloadItem->setToolTip(tr("Download shaders from online platforms"));
ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download shader packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaders);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionViewConfigs->setVisible(false);
connect(ui->actionDownloadItem, &QAction::triggered, this, &ShaderPackPage::downloadShaderPack);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected shader packs (all shader packs if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
auto updateMenu = new QMenu(this);
auto update = updateMenu->addAction(ui->actionUpdateItem->text());
connect(update, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &ShaderPackPage::deleteShaderPackMetadata);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionChangeVersion->setToolTip(tr("Change a shader pack's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &ShaderPackPage::changeShaderPackVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
}
void ShaderPackPage::downloadShaders()
void ShaderPackPage::downloadShaderPack()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast<ShaderPackFolderModel>(m_model), m_instance);
ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
auto tasks = new ConcurrentTask(this, "Download Shaders", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ShaderPackPage::updateShaderPacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!"));
return;
}
if (m_instance != nullptr && m_instance->isRunning()) {
auto response =
CustomMessageBox::selectable(this, tr("Confirm Update"),
tr("Updating shader packs while the game is running may pack duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allResources();
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {
CustomMessageBox::selectable(this, tr("Aborted"), tr("The shader pack updater was aborted!"), QMessageBox::Warning)->show();
return;
}
if (update_dialog.noUpdates()) {
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
if (mods_list.size() > 1) {
if (use_all) {
message = tr("All shader packs are up-to-date! :)");
} else {
message = tr("All selected shader packs are up-to-date! :)");
}
}
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
return;
}
if (update_dialog.exec()) {
auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
tasks->deleteLater();
});
for (auto task : update_dialog.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void ShaderPackPage::deleteShaderPackMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedShaderPacks(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 shader packs.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMetadata(selection);
}
void ShaderPackPage::changeShaderPackVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!"));
return;
}
const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows();
if (rows.count() != 1)
return;
Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row());
if (resource.metadata() == nullptr)
return;
ResourceDownload::ShaderPackDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setResourceMetadata(resource.metadata());
if (mdownload.exec()) {
auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();

View File

@ -53,5 +53,11 @@ class ShaderPackPage : public ExternalResourcesPage {
bool shouldDisplay() const override { return true; }
public slots:
void downloadShaders();
void downloadShaderPack();
void updateShaderPacks();
void deleteShaderPackMetadata();
void changeShaderPackVersion();
private:
std::shared_ptr<ShaderPackFolderModel> m_model;
};

View File

@ -44,38 +44,207 @@
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/dialogs/ResourceUpdateDialog.h"
TexturePackPage::TexturePackPage(MinecraftInstance* instance, std::shared_ptr<TexturePackFolderModel> model, QWidget* parent)
: ExternalResourcesPage(instance, model, parent)
: ExternalResourcesPage(instance, model, parent), m_model(model)
{
ui->actionDownloadItem->setText(tr("Download packs"));
ui->actionDownloadItem->setToolTip(tr("Download texture packs from online platforms"));
ui->actionDownloadItem->setText(tr("Download Packs"));
ui->actionDownloadItem->setToolTip(tr("Download texture packs from online mod platforms"));
ui->actionDownloadItem->setEnabled(true);
connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTPs);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
ui->actionViewConfigs->setVisible(false);
connect(ui->actionDownloadItem, &QAction::triggered, this, &TexturePackPage::downloadTexturePacks);
ui->actionUpdateItem->setToolTip(tr("Try to check or update all selected texture packs (all texture packs if none are selected)"));
connect(ui->actionUpdateItem, &QAction::triggered, this, &TexturePackPage::updateTexturePacks);
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem);
auto updateMenu = new QMenu(this);
auto update = updateMenu->addAction(ui->actionUpdateItem->text());
connect(update, &QAction::triggered, this, &TexturePackPage::updateTexturePacks);
updateMenu->addAction(ui->actionResetItemMetadata);
connect(ui->actionResetItemMetadata, &QAction::triggered, this, &TexturePackPage::deleteTexturePackMetadata);
ui->actionUpdateItem->setMenu(updateMenu);
ui->actionChangeVersion->setToolTip(tr("Change a texture pack's version."));
connect(ui->actionChangeVersion, &QAction::triggered, this, &TexturePackPage::changeTexturePackVersion);
ui->actionsToolbar->insertActionAfter(ui->actionUpdateItem, ui->actionChangeVersion);
ui->actionViewHomepage->setToolTip(tr("View the homepages of all selected texture packs."));
}
bool TexturePackPage::onSelectionChanged(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
void TexturePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] const QModelIndex& previous)
{
auto sourceCurrent = m_filterModel->mapToSource(current);
int row = sourceCurrent.row();
auto& rp = static_cast<TexturePack&>(m_model->at(row));
ui->frame->updateWithTexturePack(rp);
return true;
}
void TexturePackPage::downloadTPs()
void TexturePackPage::downloadTexturePacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast<TexturePackFolderModel>(m_model), m_instance);
ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
auto tasks =
new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count())
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
tasks->deleteLater();
});
for (auto& task : mdownload.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void TexturePackPage::updateTexturePacks()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!"));
return;
}
if (m_instance != nullptr && m_instance->isRunning()) {
auto response = CustomMessageBox::selectable(
this, tr("Confirm Update"),
tr("Updating texture packs while the game is running may cause pack duplication and game crashes.\n"
"The old files may not be deleted as they are in use.\n"
"Are you sure you want to do this?"),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedResources(selection);
bool use_all = mods_list.empty();
if (use_all)
mods_list = m_model->allResources();
ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, false);
update_dialog.checkCandidates();
if (update_dialog.aborted()) {
CustomMessageBox::selectable(this, tr("Aborted"), tr("The texture pack updater was aborted!"), QMessageBox::Warning)->show();
return;
}
if (update_dialog.noUpdates()) {
QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) };
if (mods_list.size() > 1) {
if (use_all) {
message = tr("All texture packs are up-to-date! :)");
} else {
message = tr("All selected texture packs are up-to-date! :)");
}
}
CustomMessageBox::selectable(this, tr("Update checker"), message)->exec();
return;
}
if (update_dialog.exec()) {
auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
});
connect(tasks, &Task::aborted, [this, tasks]() {
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
tasks->deleteLater();
});
connect(tasks, &Task::succeeded, [this, tasks]() {
QStringList warnings = tasks->warnings();
if (warnings.count()) {
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
}
tasks->deleteLater();
});
for (auto task : update_dialog.getTasks()) {
tasks->addTask(task);
}
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
loadDialog.execWithTask(tasks);
m_model->update();
}
}
void TexturePackPage::deleteTexturePackMetadata()
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto selectionCount = m_model->selectedTexturePacks(selection).length();
if (selectionCount == 0)
return;
if (selectionCount > 1) {
auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
tr("You are about to remove the metadata for %1 texture packs.\n"
"Are you sure?")
.arg(selectionCount),
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec();
if (response != QMessageBox::Yes)
return;
}
m_model->deleteMetadata(selection);
}
void TexturePackPage::changeTexturePackVersion()
{
if (m_instance->typeName() != "Minecraft")
return; // this is a null instance or a legacy instance
if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) {
QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!"));
return;
}
const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows();
if (rows.count() != 1)
return;
Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row());
if (resource.metadata() == nullptr)
return;
ResourceDownload::TexturePackDownloadDialog mdownload(this, m_model, m_instance);
mdownload.setResourceMetadata(resource.metadata());
if (mdownload.exec()) {
auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();

View File

@ -55,6 +55,12 @@ class TexturePackPage : public ExternalResourcesPage {
virtual bool shouldDisplay() const override { return m_instance->traits().contains("texturepacks"); }
public slots:
bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override;
void downloadTPs();
void updateFrame(const QModelIndex& current, const QModelIndex& previous) override;
void downloadTexturePacks();
void updateTexturePacks();
void deleteTexturePackMetadata();
void changeTexturePackVersion();
private:
std::shared_ptr<TexturePackFolderModel> m_model;
};

View File

@ -49,8 +49,12 @@
#include <QMessageBox>
#include <QString>
#include <QUrl>
#include <algorithm>
#include "QObjectPtr.h"
#include "VersionPage.h"
#include "meta/JsonFormat.h"
#include "tasks/SequentialTask.h"
#include "ui/dialogs/InstallLoaderDialog.h"
#include "ui_VersionPage.h"
@ -63,11 +67,9 @@
#include "DesktopServices.h"
#include "Exception.h"
#include "Version.h"
#include "icons/IconList.h"
#include "minecraft/PackProfile.h"
#include "minecraft/auth/AccountList.h"
#include "minecraft/mod/Mod.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
@ -241,7 +243,7 @@ void VersionPage::updateButtons(int row)
ui->actionRemove->setEnabled(patch && patch->isRemovable());
ui->actionMove_down->setEnabled(patch && patch->isMoveable());
ui->actionMove_up->setEnabled(patch && patch->isMoveable());
ui->actionChange_version->setEnabled(patch && patch->isVersionChangeable());
ui->actionChange_version->setEnabled(patch && patch->isVersionChangeable(false));
ui->actionEdit->setEnabled(patch && patch->isCustom());
ui->actionCustomize->setEnabled(patch && patch->isCustomizable());
ui->actionRevert->setEnabled(patch && patch->isRevertible());
@ -250,8 +252,11 @@ void VersionPage::updateButtons(int row)
bool VersionPage::reloadPackProfile()
{
try {
m_profile->reload(Net::Mode::Online);
return true;
auto result = m_profile->reload(Net::Mode::Online);
if (!result) {
QMessageBox::critical(this, tr("Error"), result.error);
}
return result;
} catch (const Exception& e) {
QMessageBox::critical(this, tr("Error"), e.cause());
return false;
@ -370,11 +375,25 @@ void VersionPage::on_actionChange_version_triggered()
auto patch = m_profile->getComponent(versionRow);
auto name = patch->getName();
auto list = patch->getVersionList();
list->clearExternalRecommends();
if (!list) {
return;
}
auto uid = list->uid();
// recommend the correct lwjgl version for the current minecraft version
if (uid == "org.lwjgl" || uid == "org.lwjgl3") {
auto minecraft = m_profile->getComponent("net.minecraft");
auto lwjglReq = std::find_if(minecraft->m_cachedRequires.cbegin(), minecraft->m_cachedRequires.cend(),
[uid](const Meta::Require& req) -> bool { return req.uid == uid; });
if (lwjglReq != minecraft->m_cachedRequires.cend()) {
auto lwjglVersion = !lwjglReq->equalsVersion.isEmpty() ? lwjglReq->equalsVersion : lwjglReq->suggests;
if (!lwjglVersion.isEmpty()) {
list->addExternalRecommends({ lwjglVersion });
}
}
}
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed") {
vselect.setEmptyString(tr("No intermediary mappings versions are currently available."));
@ -393,6 +412,11 @@ void VersionPage::on_actionChange_version_triggered()
bool important = false;
if (uid == "net.minecraft") {
important = true;
if (APPLICATION->settings()->get("AutomaticJavaSwitch").toBool() && m_inst->settings()->get("AutomaticJava").toBool() &&
m_inst->settings()->get("OverrideJavaLocation").toBool()) {
m_inst->settings()->set("OverrideJavaLocation", false);
m_inst->settings()->set("JavaPath", "");
}
}
m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important);
m_profile->resolve(Net::Mode::Online);
@ -410,14 +434,18 @@ void VersionPage::on_actionDownload_All_triggered()
return;
}
auto updateTask = m_inst->createUpdateTask(Net::Mode::Online);
if (!updateTask) {
auto updateTasks = m_inst->createUpdateTask();
if (updateTasks.isEmpty()) {
return;
}
auto task = makeShared<SequentialTask>();
for (auto t : updateTasks) {
task->addTask(t);
}
ProgressDialog tDialog(this);
connect(updateTask.get(), &Task::failed, this, &VersionPage::onGameUpdateError);
connect(task.get(), &Task::failed, this, &VersionPage::onGameUpdateError);
// FIXME: unused return value
tDialog.execWithTask(updateTask.get());
tDialog.execWithTask(task.get());
updateButtons();
m_container->refreshContainer();
}

View File

@ -239,7 +239,7 @@ void WorldListPage::on_actionData_Packs_triggered()
dialog->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("DataPackDownloadGeometry").toByteArray()));
auto layout = new QHBoxLayout(dialog);
auto page = new DataPackPage(m_inst, std::make_shared<DataPackFolderModel>(folder, m_inst));
auto page = new DataPackPage(m_inst.get(), std::make_shared<DataPackFolderModel>(folder, m_inst.get(), true, true));
page->setParent(dialog); // HACK: many pages extend QMainWindow; setting the parent manually prevents them from creating a window.
layout->addWidget(page);
dialog->setLayout(layout);