diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index c3477d331..816f7b8ab 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -709,7 +709,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ToolbarsLocked", false);
+ // Instance
m_settings->registerSetting("InstSortMode", "Name");
+ m_settings->registerSetting("InstRenamingMode", "AskEverytime");
m_settings->registerSetting("SelectedInstance", QString());
// Window state and geometry
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index ccfd0b847..eab91a5eb 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -386,6 +386,12 @@ void BaseInstance::setName(QString val)
emit propertiesChanged(this);
}
+bool BaseInstance::syncInstanceDirName(const QString& newRoot) const
+{
+ auto oldRoot = instanceRoot();
+ return oldRoot == newRoot || QFile::rename(oldRoot, newRoot);
+}
+
QString BaseInstance::name() const
{
return m_settings->get("name").toString();
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 9827a08b4..2a2b4dc4a 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -126,6 +126,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this.
+ *
+ * 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 "InstanceDirUpdate.h"
+
+#include
+
+#include "Application.h"
+#include "FileSystem.h"
+
+#include "InstanceList.h"
+#include "ui/dialogs/CustomMessageBox.h"
+
+QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent)
+{
+ if (oldName == newName)
+ return QString();
+
+ QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString();
+ if (renamingMode == "MetadataOnly")
+ return QString();
+
+ auto oldRoot = instance->instanceRoot();
+ auto newDirName = FS::DirNameFromString(newName, QFileInfo(oldRoot).dir().absolutePath());
+ auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newDirName);
+ if (oldRoot == newRoot)
+ return QString();
+ if (oldRoot == FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName))
+ return QString();
+
+ // Check for conflict
+ if (QDir(newRoot).exists()) {
+ QMessageBox::warning(parent, QObject::tr("Cannot rename instance"),
+ QObject::tr("New instance root (%1) already exists.
Only the metadata will be renamed.").arg(newRoot));
+ return QString();
+ }
+
+ // Ask if we should rename
+ if (renamingMode == "AskEverytime") {
+ auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent);
+ auto dialog =
+ CustomMessageBox::selectable(parent, QObject::tr("Rename instance folder"),
+ QObject::tr("Would you also like to rename the instance folder?\n\n"
+ "Old name: %1\n"
+ "New name: %2")
+ .arg(oldName, newName),
+ QMessageBox::Question, QMessageBox::No | QMessageBox::Yes, QMessageBox::NoButton, checkBox);
+
+ auto res = dialog->exec();
+ if (checkBox->isChecked()) {
+ if (res == QMessageBox::Yes)
+ APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir");
+ else
+ APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly");
+ }
+ if (res == QMessageBox::No)
+ return QString();
+ }
+
+ // Check for linked instances
+ if (!checkLinkedInstances(instance->id(), parent, QObject::tr("Renaming")))
+ return QString();
+
+ // Now we can confirm that a renaming is happening
+ if (!instance->syncInstanceDirName(newRoot)) {
+ QMessageBox::warning(parent, QObject::tr("Cannot rename instance"),
+ QObject::tr("An error occurred when performing the following renaming operation:
"
+ " - Old instance root: %1
"
+ " - New instance root: %2
"
+ "Only the metadata is renamed.")
+ .arg(oldRoot, newRoot));
+ return QString();
+ }
+ return newRoot;
+}
+
+bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb)
+{
+ auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
+ if (!linkedInstances.empty()) {
+ auto response = CustomMessageBox::selectable(parent, QObject::tr("There are linked instances"),
+ QObject::tr("The following instance(s) might reference files in this instance:\n\n"
+ "%1\n\n"
+ "%2 it could break the other instance(s), \n\n"
+ "Do you wish to proceed?",
+ nullptr, linkedInstances.count())
+ .arg(linkedInstances.join("\n"))
+ .arg(verb),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+ if (response != QMessageBox::Yes)
+ return false;
+ }
+ return true;
+}
diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h
new file mode 100644
index 000000000..b92a59c4c
--- /dev/null
+++ b/launcher/InstanceDirUpdate.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ *
+ * 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 .
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "BaseInstance.h"
+
+/// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened
+QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent);
+
+/// Check if there are linked instances, and display a warning; return true if the operation should proceed
+bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb);
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index a9473ac15..ddf726373 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -123,6 +123,7 @@
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
+#include "InstanceDirUpdate.h"
#include "Json.h"
@@ -288,10 +289,27 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
view->setSelectionMode(QAbstractItemView::SingleSelection);
// FIXME: leaks ListViewDelegate
- view->setItemDelegate(new ListViewDelegate(this));
+ auto delegate = new ListViewDelegate(this);
+ view->setItemDelegate(delegate);
view->setFrameShape(QFrame::NoFrame);
// do not show ugly blue border on the mac
view->setAttribute(Qt::WA_MacShowFocusRect, false);
+ connect(delegate, &ListViewDelegate::textChanged, this, [this](QString before, QString after) {
+ if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, before, after, this); !newRoot.isEmpty()) {
+ auto oldID = m_selectedInstance->id();
+ auto newID = QFileInfo(newRoot).fileName();
+ QString origGroup(APPLICATION->instances()->getInstanceGroup(oldID));
+ bool syncGroup = origGroup != GroupId() && oldID != newID;
+ if (syncGroup)
+ APPLICATION->instances()->setInstanceGroup(oldID, GroupId());
+
+ refreshInstances();
+ setSelectedInstanceById(newID);
+
+ if (syncGroup)
+ APPLICATION->instances()->setInstanceGroup(newID, origGroup);
+ }
+ });
view->installEventFilter(this);
view->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -1377,20 +1395,8 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (response != QMessageBox::Yes)
return;
- auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
- if (!linkedInstances.empty()) {
- response = CustomMessageBox::selectable(this, tr("There are linked instances"),
- tr("The following instance(s) might reference files in this instance:\n\n"
- "%1\n\n"
- "Deleting it could break the other instance(s), \n\n"
- "Do you wish to proceed?",
- nullptr, linkedInstances.count())
- .arg(linkedInstances.join("\n")),
- QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
- ->exec();
- if (response != QMessageBox::Yes)
- return;
- }
+ if (!checkLinkedInstances(id, this, tr("Deleting")))
+ return;
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
diff --git a/launcher/ui/dialogs/CustomMessageBox.cpp b/launcher/ui/dialogs/CustomMessageBox.cpp
index 1af47a449..ca0fe99e0 100644
--- a/launcher/ui/dialogs/CustomMessageBox.cpp
+++ b/launcher/ui/dialogs/CustomMessageBox.cpp
@@ -21,7 +21,8 @@ QMessageBox* selectable(QWidget* parent,
const QString& text,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
- QMessageBox::StandardButton defaultButton)
+ QMessageBox::StandardButton defaultButton,
+ QCheckBox* checkBox)
{
QMessageBox* messageBox = new QMessageBox(parent);
messageBox->setWindowTitle(title);
@@ -31,6 +32,8 @@ QMessageBox* selectable(QWidget* parent,
messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
messageBox->setIcon(icon);
messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
+ if (checkBox)
+ messageBox->setCheckBox(checkBox);
return messageBox;
}
diff --git a/launcher/ui/dialogs/CustomMessageBox.h b/launcher/ui/dialogs/CustomMessageBox.h
index a9bc6a24a..1ee29903e 100644
--- a/launcher/ui/dialogs/CustomMessageBox.h
+++ b/launcher/ui/dialogs/CustomMessageBox.h
@@ -23,5 +23,6 @@ QMessageBox* selectable(QWidget* parent,
const QString& text,
QMessageBox::Icon icon = QMessageBox::NoIcon,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
- QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
+ QMessageBox::StandardButton defaultButton = QMessageBox::NoButton,
+ QCheckBox* checkBox = nullptr);
}
diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp
index d947163bc..c7115801e 100644
--- a/launcher/ui/instanceview/InstanceDelegate.cpp
+++ b/launcher/ui/instanceview/InstanceDelegate.cpp
@@ -397,6 +397,7 @@ void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
// Prevent instance names longer than 128 chars
text.truncate(128);
if (text.size() != 0) {
+ emit textChanged(model->data(index).toString(), text);
model->setData(index, text);
}
}
diff --git a/launcher/ui/instanceview/InstanceDelegate.h b/launcher/ui/instanceview/InstanceDelegate.h
index 69dd32ba7..98ff9a2fc 100644
--- a/launcher/ui/instanceview/InstanceDelegate.h
+++ b/launcher/ui/instanceview/InstanceDelegate.h
@@ -33,6 +33,9 @@ class ListViewDelegate : public QStyledItemDelegate {
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
+ signals:
+ void textChanged(QString before, QString after) const;
+
private slots:
void editingDone();
};
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 04ee01b00..d89a65a85 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -65,6 +65,15 @@ enum InstSortMode {
Sort_LastLaunch
};
+enum InstRenamingMode {
+ // Rename metadata only.
+ Rename_Always,
+ // Ask everytime.
+ Rename_Ask,
+ // Rename physical directory too.
+ Rename_Never
+};
+
LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage)
{
ui->setupUi(this);
@@ -234,6 +243,7 @@ void LauncherPage::applySettings()
s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked());
s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked());
+ // Instance
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
switch (sortMode) {
case Sort_LastLaunch:
@@ -245,6 +255,20 @@ void LauncherPage::applySettings()
break;
}
+ auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex();
+ switch (renamingMode) {
+ case Rename_Always:
+ s->set("InstRenamingMode", "MetadataOnly");
+ break;
+ case Rename_Never:
+ s->set("InstRenamingMode", "PhysicalDir");
+ break;
+ case Rename_Ask:
+ default:
+ s->set("InstRenamingMode", "AskEverytime");
+ break;
+ }
+
// Cat
s->set("CatOpacity", ui->catOpacitySpinBox->value());
@@ -299,14 +323,25 @@ void LauncherPage::loadSettings()
ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool());
ui->downloadsDirMoveCheckBox->setChecked(s->get("MoveModsFromDownloadsDir").toBool());
+ // Instance
QString sortMode = s->get("InstSortMode").toString();
-
if (sortMode == "LastLaunch") {
ui->sortLastLaunchedBtn->setChecked(true);
} else {
ui->sortByNameBtn->setChecked(true);
}
+ QString renamingMode = s->get("InstRenamingMode").toString();
+ InstRenamingMode renamingModeEnum;
+ if (renamingMode == "MetadataOnly") {
+ renamingModeEnum = Rename_Always;
+ } else if (renamingMode == "PhysicalDir") {
+ renamingModeEnum = Rename_Never;
+ } else {
+ renamingModeEnum = Rename_Ask;
+ }
+ ui->renamingBehaviorComboBox->setCurrentIndex(renamingModeEnum);
+
// Cat
ui->catOpacitySpinBox->setValue(s->get("CatOpacity").toInt());
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 31c878f3e..7942f4f4f 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -112,40 +112,76 @@
Folders
- -
-
+
-
+
- &Downloads:
+ I&nstances:
- downloadsDirTextBox
+ instDirTextBox
- -
-
+
-
+
+
+ -
+
Browse
- -
-
-
- -
-
-
- -
-
+
+
-
+
- &Skins:
-
-
- skinsDirTextBox
+ Rename instance folders
+ -
+
+
-
+
+ Never
+
+
+ -
+
+ Ask
+
+
+ -
+
+ Always
+
+
+
+
+
-
+
+
+ &Mods:
+
+
+ modsDirTextBox
+
+
+
+ -
+
+
+ -
+
+
+ Browse
+
+
+
+
+ -
&Icons:
@@ -155,7 +191,81 @@
- -
+
-
+
+
+ -
+
+
+ Browse
+
+
+
+
+ -
+
+
+ &Java:
+
+
+ javaDirTextBox
+
+
+
+ -
+
+
+ -
+
+
+ Browse
+
+
+
+
+ -
+
+
+ &Skins:
+
+
+ skinsDirTextBox
+
+
+
+ -
+
+
+ -
+
+
+ Browse
+
+
+
+
+ -
+
+
+ &Downloads:
+
+
+ downloadsDirTextBox
+
+
+
+ -
+
+
+ -
+
+
+ Browse
+
+
+
+
+ -
-
@@ -179,83 +289,6 @@
- -
-
-
- -
-
-
- &Java:
-
-
- javaDirTextBox
-
-
-
- -
-
-
- &Mods:
-
-
- modsDirTextBox
-
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- Browse
-
-
-
- -
-
-
- Browse
-
-
-
- -
-
-
- Browse
-
-
-
- -
-
-
- I&nstances:
-
-
- instDirTextBox
-
-
-
- -
-
-
- Browse
-
-
-
- -
-
-
- Browse
-
-
-