From cb4f2220880f2f59f3d0cd2cf23f53269772a811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6?= <2411829240@qq.com> Date: Fri, 9 Feb 2024 21:46:49 +0800 Subject: [PATCH 1/7] =?UTF-8?q?Add=20logic=20and=20UI=20for=20discovering?= =?UTF-8?q?=20instances=20from=20external=20directories=20Signed-off-by:?= =?UTF-8?q?=20=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6=20<2411829240@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- launcher/Application.cpp | 2 + launcher/CMakeLists.txt | 3 + launcher/InstanceList.cpp | 203 +++++++++++++--- launcher/InstanceList.h | 12 +- .../ui/pages/global/InstancesDirListPage.cpp | 218 ++++++++++++++++++ .../ui/pages/global/InstancesDirListPage.h | 54 +++++ .../ui/pages/global/InstancesDirListPage.ui | 71 ++++++ 7 files changed, 522 insertions(+), 41 deletions(-) create mode 100644 launcher/ui/pages/global/InstancesDirListPage.cpp create mode 100644 launcher/ui/pages/global/InstancesDirListPage.h create mode 100644 launcher/ui/pages/global/InstancesDirListPage.ui diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 42343ff8ff..946b40c6f0 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -60,6 +60,7 @@ #include "ui/pages/global/CustomCommandsPage.h" #include "ui/pages/global/EnvironmentVariablesPage.h" #include "ui/pages/global/ExternalToolsPage.h" +#include "ui/pages/global/InstancesDirListPage.h" #include "ui/pages/global/JavaPage.h" #include "ui/pages/global/LanguagePage.h" #include "ui/pages/global/LauncherPage.h" @@ -758,6 +759,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) { m_globalSettingsProvider = std::make_shared(tr("Settings")); m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 99acf8fc57..68d29ff721 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -887,6 +887,8 @@ SET(LAUNCHER_SOURCES # GUI - global settings pages ui/pages/global/AccountListPage.cpp ui/pages/global/AccountListPage.h + ui/pages/global/InstancesDirListPage.cpp + ui/pages/global/InstancesDirListPage.h ui/pages/global/CustomCommandsPage.cpp ui/pages/global/CustomCommandsPage.h ui/pages/global/EnvironmentVariablesPage.cpp @@ -1124,6 +1126,7 @@ qt_wrap_ui(LAUNCHER_UI ui/setupwizard/PasteWizardPage.ui ui/setupwizard/ThemeWizardPage.ui ui/pages/global/AccountListPage.ui + ui/pages/global/InstancesDirListPage.ui ui/pages/global/JavaPage.ui ui/pages/global/LauncherPage.ui ui/pages/global/APIPage.ui diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index c884a4f12d..6c17550851 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -66,6 +66,7 @@ #endif const static int GROUP_FILE_FORMAT_VERSION = 1; +const static int EXT_INST_FILE_FORMAT_VERSION = 1; InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent) : QAbstractListModel(parent), m_globalSettings(settings) @@ -79,10 +80,10 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, Q connect(this, &InstanceList::instancesChanged, this, &InstanceList::providerUpdated); // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! - m_instDir = QDir(instDir).canonicalPath(); + m_instRootDir = QDir(instDir).canonicalPath(); m_watcher = new QFileSystemWatcher(this); connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &InstanceList::instanceDirContentsChanged); - m_watcher->addPath(m_instDir); + m_watcher->addPath(m_instRootDir); } InstanceList::~InstanceList() {} @@ -425,11 +426,11 @@ static QMap getIdMapping(const QList& return out; } -QList InstanceList::discoverInstances() +QList discoverInstancesFormDir(QString& path) { - qDebug() << "Discovering instances in" << m_instDir; + qDebug() << "Discovering instances in" << path; QList out; - QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); + QDirIterator iter(path, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); while (iter.hasNext()) { QString subDir = iter.next(); QFileInfo dirInfo(subDir); @@ -438,7 +439,7 @@ QList InstanceList::discoverInstances() // if it is a symlink, ignore it if it goes to the instance folder if (dirInfo.isSymLink()) { QFileInfo targetInfo(dirInfo.symLinkTarget()); - QFileInfo instDirInfo(m_instDir); + QFileInfo instDirInfo(path); if (targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) { qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder"; continue; @@ -448,33 +449,139 @@ QList InstanceList::discoverInstances() out.append(id); qDebug() << "Found instance ID" << id; } -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - instanceSet = QSet(out.begin(), out.end()); -#else - instanceSet = out.toSet(); -#endif - m_instancesProbed = true; return out; } +void InstanceList::saveExtInstDir() +{ + qDebug() << "Will save external instance directory now."; + + WatchLock foo(m_watcher, m_instRootDir); + QString extInstDirFileName = m_instRootDir + "/extinstdir.json"; + + QJsonObject jsonObject; + jsonObject.insert("formatVersion", QJsonValue(QString("1"))); + + QJsonArray extArray; + for (auto& string : m_extInstDir) { + if (!QFileInfo(string).isDir()) { + qWarning() << "Skip" << string << "because it is not a Directory."; + continue; + } + extArray.append(string); + } + + jsonObject.insert("ext", extArray); + QJsonDocument doc(jsonObject); + try { + FS::write(extInstDirFileName, doc.toJson()); + qDebug() << "External instance directory saved."; + } catch (const FS::FileSystemException& e) { + qCritical() << "Failed to write external instance directory file :" << e.cause(); + } +} + +void InstanceList::loadExtInstDir() +{ + qDebug() << "Will load external instance directory now."; + + QString extInstDirFileName = m_instRootDir + "/extinstdir.json"; + + if (!QFileInfo(extInstDirFileName).exists()) + return; + + QByteArray jsonData; + try { + jsonData = FS::read(extInstDirFileName); + } catch (const FS::FileSystemException& e) { + qCritical() << "Failed to read external instance directory file :" << e.cause(); + return; + } + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); + + // if the json was bad, fail + if (error.error != QJsonParseError::NoError) { + qCritical() << QString("Failed to parse external instance directory file: %1 at offset %2") + .arg(error.errorString(), QString::number(error.offset)) + .toUtf8(); + return; + } + + // if the root of the json wasn't an object, fail + if (!jsonDoc.isObject()) { + qWarning() << "Invalid external instance directory file. Root entry should be an object."; + return; + } + + QJsonObject rootObj = jsonDoc.object(); + + // Make sure the format version matches, otherwise fail. + if (rootObj.value("formatVersion").toVariant().toInt() != EXT_INST_FILE_FORMAT_VERSION) + return; + + // Get the ext instance dir. if it's not an object, fail + if (!rootObj.value("ext").isArray()) { + qWarning() << "Invalid external instance directory JSON: 'ext' should be an object."; + return; + } + + m_extInstDir.clear(); + + QJsonArray extArray = rootObj.value("ext").toArray(); + for (auto value : extArray) { + auto string = value.toString(); + if (!QFileInfo(string).isDir()) { + qWarning() << "Skip" << string << "because it is not a Directory."; + continue; + } + m_extInstDir.append(string); + } + m_extInstDirLoaded = true; + qDebug() << "External instance directory loaded."; +} + InstanceList::InstListError InstanceList::loadList() { auto existingIds = getIdMapping(m_instances); QList newList; - for (auto& id : discoverInstances()) { - if (existingIds.contains(id)) { - auto instPair = existingIds[id]; - existingIds.remove(id); - qDebug() << "Should keep and soft-reload" << id; - } else { - InstancePtr instPtr = loadInstance(id); - if (instPtr) { - newList.append(instPtr); + if (!m_extInstDirLoaded) { + loadExtInstDir(); + } + QList allInstDir(m_instRootDir); + allInstDir.append(m_extInstDir); + + QList allInst; + for (auto& path : allInstDir) { + auto list = discoverInstancesFormDir(path); + for (auto& id : list) { + if (allInst.contains(id)) { + qWarning() << "The" << id << "of the same name already exists in the previous directory, so this instance of" << path + << "is skipped."; + continue; + } + allInst.append(id); + if (existingIds.contains(id)) { + auto instPair = existingIds[id]; + existingIds.remove(id); + qDebug() << "Should keep and soft-reload" << id; + } else { + InstancePtr instPtr = loadInstance(id, path); + if (instPtr) { + newList.append(instPtr); + } } } } +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + instanceSet = QSet(allInst.begin(), allInst.end()); +#else + instanceSet = allInst.toSet(); +#endif + m_instancesProbed = true; // TODO: looks like a general algorithm with a few specifics inserted. Do something about it. if (!existingIds.isEmpty()) { @@ -620,13 +727,13 @@ void InstanceList::propertiesChanged(BaseInstance* inst) } } -InstancePtr InstanceList::loadInstance(const InstanceId& id) +InstancePtr InstanceList::loadInstance(const InstanceId& id, const QString& instDir) { if (!m_groupsLoaded) { loadGroupList(); } - auto instanceRoot = FS::PathCombine(m_instDir, id); + auto instanceRoot = FS::PathCombine(instDir, id); auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); InstancePtr inst; @@ -671,8 +778,8 @@ void InstanceList::saveGroupList() qDebug() << "Group saving prevented because we don't know the full list of instances yet."; return; } - WatchLock foo(m_watcher, m_instDir); - QString groupFileName = m_instDir + "/instgroups.json"; + WatchLock foo(m_watcher, m_instRootDir); + QString groupFileName = m_instRootDir + "/instgroups.json"; QMap> reverseGroupMap; for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) { const QString& id = iter.key(); @@ -722,7 +829,7 @@ void InstanceList::loadGroupList() { qDebug() << "Will load group list now."; - QString groupFileName = m_instDir + "/instgroups.json"; + QString groupFileName = m_instRootDir + "/instgroups.json"; // if there's no group file, fail if (!QFileInfo(groupFileName).exists()) @@ -817,15 +924,22 @@ void InstanceList::instanceDirContentsChanged(const QString& path) void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, QVariant value) { QString newInstDir = QDir(value.toString()).canonicalPath(); - if (newInstDir != m_instDir) { + if (newInstDir != m_instRootDir) { if (m_groupsLoaded) { saveGroupList(); + m_groupsLoaded = false; + } + if (m_extInstDirLoaded) { + saveExtInstDir(); + m_extInstDirLoaded = false; + } + m_instRootDir = newInstDir; + + if (count() > 0) { + beginRemoveRows(QModelIndex(), 0, count() - 1); + m_instances.erase(m_instances.begin(), m_instances.end()); + endRemoveRows(); } - m_instDir = newInstDir; - m_groupsLoaded = false; - beginRemoveRows(QModelIndex(), 0, count()); - m_instances.erase(m_instances.begin(), m_instances.end()); - endRemoveRows(); emit instancesChanged(); } } @@ -936,13 +1050,13 @@ QString InstanceList::getStagedInstancePath() QString key = QUuid::createUuid().toString(QUuid::WithoutBraces); QString tempDir = ".LAUNCHER_TEMP/"; QString relPath = FS::PathCombine(tempDir, key); - QDir rootPath(m_instDir); - auto path = FS::PathCombine(m_instDir, relPath); + QDir rootPath(m_instRootDir); + auto path = FS::PathCombine(m_instRootDir, relPath); if (!rootPath.mkpath(relPath)) { return QString(); } #ifdef Q_OS_WIN32 - auto tempPath = FS::PathCombine(m_instDir, tempDir); + auto tempPath = FS::PathCombine(m_instRootDir, tempDir); SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); #endif return path; @@ -965,14 +1079,14 @@ bool InstanceList::commitStagedInstance(const QString& path, if (should_override) { instID = commiting.originalInstanceID(); } else { - instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir); + instID = FS::DirNameFromString(instanceName.modifiedName(), m_instRootDir); } Q_ASSERT(!instID.isEmpty()); { - WatchLock lock(m_watcher, m_instDir); - QString destination = FS::PathCombine(m_instDir, instID); + WatchLock lock(m_watcher, m_instRootDir); + QString destination = FS::PathCombine(m_instRootDir, instID); if (should_override) { if (!FS::overrideFolder(destination, path)) { @@ -1009,5 +1123,18 @@ int InstanceList::getTotalPlayTime() updateTotalPlayTime(); return totalPlayTime; } +QStringList InstanceList::getExtInstDir() +{ + if (!m_extInstDirLoaded) + loadExtInstDir(); + return m_extInstDir; +} +void InstanceList::setExtInstDir(const QStringList& newValue) +{ + if (m_extInstDir == newValue) + return; + m_extInstDir = newValue; + saveExtInstDir(); +} #include "InstanceList.moc" diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index 5ddddee95d..96c14f390b 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -94,6 +94,9 @@ class InstanceList : public QAbstractListModel { int count() const { return m_instances.count(); } + QStringList getExtInstDir(); + void setExtInstDir(const QStringList& newValue); + InstListError loadList(); void saveNow(); @@ -176,8 +179,9 @@ class InstanceList : public QAbstractListModel { void add(const QList& list); void loadGroupList(); void saveGroupList(); - QList discoverInstances(); - InstancePtr loadInstance(const InstanceId& id); + void loadExtInstDir(); + void saveExtInstDir(); + InstancePtr loadInstance(const InstanceId& id, const QString& instDir); void increaseGroupCount(const QString& group); void decreaseGroupCount(const QString& group); @@ -191,7 +195,7 @@ class InstanceList : public QAbstractListModel { QMap m_groupNameCache; SettingsObjectPtr m_globalSettings; - QString m_instDir; + QString m_instRootDir; QFileSystemWatcher* m_watcher; // FIXME: this is so inefficient that looking at it is almost painful. QSet m_collapsedGroups; @@ -201,4 +205,6 @@ class InstanceList : public QAbstractListModel { bool m_instancesProbed = false; QStack m_trashHistory; + QStringList m_extInstDir; + bool m_extInstDirLoaded = false; }; diff --git a/launcher/ui/pages/global/InstancesDirListPage.cpp b/launcher/ui/pages/global/InstancesDirListPage.cpp new file mode 100644 index 0000000000..3212b83f24 --- /dev/null +++ b/launcher/ui/pages/global/InstancesDirListPage.cpp @@ -0,0 +1,218 @@ + +#include "InstancesDirListPage.h" +#include "ui_InstancesDirListPage.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "BuildConfig.h" +#include "DesktopServices.h" +#include "FileSystem.h" +#include "InstanceList.h" +class FolderButtonDelegate : public QStyledItemDelegate { + Q_OBJECT + public: + explicit FolderButtonDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override + { + Q_UNUSED(index); + Q_UNUSED(option); + auto* editorWidget = new QWidget(parent); + auto* layout = new QHBoxLayout(editorWidget); + + auto* lineEdit = new QLineEdit(editorWidget); + lineEdit->installEventFilter(const_cast(this)); + auto* pushButton = new QPushButton(tr("Open"), editorWidget); + connect(pushButton, &QPushButton::clicked, [=]() { + QString raw_dir = QFileDialog::getExistingDirectory(editorWidget, tr("External Instance Folder"), lineEdit->text()); + if (!raw_dir.isEmpty()) + lineEdit->setText(raw_dir); + }); + layout->addWidget(lineEdit); + layout->addWidget(pushButton); + + layout->setContentsMargins(0, 0, 0, 0); + editorWidget->setLayout(layout); + + return editorWidget; + } + + bool eventFilter(QObject* obj, QEvent* event) override + { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + emit commitData(dynamic_cast(obj)); + emit closeEditor(dynamic_cast(obj)); + return true; + } + } + return false; + } + + void setEditorData(QWidget* editor, const QModelIndex& index) const override + { + auto text = index.data(Qt::EditRole).toString(); + auto* lineEdit = qobject_cast(editor->layout()->itemAt(0)->widget()); + lineEdit->setText(text); + } + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override + { + auto* lineEdit = qobject_cast(editor->layout()->itemAt(0)->widget()); + auto text = lineEdit->text(); + if (InstancesDirListPage::verifyInstDirPath(text)) { + QString cooked_dir = FS::NormalizePath(text); + if (!dynamic_cast(model)->stringList().contains(cooked_dir)) + model->setData(index, cooked_dir, Qt::EditRole); + } + } + void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override + { + Q_UNUSED(index); + editor->setGeometry(option.rect); + } +}; + +InstancesDirListPage::InstancesDirListPage(QWidget* parent) : QMainWindow(parent), ui(new Ui::InstancesDirListPage) +{ + ui->setupUi(this); + ui->listView->setEmptyString( + tr("Welcome!\n" + "You can add external instance directories here. (The directory where the instance folder is stored, not the instance folder " + "itself.)")); + ui->listView->setEmptyMode(VersionListView::String); + ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); + ui->listView->header()->setSectionResizeMode(QHeaderView::Stretch); + ui->listView->setHeaderHidden(true); + ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked); + + m_model = new QStringListModel(this); + + ui->listView->setModel(m_model); + + ui->listView->setItemDelegate(new FolderButtonDelegate(ui->listView)); + connect(ui->listView, &VersionListView::customContextMenuRequested, this, &InstancesDirListPage::ShowContextMenu); +} + +InstancesDirListPage::~InstancesDirListPage() +{ + delete ui; +} + +void InstancesDirListPage::retranslate() +{ + ui->retranslateUi(this); +} + +void InstancesDirListPage::ShowContextMenu(const QPoint& pos) +{ + auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + menu->exec(ui->listView->mapToGlobal(pos)); + delete menu; +} + +void InstancesDirListPage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } + QMainWindow::changeEvent(event); +} + +QMenu* InstancesDirListPage::createPopupMenu() +{ + QMenu* filteredMenu = QMainWindow::createPopupMenu(); + filteredMenu->removeAction(ui->toolBar->toggleViewAction()); + return filteredMenu; +} + +inline bool InstancesDirListPage::verifyInstDirPath(const QString& raw_dir) +{ + bool result = false; + if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { + QString cooked_dir = FS::NormalizePath(raw_dir); + if (FS::checkProblemticPathJava(QDir(cooked_dir))) { + QMessageBox warning; + warning.setText( + tr("You're trying to specify an instance folder which\'s path " + "contains at least one \'!\'. " + "Java is known to cause problems if that is the case, your " + "instances (probably) won't start!")); + warning.setInformativeText( + tr("Do you really want to use this path? " + "Selecting \"No\" will close this and not alter your instance path.")); + warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + int result1 = warning.exec(); + if (result1 == QMessageBox::Ok) { + result = true; + } + } else if (APPLICATION->settings()->get("InstanceDir").toString() == cooked_dir) { + QMessageBox warning; + warning.setText( + tr("The external instance directory cannot be set to the root directory!").arg(BuildConfig.LAUNCHER_DISPLAYNAME)); + warning.setStandardButtons(QMessageBox::Cancel); + warning.exec(); + } else if (DesktopServices::isFlatpak() && raw_dir.startsWith("/run/user")) { + QMessageBox warning; + warning.setText(tr("You're trying to specify an instance folder " + "which was granted temporarily via Flatpak.\n" + "This is known to cause problems. " + "After a restart the launcher might break, " + "because it will no longer have access to that directory.\n\n" + "Granting %1 access to it via Flatseal is recommended.") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME)); + warning.setInformativeText(tr("Do you want to proceed anyway?")); + warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + int result1 = warning.exec(); + if (result1 == QMessageBox::Ok) { + result = true; + } + } else { + result = true; + } + } + return result; +} + +void InstancesDirListPage::on_actionAddExtInst_triggered() +{ + QString raw_dir = QFileDialog::getExistingDirectory(this, tr("External Instance Folder")); + + if (verifyInstDirPath(raw_dir)) { + QString cooked_dir = FS::NormalizePath(raw_dir); + m_model->insertRow(m_model->rowCount()); + auto index = m_model->index(m_model->rowCount() - 1, 0); + m_model->setData(index, cooked_dir, Qt::DisplayRole); + ui->listView->setCurrentIndex(index); + } +} + +void InstancesDirListPage::on_actionRemove_triggered() +{ + auto index = ui->listView->currentIndex(); + + m_model->removeRow(index.row()); +} + +void InstancesDirListPage::on_actionHide_triggered() {} +bool InstancesDirListPage::apply() +{ + if (m_rootInstDir == APPLICATION->settings()->get("InstanceDir").toString()) + APPLICATION->instances()->setExtInstDir(m_model->stringList()); + + return true; +} +void InstancesDirListPage::openedImpl() +{ + m_rootInstDir = APPLICATION->settings()->get("InstanceDir").toString(); + m_model->setStringList(APPLICATION->instances()->getExtInstDir()); +} +#include "InstancesDirListPage.moc" \ No newline at end of file diff --git a/launcher/ui/pages/global/InstancesDirListPage.h b/launcher/ui/pages/global/InstancesDirListPage.h new file mode 100644 index 0000000000..6962c9aa59 --- /dev/null +++ b/launcher/ui/pages/global/InstancesDirListPage.h @@ -0,0 +1,54 @@ + +#pragma once + +#include +#include + +#include "ui/pages/BasePage.h" + +#include "Application.h" +class QStringListModel; + +namespace Ui { +class InstancesDirListPage; +} + +class InstancesDirListPage : public QMainWindow, public BasePage { + Q_OBJECT + + public: + inline static bool verifyInstDirPath(const QString& raw_dir); + + explicit InstancesDirListPage(QWidget* parent = 0); + ~InstancesDirListPage(); + + QString displayName() const override { return tr("External Instance"); } + QIcon icon() const override + { + auto icon = APPLICATION->getThemedIcon("accounts"); + if (icon.isNull()) { + icon = APPLICATION->getThemedIcon("noaccount"); + } + return icon; + } + QString id() const override { return "external-Instance-directory"; } + QString helpPage() const override { return "Getting-Started#adding-an-account"; } + void retranslate() override; + bool apply() override; + void openedImpl() override; + public slots: + void on_actionRemove_triggered(); + void on_actionAddExtInst_triggered(); + void on_actionHide_triggered(); + + protected slots: + void ShowContextMenu(const QPoint& pos); + + private: + void changeEvent(QEvent* event) override; + QMenu* createPopupMenu() override; + + QString m_rootInstDir; + QStringListModel* m_model; + Ui::InstancesDirListPage* ui; +}; diff --git a/launcher/ui/pages/global/InstancesDirListPage.ui b/launcher/ui/pages/global/InstancesDirListPage.ui new file mode 100644 index 0000000000..e8755e53f0 --- /dev/null +++ b/launcher/ui/pages/global/InstancesDirListPage.ui @@ -0,0 +1,71 @@ + + + InstancesDirListPage + + + + 0 + 0 + 800 + 600 + + + + + + + + true + + + false + + + false + + + true + + + false + + + + + + + + RightToolBarArea + + + false + + + + + + + Remove + + + + + Add Instance Dir + + + + + + VersionListView + QTreeView +
ui/widgets/VersionListView.h
+
+ + WideBar + QToolBar +
ui/widgets/WideBar.h
+
+
+ + +
From af1760edb2b6abe22a14de9fb0be0321d7ac446e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6?= <2411829240@qq.com> Date: Sat, 10 Feb 2024 00:14:01 +0800 Subject: [PATCH 2/7] =?UTF-8?q?Fix=20a=20bug=20where=20qt5=20could=20not?= =?UTF-8?q?=20be=20used=20Signed-off-by:=20=E5=88=9D=E5=A4=8F=E5=90=8C?= =?UTF-8?q?=E5=AD=A6=20<2411829240@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- launcher/InstanceList.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 6c17550851..d498c0a1d4 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -551,7 +551,8 @@ InstanceList::InstListError InstanceList::loadList() if (!m_extInstDirLoaded) { loadExtInstDir(); } - QList allInstDir(m_instRootDir); + QList allInstDir; + allInstDir.append(m_instRootDir); allInstDir.append(m_extInstDir); QList allInst; From 220a0984fb23a7317ede0c5362433b7f28ec72ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6?= <2411829240@qq.com> Date: Sat, 10 Feb 2024 00:47:24 +0800 Subject: [PATCH 3/7] =?UTF-8?q?Add=20an=20environment=20variable=20to=20mo?= =?UTF-8?q?dify=20the=20user=20data=20directory=20Signed-off-by:=20?= =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6=20<2411829240@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- launcher/Application.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 946b40c6f0..6bf3e8a9cb 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -298,23 +298,29 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) adjustedBy = "Command line"; dataPath = dirParam; } else { - QDir foo; - if (DesktopServices::isSnap()) { - foo = QDir(getenv("SNAP_USER_COMMON")); + auto env = QString(getenv("PRISM_LAUNCHER_COMMON_DIR")); + if (!env.isEmpty() && FS::ensureFolderPathExists(env)) { + dataPath = QDir(env).absolutePath(); + adjustedBy = "Environment variable PRISM_LAUNCHER_COMMON_DIR"; } else { - foo = QDir(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); - } + QDir foo; + if (DesktopServices::isSnap()) { + foo = QDir(getenv("SNAP_USER_COMMON")); + } else { + foo = QDir(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); + } - dataPath = foo.absolutePath(); - adjustedBy = "Persistent data path"; + dataPath = foo.absolutePath(); + adjustedBy = "Persistent data path"; #ifndef Q_OS_MACOS - if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { - dataPath = m_rootPath; - adjustedBy = "Portable data path"; - m_portable = true; - } + if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { + dataPath = m_rootPath; + adjustedBy = "Portable data path"; + m_portable = true; + } #endif + } } if (!FS::ensureFolderPathExists(dataPath)) { From a31a83271f7a0133eb291ed48d6b095541c176a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6?= <2411829240@qq.com> Date: Sat, 10 Feb 2024 01:41:01 +0800 Subject: [PATCH 4/7] =?UTF-8?q?Add=20copyright=20headers=20Signed-off-by:?= =?UTF-8?q?=20=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6=20<2411829240@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/pages/global/InstancesDirListPage.cpp | 17 +++++++++++++++++ launcher/ui/pages/global/InstancesDirListPage.h | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/launcher/ui/pages/global/InstancesDirListPage.cpp b/launcher/ui/pages/global/InstancesDirListPage.cpp index 3212b83f24..2acc7783c6 100644 --- a/launcher/ui/pages/global/InstancesDirListPage.cpp +++ b/launcher/ui/pages/global/InstancesDirListPage.cpp @@ -1,3 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 初夏同学 <2411829240@qq.com> + * + * 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 . + */ #include "InstancesDirListPage.h" #include "ui_InstancesDirListPage.h" diff --git a/launcher/ui/pages/global/InstancesDirListPage.h b/launcher/ui/pages/global/InstancesDirListPage.h index 6962c9aa59..6d1cbd452c 100644 --- a/launcher/ui/pages/global/InstancesDirListPage.h +++ b/launcher/ui/pages/global/InstancesDirListPage.h @@ -1,3 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 初夏同学 <2411829240@qq.com> + * + * 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 . + */ #pragma once From e38941210bb37726e12021b92d4df06dbce34062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6?= <2411829240@qq.com> Date: Sat, 10 Feb 2024 08:56:57 +0800 Subject: [PATCH 5/7] =?UTF-8?q?Change=20the=20ICON=20and=20name=20of=20the?= =?UTF-8?q?=20ExternalInstancePage=20Signed-off-by:=20=E5=88=9D=E5=A4=8F?= =?UTF-8?q?=E5=90=8C=E5=AD=A6=20<2411829240@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- launcher/Application.cpp | 4 +-- launcher/CMakeLists.txt | 6 ++-- ...rListPage.cpp => ExternalInstancePage.cpp} | 34 +++++++++---------- ...esDirListPage.h => ExternalInstancePage.h} | 19 ++++------- ...DirListPage.ui => ExternalInstancePage.ui} | 4 +-- 5 files changed, 30 insertions(+), 37 deletions(-) rename launcher/ui/pages/global/{InstancesDirListPage.cpp => ExternalInstancePage.cpp} (90%) rename launcher/ui/pages/global/{InstancesDirListPage.h => ExternalInstancePage.h} (79%) rename launcher/ui/pages/global/{InstancesDirListPage.ui => ExternalInstancePage.ui} (96%) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 6bf3e8a9cb..ebbb337a00 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -59,8 +59,8 @@ #include "ui/pages/global/AccountListPage.h" #include "ui/pages/global/CustomCommandsPage.h" #include "ui/pages/global/EnvironmentVariablesPage.h" +#include "ui/pages/global/ExternalInstancePage.h" #include "ui/pages/global/ExternalToolsPage.h" -#include "ui/pages/global/InstancesDirListPage.h" #include "ui/pages/global/JavaPage.h" #include "ui/pages/global/LanguagePage.h" #include "ui/pages/global/LauncherPage.h" @@ -765,7 +765,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) { m_globalSettingsProvider = std::make_shared(tr("Settings")); m_globalSettingsProvider->addPage(); - m_globalSettingsProvider->addPage(); + m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); m_globalSettingsProvider->addPage(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 68d29ff721..a44ef874a0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -887,8 +887,8 @@ SET(LAUNCHER_SOURCES # GUI - global settings pages ui/pages/global/AccountListPage.cpp ui/pages/global/AccountListPage.h - ui/pages/global/InstancesDirListPage.cpp - ui/pages/global/InstancesDirListPage.h + ui/pages/global/ExternalInstancePage.cpp + ui/pages/global/ExternalInstancePage.h ui/pages/global/CustomCommandsPage.cpp ui/pages/global/CustomCommandsPage.h ui/pages/global/EnvironmentVariablesPage.cpp @@ -1126,7 +1126,7 @@ qt_wrap_ui(LAUNCHER_UI ui/setupwizard/PasteWizardPage.ui ui/setupwizard/ThemeWizardPage.ui ui/pages/global/AccountListPage.ui - ui/pages/global/InstancesDirListPage.ui + ui/pages/global/ExternalInstancePage.ui ui/pages/global/JavaPage.ui ui/pages/global/LauncherPage.ui ui/pages/global/APIPage.ui diff --git a/launcher/ui/pages/global/InstancesDirListPage.cpp b/launcher/ui/pages/global/ExternalInstancePage.cpp similarity index 90% rename from launcher/ui/pages/global/InstancesDirListPage.cpp rename to launcher/ui/pages/global/ExternalInstancePage.cpp index 2acc7783c6..4791e2e95e 100644 --- a/launcher/ui/pages/global/InstancesDirListPage.cpp +++ b/launcher/ui/pages/global/ExternalInstancePage.cpp @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -#include "InstancesDirListPage.h" -#include "ui_InstancesDirListPage.h" +#include "ExternalInstancePage.h" +#include "ui_ExternalInstancePage.h" #include #include @@ -85,7 +85,7 @@ class FolderButtonDelegate : public QStyledItemDelegate { { auto* lineEdit = qobject_cast(editor->layout()->itemAt(0)->widget()); auto text = lineEdit->text(); - if (InstancesDirListPage::verifyInstDirPath(text)) { + if (ExternalInstancePage::verifyInstDirPath(text)) { QString cooked_dir = FS::NormalizePath(text); if (!dynamic_cast(model)->stringList().contains(cooked_dir)) model->setData(index, cooked_dir, Qt::EditRole); @@ -98,7 +98,7 @@ class FolderButtonDelegate : public QStyledItemDelegate { } }; -InstancesDirListPage::InstancesDirListPage(QWidget* parent) : QMainWindow(parent), ui(new Ui::InstancesDirListPage) +ExternalInstancePage::ExternalInstancePage(QWidget* parent) : QMainWindow(parent), ui(new Ui::ExternalInstancePage) { ui->setupUi(this); ui->listView->setEmptyString( @@ -116,27 +116,27 @@ InstancesDirListPage::InstancesDirListPage(QWidget* parent) : QMainWindow(parent ui->listView->setModel(m_model); ui->listView->setItemDelegate(new FolderButtonDelegate(ui->listView)); - connect(ui->listView, &VersionListView::customContextMenuRequested, this, &InstancesDirListPage::ShowContextMenu); + connect(ui->listView, &VersionListView::customContextMenuRequested, this, &ExternalInstancePage::ShowContextMenu); } -InstancesDirListPage::~InstancesDirListPage() +ExternalInstancePage::~ExternalInstancePage() { delete ui; } -void InstancesDirListPage::retranslate() +void ExternalInstancePage::retranslate() { ui->retranslateUi(this); } -void InstancesDirListPage::ShowContextMenu(const QPoint& pos) +void ExternalInstancePage::ShowContextMenu(const QPoint& pos) { auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); menu->exec(ui->listView->mapToGlobal(pos)); delete menu; } -void InstancesDirListPage::changeEvent(QEvent* event) +void ExternalInstancePage::changeEvent(QEvent* event) { if (event->type() == QEvent::LanguageChange) { ui->retranslateUi(this); @@ -144,14 +144,14 @@ void InstancesDirListPage::changeEvent(QEvent* event) QMainWindow::changeEvent(event); } -QMenu* InstancesDirListPage::createPopupMenu() +QMenu* ExternalInstancePage::createPopupMenu() { QMenu* filteredMenu = QMainWindow::createPopupMenu(); filteredMenu->removeAction(ui->toolBar->toggleViewAction()); return filteredMenu; } -inline bool InstancesDirListPage::verifyInstDirPath(const QString& raw_dir) +inline bool ExternalInstancePage::verifyInstDirPath(const QString& raw_dir) { bool result = false; if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { @@ -199,7 +199,7 @@ inline bool InstancesDirListPage::verifyInstDirPath(const QString& raw_dir) return result; } -void InstancesDirListPage::on_actionAddExtInst_triggered() +void ExternalInstancePage::on_actionAddExtInst_triggered() { QString raw_dir = QFileDialog::getExistingDirectory(this, tr("External Instance Folder")); @@ -212,24 +212,24 @@ void InstancesDirListPage::on_actionAddExtInst_triggered() } } -void InstancesDirListPage::on_actionRemove_triggered() +void ExternalInstancePage::on_actionRemove_triggered() { auto index = ui->listView->currentIndex(); m_model->removeRow(index.row()); } -void InstancesDirListPage::on_actionHide_triggered() {} -bool InstancesDirListPage::apply() +void ExternalInstancePage::on_actionHide_triggered() {} +bool ExternalInstancePage::apply() { if (m_rootInstDir == APPLICATION->settings()->get("InstanceDir").toString()) APPLICATION->instances()->setExtInstDir(m_model->stringList()); return true; } -void InstancesDirListPage::openedImpl() +void ExternalInstancePage::openedImpl() { m_rootInstDir = APPLICATION->settings()->get("InstanceDir").toString(); m_model->setStringList(APPLICATION->instances()->getExtInstDir()); } -#include "InstancesDirListPage.moc" \ No newline at end of file +#include "ExternalInstancePage.moc" \ No newline at end of file diff --git a/launcher/ui/pages/global/InstancesDirListPage.h b/launcher/ui/pages/global/ExternalInstancePage.h similarity index 79% rename from launcher/ui/pages/global/InstancesDirListPage.h rename to launcher/ui/pages/global/ExternalInstancePage.h index 6d1cbd452c..55a361d182 100644 --- a/launcher/ui/pages/global/InstancesDirListPage.h +++ b/launcher/ui/pages/global/ExternalInstancePage.h @@ -27,27 +27,20 @@ class QStringListModel; namespace Ui { -class InstancesDirListPage; +class ExternalInstancePage; } -class InstancesDirListPage : public QMainWindow, public BasePage { +class ExternalInstancePage : public QMainWindow, public BasePage { Q_OBJECT public: inline static bool verifyInstDirPath(const QString& raw_dir); - explicit InstancesDirListPage(QWidget* parent = 0); - ~InstancesDirListPage(); + explicit ExternalInstancePage(QWidget* parent = 0); + ~ExternalInstancePage(); QString displayName() const override { return tr("External Instance"); } - QIcon icon() const override - { - auto icon = APPLICATION->getThemedIcon("accounts"); - if (icon.isNull()) { - icon = APPLICATION->getThemedIcon("noaccount"); - } - return icon; - } + QIcon icon() const override { return APPLICATION->getThemedIcon("viewfolder"); } QString id() const override { return "external-Instance-directory"; } QString helpPage() const override { return "Getting-Started#adding-an-account"; } void retranslate() override; @@ -67,5 +60,5 @@ class InstancesDirListPage : public QMainWindow, public BasePage { QString m_rootInstDir; QStringListModel* m_model; - Ui::InstancesDirListPage* ui; + Ui::ExternalInstancePage* ui; }; diff --git a/launcher/ui/pages/global/InstancesDirListPage.ui b/launcher/ui/pages/global/ExternalInstancePage.ui similarity index 96% rename from launcher/ui/pages/global/InstancesDirListPage.ui rename to launcher/ui/pages/global/ExternalInstancePage.ui index e8755e53f0..20dc5de91c 100644 --- a/launcher/ui/pages/global/InstancesDirListPage.ui +++ b/launcher/ui/pages/global/ExternalInstancePage.ui @@ -1,7 +1,7 @@ - InstancesDirListPage - + ExternalInstancePage + 0 From eb67554246465f04fefaf256a3dfe4472d86cd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6?= <2411829240@qq.com> Date: Sat, 10 Feb 2024 09:43:55 +0800 Subject: [PATCH 6/7] =?UTF-8?q?Fix=20failure=20to=20properly=20end=20edits?= =?UTF-8?q?=20in=20ExternalInstancePage=20Signed-off-by:=20=E5=88=9D?= =?UTF-8?q?=E5=A4=8F=E5=90=8C=E5=AD=A6=20<2411829240@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/pages/global/ExternalInstancePage.cpp | 74 ++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/launcher/ui/pages/global/ExternalInstancePage.cpp b/launcher/ui/pages/global/ExternalInstancePage.cpp index 4791e2e95e..5fafe4d256 100644 --- a/launcher/ui/pages/global/ExternalInstancePage.cpp +++ b/launcher/ui/pages/global/ExternalInstancePage.cpp @@ -34,57 +34,73 @@ #include "DesktopServices.h" #include "FileSystem.h" #include "InstanceList.h" -class FolderButtonDelegate : public QStyledItemDelegate { +class EditWidget : public QWidget { Q_OBJECT public: - explicit FolderButtonDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} - QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override + explicit EditWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) : QWidget(parent, f) { - Q_UNUSED(index); - Q_UNUSED(option); - auto* editorWidget = new QWidget(parent); - auto* layout = new QHBoxLayout(editorWidget); - - auto* lineEdit = new QLineEdit(editorWidget); - lineEdit->installEventFilter(const_cast(this)); - auto* pushButton = new QPushButton(tr("Open"), editorWidget); + auto* layout = new QHBoxLayout(this); + m_lineEdit = new QLineEdit(this); + m_lineEdit->installEventFilter(const_cast(this)); + auto* pushButton = new QPushButton(tr("Open"), this); connect(pushButton, &QPushButton::clicked, [=]() { - QString raw_dir = QFileDialog::getExistingDirectory(editorWidget, tr("External Instance Folder"), lineEdit->text()); + QString raw_dir = QFileDialog::getExistingDirectory(pushButton, tr("External Instance Folder"), m_lineEdit->text()); if (!raw_dir.isEmpty()) - lineEdit->setText(raw_dir); + m_lineEdit->setText(raw_dir); }); - layout->addWidget(lineEdit); + + layout->addWidget(m_lineEdit); layout->addWidget(pushButton); layout->setContentsMargins(0, 0, 0, 0); - editorWidget->setLayout(layout); - - return editorWidget; - } + this->setLayout(layout); + }; + ~EditWidget() override = default; bool eventFilter(QObject* obj, QEvent* event) override { if (event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(event); + auto* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { - emit commitData(dynamic_cast(obj)); - emit closeEditor(dynamic_cast(obj)); + emit editDone(); return true; } } return false; } + inline void setData(const QString& data) { m_lineEdit->setText(data); } + inline QString getData() { return m_lineEdit->text(); } + + signals: + void editDone(); + + private: + QLineEdit* m_lineEdit; +}; + +class FolderButtonDelegate : public QStyledItemDelegate { + Q_OBJECT + public: + explicit FolderButtonDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override + { + Q_UNUSED(index); + Q_UNUSED(option); + auto* editorWidget = new EditWidget(parent); + connect(editorWidget, &EditWidget::editDone, this, &FolderButtonDelegate::editDone); + + return editorWidget; + } + void setEditorData(QWidget* editor, const QModelIndex& index) const override { auto text = index.data(Qt::EditRole).toString(); - auto* lineEdit = qobject_cast(editor->layout()->itemAt(0)->widget()); - lineEdit->setText(text); + qobject_cast(editor)->setData(text); } void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override { - auto* lineEdit = qobject_cast(editor->layout()->itemAt(0)->widget()); - auto text = lineEdit->text(); + auto text = qobject_cast(editor)->getData(); if (ExternalInstancePage::verifyInstDirPath(text)) { QString cooked_dir = FS::NormalizePath(text); if (!dynamic_cast(model)->stringList().contains(cooked_dir)) @@ -96,6 +112,14 @@ class FolderButtonDelegate : public QStyledItemDelegate { Q_UNUSED(index); editor->setGeometry(option.rect); } + + public slots: + void editDone() + { + auto* editor = qobject_cast(sender()); + emit commitData(editor); + emit closeEditor(editor); + }; }; ExternalInstancePage::ExternalInstancePage(QWidget* parent) : QMainWindow(parent), ui(new Ui::ExternalInstancePage) From 4bb54e2f2e3dcfe5bed757ec3f597a1df166a77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6?= <2411829240@qq.com> Date: Sat, 10 Feb 2024 14:36:20 +0800 Subject: [PATCH 7/7] =?UTF-8?q?Add=20new=20parameter=20options=20to=20the?= =?UTF-8?q?=20command=20line=20to=20adjust=20the=20main=20Settings=20file?= =?UTF-8?q?=20Signed-off-by:=20=E5=88=9D=E5=A4=8F=E5=90=8C=E5=AD=A6=20<241?= =?UTF-8?q?1829240@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- launcher/Application.cpp | 38 ++++++++++++++++++++++++--- launcher/Application.h | 1 + launcher/CMakeLists.txt | 2 ++ launcher/settings/ConstantSetting.cpp | 31 ++++++++++++++++++++++ launcher/settings/ConstantSetting.h | 37 ++++++++++++++++++++++++++ launcher/settings/SettingsObject.cpp | 14 ++++++++++ launcher/settings/SettingsObject.h | 8 ++++++ 7 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 launcher/settings/ConstantSetting.cpp create mode 100644 launcher/settings/ConstantSetting.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index ebbb337a00..3e3e1f2654 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -238,7 +238,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) { { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" }, { "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" }, { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" }, - { "show", "Opens the window for the specified instance (by instance ID)", "show" } }); + { "show", "Opens the window for the specified instance (by instance ID)", "show" }, + { "settings", "Override the configuration entry in the configuration file. Usage:--settings key1=value1&key2=value2...", + "settings" } }); // Has to be positional for some OS to handle that properly parser.addPositionalArgument("URL", "Import the resource(s) at the given URL(s) (same as -I / --import)", "[URL...]"); @@ -254,6 +256,19 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_instanceIdToShowWindowOf = parser.value("show"); + for (const auto& setting1 : parser.values("settings")) { + for (const auto& setting2 : setting1.split("&")) { + auto index = setting2.indexOf('='); + if (index != -1) { + QString key = setting2.left(index); + QString value = setting2.right(setting2.length() - index - 1); + m_settingsOverride.append(QPair(key, value)); + } else { + std::cerr << "Error format, please provide as key=value." << std::endl; + } + } + } + for (auto url : parser.values("import")) { m_urlsToImport.append(normalizeImportUrl(url)); } @@ -298,10 +313,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) adjustedBy = "Command line"; dataPath = dirParam; } else { - auto env = QString(getenv("PRISM_LAUNCHER_COMMON_DIR")); + auto varName = (BuildConfig.LAUNCHER_NAME.toUpper() + "_COMMON_DIR"); + auto env = QString(qEnvironmentVariable(varName.toUtf8().constData())); if (!env.isEmpty() && FS::ensureFolderPathExists(env)) { dataPath = QDir(env).absolutePath(); - adjustedBy = "Environment variable PRISM_LAUNCHER_COMMON_DIR"; + adjustedBy = "Environment variable" + varName; } else { QDir foo; if (DesktopServices::isSnap()) { @@ -761,6 +777,22 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) // FTBApp instances m_settings->registerSetting("FTBAppInstancesPath", ""); + // The command line sets the override + { + for (auto& setting1 : m_settingsOverride) { + if (m_settings->contains(setting1.first)) { + auto setting2 = m_settings->get(setting1.first); + if (setting2.isValid()) { + qDebug() << "The <> of setting option has an override option from the command line, <> -> <>" << setting1.first + << setting2 << setting1.second; + m_settings->registerConstant(setting1.first, setting1.second); + } else { + qDebug() << "The <> of setting option does not exist. skip." << setting1.first; + } + } + } + } + // Init page provider { m_globalSettingsProvider = std::make_shared(tr("Settings")); diff --git a/launcher/Application.h b/launcher/Application.h index 7669e08ec3..deb904a9cc 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -292,6 +292,7 @@ class Application : public QApplication { QString m_profileToUse; bool m_liveCheck = false; QList m_urlsToImport; + QList> m_settingsOverride; QString m_instanceIdToShowWindowOf; std::unique_ptr logFile; }; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a44ef874a0..e372a40de4 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -413,6 +413,8 @@ set(SETTINGS_SOURCES settings/OverrideSetting.h settings/PassthroughSetting.cpp settings/PassthroughSetting.h + settings/ConstantSetting.cpp + settings/ConstantSetting.h settings/Setting.cpp settings/Setting.h settings/SettingsObject.cpp diff --git a/launcher/settings/ConstantSetting.cpp b/launcher/settings/ConstantSetting.cpp new file mode 100644 index 0000000000..e80479d558 --- /dev/null +++ b/launcher/settings/ConstantSetting.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 初夏同学 <2411829240@qq.com> + * + * 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 . + */ + +#include "ConstantSetting.h" + +#include +ConstantSetting::ConstantSetting(std::shared_ptr origin, QVariant defVal) : Setting(origin->configKeys(), std::move(defVal)) +{ + m_other = origin; +} +QVariant ConstantSetting::get() const +{ + return m_defVal; +} +void ConstantSetting::set(QVariant value) {} +void ConstantSetting::reset() {} diff --git a/launcher/settings/ConstantSetting.h b/launcher/settings/ConstantSetting.h new file mode 100644 index 0000000000..63bddd3940 --- /dev/null +++ b/launcher/settings/ConstantSetting.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (c) 2024 初夏同学 <2411829240@qq.com> + * + * 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 . + */ + +#pragma once + +#include "Setting.h" + +/*! + * \brief A setting used for command line. It cannot be modified or saved. + */ +class ConstantSetting : public Setting { + Q_OBJECT + public: + explicit ConstantSetting(std::shared_ptr origin, QVariant defVal = QVariant()); + + virtual QVariant get() const; + virtual void set(QVariant value); + virtual void reset(); + + protected: + std::shared_ptr m_other; +}; diff --git a/launcher/settings/SettingsObject.cpp b/launcher/settings/SettingsObject.cpp index 1e5dce251e..526c576ee6 100644 --- a/launcher/settings/SettingsObject.cpp +++ b/launcher/settings/SettingsObject.cpp @@ -15,6 +15,7 @@ #include "settings/SettingsObject.h" #include +#include "ConstantSetting.h" #include "PassthroughSetting.h" #include "settings/OverrideSetting.h" #include "settings/Setting.h" @@ -54,6 +55,19 @@ std::shared_ptr SettingsObject::registerPassthrough(std::shared_ptr SettingsObject::registerConstant(const QString& id, QVariant val) +{ + if (!contains(id)) { + qCritical() << QString("Failed to register setting %1. ID does not exists.").arg(id); + return nullptr; // Fail + } + auto constant = std::make_shared(m_settings[id], val); + constant->m_storage = this; + // connectSignals(*constant); // Don't need it + m_settings[id] = constant; + return constant; +} + std::shared_ptr SettingsObject::registerSetting(QStringList synonyms, QVariant defVal) { if (synonyms.empty()) diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h index f133f2f7fc..94d759be3f 100644 --- a/launcher/settings/SettingsObject.h +++ b/launcher/settings/SettingsObject.h @@ -76,6 +76,14 @@ class SettingsObject : public QObject { */ std::shared_ptr registerPassthrough(std::shared_ptr original, std::shared_ptr gate); + /*! + * Register a constant setting. Only Settings that are already in place can be overridden. + * This setting will not be saved to a file. No changes will be made to the original + * Settings (including those stored in files). + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerConstant(const QString& id, QVariant val); + /*! * Registers the given setting with this SettingsObject and connects the necessary signals. *