summaryrefslogtreecommitdiff
path: root/src/instancemanagerdialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/instancemanagerdialog.cpp')
-rw-r--r--src/instancemanagerdialog.cpp527
1 files changed, 174 insertions, 353 deletions
diff --git a/src/instancemanagerdialog.cpp b/src/instancemanagerdialog.cpp
index 3b6daeb8..b461d39d 100644
--- a/src/instancemanagerdialog.cpp
+++ b/src/instancemanagerdialog.cpp
@@ -13,292 +13,93 @@
using namespace MOBase;
-class InstanceInfo
+// returns the icon for the given instance or an empty 32x32 icon if the game
+// plugin couldn't be found
+//
+QIcon instanceIcon(const PluginContainer& pc, const Instance& i)
{
-public:
- struct Object
- {
- QString path;
- bool mandatoryDelete;
+ const auto* game = InstanceManager::singleton()
+ .gamePluginForDirectory(i.directory(), pc);
- Object(QString p, bool d=false)
- : path(std::move(p)), mandatoryDelete(d)
- {
- }
-
- bool operator<(const Object& o) const
- {
- if (mandatoryDelete && !o.mandatoryDelete) {
- return true;
- } else if (!mandatoryDelete && o.mandatoryDelete) {
- return false;
- }
-
- return false;
- }
- };
+ if (game)
+ return game->gameIcon();
+ QPixmap empty(32, 32);
+ empty.fill(QColor(0, 0, 0, 0));
+ return QIcon(empty);
+}
- InstanceInfo(QDir dir, bool isPortable)
- : m_portable(isPortable)
- {
- setDir(dir);
- }
-
- void setDir(const QDir& dir)
- {
- m_dir = dir;
- m_settings.reset(new Settings(InstanceManager::iniPath(dir)));
- }
-
- QString name() const
- {
- if (m_portable) {
- return QObject::tr("Portable");
- } else {
- return m_dir.dirName();
- }
- }
+// pops up a dialog to ask for an instance name when renaming
+//
+QString getInstanceName(
+ QWidget* parent, const QString& title, const QString& moreText,
+ const QString& label, const QString& oldName={})
+{
+ auto& m = InstanceManager::singleton();
- QString gameName() const
- {
- if (auto n=m_settings->game().name()) {
- if (auto e=m_settings->game().edition()) {
- if (!e->isEmpty()) {
- return *n + " (" + *e + ")";
- }
- }
+ QDialog dlg(parent);
+ dlg.setWindowTitle(title);
- return *n;
- } else {
- return {};
- }
- }
+ auto* ly = new QVBoxLayout(&dlg);
- QString gamePath() const
- {
- if (auto n=m_settings->game().directory()) {
- return QDir::toNativeSeparators(*n);
- } else {
- return {};
- }
- }
+ auto* bb = new QDialogButtonBox(
+ QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
- QString location() const
- {
- return QDir::toNativeSeparators(m_dir.path());
- }
+ auto* text = new QLineEdit(oldName);
+ text->selectAll();
- QString baseDirectory() const
- {
- return QDir::toNativeSeparators(m_settings->paths().base());
- }
+ auto* error = new QLabel;
- QString iniFile() const
- {
- return InstanceManager::iniPath(m_dir);
+ if (!moreText.isEmpty()) {
+ auto* lb = new QLabel(moreText);
+ lb->setWordWrap(true);
+ ly->addWidget(lb);
+ ly->addSpacing(10);
}
- QIcon icon(const PluginContainer& plugins) const
- {
- const auto* game = InstanceManager::singleton().gamePluginForDirectory(
- m_dir, plugins);
-
- if (game)
- return game->gameIcon();
+ auto* lb = new QLabel(label);
+ lb->setWordWrap(true);
+ ly->addWidget(lb);
- QPixmap empty(32, 32);
- empty.fill(QColor(0, 0, 0, 0));
- return QIcon(empty);
- }
+ ly->addWidget(text);
+ ly->addWidget(error);
+ ly->addStretch();
+ ly->addWidget(bb);
- bool isPortable() const
- {
- return m_portable;
- }
+ auto check = [&] {
+ bool okay = false;
- bool isActive() const
- {
- auto& m = InstanceManager::singleton();
+ if (text->text().isEmpty()) {
+ error->setText("");
+ } else if (!m.validInstanceName(text->text())) {
+ error->setText(QObject::tr("The instance name must be a valid folder name."));
+ } else {
+ const auto name = m.sanitizeInstanceName(text->text());
- if (auto i=m.currentInstance())
- {
- if (m_portable) {
- return i->isPortable();
+ if ((name != oldName) && m.instanceExists(text->text())) {
+ error->setText(QObject::tr("An instance with this name already exists."));
} else {
- return (i->name() == name());
- }
- }
-
- return false;
- }
-
- // returns a list of files and folders that must be deleted when deleting
- // this instance
- //
- std::vector<Object> objectsForDeletion() const
- {
- // native separators and ending slash
- auto prettyDir = [](auto s) {
- if (!s.endsWith("/") || !s.endsWith("\\")) {
- s += "/";
- }
-
- return QDir::toNativeSeparators(s);
- };
-
- // native separators
- auto prettyFile = [](auto s) {
- return QDir::toNativeSeparators(s);
- };
-
-
- // lowercase, native separators and ending slash
- auto canonicalDir = [](auto s) {
- s = s.toLower();
- if (!s.endsWith("/") || !s.endsWith("\\")) {
- s += "/";
- }
-
- return QDir::toNativeSeparators(s);
- };
-
- // lower and native separators
- auto canonicalFile = [](auto s) {
- return QDir::toNativeSeparators(s.toLower());
- };
-
-
-
- // whether the given directory is contained in the root
- auto dirInRoot = [&](auto root, auto dir) {
- return canonicalDir(dir).startsWith(canonicalDir(root));
- };
-
- // whether the given file is contained in the root
- auto fileInRoot = [&](auto root, auto file) {
- return canonicalFile(file).startsWith(canonicalDir(root));
- };
-
-
-
-
- const auto loc = location();
- const auto base = m_settings->paths().base();
-
-
- // directories that might contain the individual files and directories
- // set in the path settings
- std::vector<Object> roots;
-
- // a portable instance has its location in the installation directory,
- // don't delete that
- if (!isPortable()) {
- if (QDir(loc).exists()) {
- roots.push_back({loc, true});
- }
- }
-
- // the base directory is the location directory by default, don't add it
- // if it's the same
- if (canonicalDir(base) != canonicalDir(loc)) {
- if (QDir(base).exists()) {
- roots.push_back({base, false});
- }
- }
-
-
- // all the directories that are part of an instance; none of them are
- // mandatory for deletion
- const std::vector<Object> dirs = {
- m_settings->paths().downloads(),
- m_settings->paths().mods(),
- m_settings->paths().cache(),
- m_settings->paths().profiles(),
- m_settings->paths().overwrite(),
- m_dir.filePath(QString::fromStdWString(AppConfig::dumpsDir())),
- m_dir.filePath(QString::fromStdWString(AppConfig::logPath())),
- };
-
- // all the files that are part of an instance
- const std::vector<Object> files = {
- {iniFile(), true}, // the ini file must be deleted
- };
-
-
- // this will contain the root directories, plus all the individual
- // directories that are not inside these roots
- std::vector<Object> cleanDirs;
-
- for (const auto& f : dirs) {
- bool inRoots = false;
-
- for (const auto& root : roots) {
- if (dirInRoot(root.path, f.path)) {
- inRoots = true;
- break;
- }
- }
-
- if (!inRoots) {
- // not in roots, this is a path that was changed by the user; make
- // sure it exists
- if (QDir(f.path).exists()) {
- cleanDirs.push_back({prettyDir(f.path), f.mandatoryDelete});
- }
- }
- }
-
- // prepending the roots
- for (auto itor=roots.rbegin(); itor!=roots.rend(); ++itor) {
- cleanDirs.insert(
- cleanDirs.begin(),
- {prettyDir(itor->path), itor->mandatoryDelete});
- }
-
-
-
- // this will contain the individual files that are not inside the roots;
- // not that this only contains the INI file for now, so most of this is
- // useless
- std::vector<Object> cleanFiles;
-
- for (const auto& f : files) {
- bool inRoots = false;
-
- for (const auto& root : roots) {
- if (fileInRoot(root.path, f.path)) {
- inRoots = true;
- break;
- }
- }
-
- if (!inRoots) {
- // not in roots, this is a path that was changed by the user; make
- // sure it exists
- if (QFileInfo(f.path).exists()) {
- cleanFiles.push_back({prettyFile(f.path), f.mandatoryDelete});
- }
+ okay = true;
}
}
+ error->setVisible(!okay);
+ bb->button(QDialogButtonBox::Ok)->setEnabled(okay);
+ };
- // contains all the directories and files to be deleted
- std::vector<Object> all;
- all.insert(all.end(), cleanDirs.begin(), cleanDirs.end());
- all.insert(all.end(), cleanFiles.begin(), cleanFiles.end());
+ QObject::connect(text, &QLineEdit::textChanged, [&] { check(); });
+ QObject::connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); });
+ QObject::connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); });
- // mandatory on top
- std::stable_sort(all.begin(), all.end());
+ check();
- return all;
+ dlg.resize({400, 120});
+ if (dlg.exec() != QDialog::Accepted) {
+ return {};
}
-private:
- const bool m_portable;
- QDir m_dir;
- std::unique_ptr<Settings> m_settings;
-};
+ return m.sanitizeInstanceName(text->text());
+}
InstanceManagerDialog::~InstanceManagerDialog() = default;
@@ -310,7 +111,7 @@ InstanceManagerDialog::InstanceManagerDialog(
{
ui->setupUi(this);
- ui->splitter->setSizes({200, 1});
+ ui->splitter->setSizes({250, 1});
ui->splitter->setStretchFactor(0, 0);
ui->splitter->setStretchFactor(1, 1);
@@ -345,14 +146,40 @@ InstanceManagerDialog::InstanceManagerDialog(
connect(ui->close, &QPushButton::clicked, [&]{ close(); });
}
+void InstanceManagerDialog::showEvent(QShowEvent* e)
+{
+ // there might not be a global Settings object if this is called on startup
+ // when there's no current instance
+ const auto* s = Settings::maybeInstance();
+
+ if (s) {
+ s->geometry().restoreGeometry(this);
+ }
+
+ QDialog::showEvent(e);
+}
+
+void InstanceManagerDialog::done(int r)
+{
+ // there might not be a global Settings object if this is called on startup
+ // when there's no current instance
+ auto* s = Settings::maybeInstance();
+
+ if (s) {
+ s->geometry().saveGeometry(this);
+ }
+
+ QDialog::done(r);
+}
+
void InstanceManagerDialog::updateInstances()
{
auto& m = InstanceManager::singleton();
m_instances.clear();
- for (auto&& d : m.instancePaths()) {
- m_instances.push_back(std::make_unique<InstanceInfo>(d, false));
+ for (auto&& d : m.globalInstancePaths()) {
+ m_instances.push_back(std::make_unique<Instance>(d, false));
}
// sort first, prepend portable after so it's always on top
@@ -363,7 +190,12 @@ void InstanceManagerDialog::updateInstances()
if (m.portableInstanceExists()) {
m_instances.insert(
m_instances.begin(),
- std::make_unique<InstanceInfo>(m.portablePath(), true));
+ std::make_unique<Instance>(m.portablePath(), true));
+ }
+
+ // read all inis, ignore errors
+ for (auto&& i : m_instances) {
+ i->readFromIni();
}
}
@@ -374,14 +206,14 @@ void InstanceManagerDialog::updateList()
m_model->clear();
- const std::size_t NoSel = -1;
- std::size_t sel = NoSel;
+ std::size_t sel = NoSelection;
+ // creating items for instances
for (std::size_t i=0; i<m_instances.size(); ++i) {
const auto& ii = *m_instances[i];
auto* item = new QStandardItem(ii.name());
- item->setIcon(ii.icon(m_pc));
+ item->setIcon(instanceIcon(m_pc, ii));
m_model->appendRow(item);
@@ -396,7 +228,7 @@ void InstanceManagerDialog::updateList()
if (m_instances.empty()) {
select(-1);
} else {
- if (sel == NoSel) {
+ if (sel == NoSelection) {
if (prevSelIndex >= m_instances.size()) {
sel = m_instances.size() - 1;
} else {
@@ -462,10 +294,16 @@ void InstanceManagerDialog::openSelectedInstance()
return;
}
- if (m_instances[i]->isPortable()) {
+ const auto& to = *m_instances[i];
+
+ if (!confirmSwitch(to)) {
+ return;
+ }
+
+ if (to.isPortable()) {
InstanceManager::singleton().setCurrentInstance("");
} else {
- InstanceManager::singleton().setCurrentInstance(m_instances[i]->name());
+ InstanceManager::singleton().setCurrentInstance(to.name());
}
if (m_restartOnSelect) {
@@ -475,74 +313,36 @@ void InstanceManagerDialog::openSelectedInstance()
accept();
}
-QString getInstanceName(
- QWidget* parent, const QString& title, const QString& moreText,
- const QString& label, const QString& oldName={})
+bool InstanceManagerDialog::confirmSwitch(const Instance& to)
{
- auto& m = InstanceManager::singleton();
-
- QDialog dlg(parent);
- dlg.setWindowTitle(title);
-
- auto* ly = new QVBoxLayout(&dlg);
-
- auto* bb = new QDialogButtonBox(
- QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
-
- auto* text = new QLineEdit(oldName);
- text->selectAll();
-
- auto* error = new QLabel;
+ // there might not be a global Settings object if this is called on startup
+ // when there's no current instance
+ const auto* s = Settings::maybeInstance();
- if (!moreText.isEmpty()) {
- auto* lb = new QLabel(moreText);
- lb->setWordWrap(true);
- ly->addWidget(lb);
- ly->addSpacing(10);
+ // if there is are no settings, no instances are loaded and the confirmation
+ // wouldn't make sense
+ if (!s) {
+ return true;
}
- auto* lb = new QLabel(label);
- lb->setWordWrap(true);
- ly->addWidget(lb);
-
- ly->addWidget(text);
- ly->addWidget(error);
- ly->addStretch();
- ly->addWidget(bb);
-
- auto check = [&] {
- bool okay = false;
-
- if (text->text().isEmpty()) {
- error->setText("");
- } else if (!m.validInstanceName(text->text())) {
- error->setText(QObject::tr("The instance name must be a valid folder name."));
- } else {
- const auto name = m.sanitizeInstanceName(text->text());
-
- if ((name != oldName) && m.instanceExists(text->text())) {
- error->setText(QObject::tr("An instance with this name already exists."));
- } else {
- okay = true;
- }
- }
-
- error->setVisible(!okay);
- bb->button(QDialogButtonBox::Ok)->setEnabled(okay);
- };
-
- QObject::connect(text, &QLineEdit::textChanged, [&] { check(); });
- QObject::connect(bb, &QDialogButtonBox::accepted, [&]{ dlg.accept(); });
- QObject::connect(bb, &QDialogButtonBox::rejected, [&]{ dlg.reject(); });
+ if (!s->interface().showChangeGameConfirmation()) {
+ // user disabled confirmation
+ return true;
+ }
- check();
+ MOBase::TaskDialog dlg(this);
- dlg.resize({400, 120});
- if (dlg.exec() != QDialog::Accepted) {
- return {};
- }
+ const auto r = dlg
+ .title(tr("Switching instances"))
+ .main(tr("Mod Organizer must restart to manage the instance '%1'.")
+ .arg(to.name()))
+ .content(tr("This confirmation can be disabled in the settings."))
+ .icon(QMessageBox::Question)
+ .button({tr("Restart Mod Organizer"), QMessageBox::Ok})
+ .button({tr("Cancel"), QMessageBox::Cancel})
+ .exec();
- return m.sanitizeInstanceName(text->text());
+ return (r == QMessageBox::Ok);
}
void InstanceManagerDialog::rename()
@@ -561,6 +361,8 @@ void InstanceManagerDialog::rename()
return;
}
+
+ // getting new name
const auto newName = getInstanceName(
this, tr("Rename instance"), "", tr("Instance name"), i->name());
@@ -568,11 +370,16 @@ void InstanceManagerDialog::rename()
return;
}
- const QString src = i->location();
+
+ // renaming
+ const QString src = i->directory();
const QString dest = QDir::toNativeSeparators(
- QFileInfo(i->location()).dir().path() + "/" + newName);
+ QFileInfo(src).dir().path() + "/" + newName);
+
+ log::info("renaming {} to {}", src, dest);
const auto r = shell::Rename(src, dest, false);
+
if (!r) {
QMessageBox::critical(
this, tr("Error"),
@@ -582,15 +389,21 @@ void InstanceManagerDialog::rename()
return;
}
+
+ // updating ui
+ auto newInstance = std::make_unique<Instance>(dest, false);
+ i = newInstance.get();
+
m_model->item(selIndex)->setText(newName);
- i->setDir(dest);
+ m_instances[selIndex] = std::move(newInstance);
+
fillData(*i);
}
void InstanceManagerDialog::exploreLocation()
{
if (const auto* i=singleSelection()) {
- shell::Explore(i->location());
+ shell::Explore(i->directory());
}
}
@@ -604,14 +417,14 @@ void InstanceManagerDialog::exploreBaseDirectory()
void InstanceManagerDialog::exploreGame()
{
if (const auto* i=singleSelection()) {
- shell::Explore(i->gamePath());
+ shell::Explore(i->gameDirectory());
}
}
void InstanceManagerDialog::openINI()
{
if (const auto* i=singleSelection()) {
- shell::Open(i->iniFile());
+ shell::Open(i->iniPath());
}
}
@@ -629,6 +442,8 @@ void InstanceManagerDialog::deleteInstance()
return;
}
+ // creating dialog
+
const auto Recycle = QMessageBox::Save;
const auto Delete = QMessageBox::Yes;
const auto Cancel = QMessageBox::Cancel;
@@ -652,6 +467,8 @@ void InstanceManagerDialog::deleteInstance()
list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
list->setMaximumHeight(160);
+
+ // filling the list
for (const auto& f : files) {
auto* item = new QListWidgetItem(f.path);
@@ -676,6 +493,7 @@ void InstanceManagerDialog::deleteInstance()
}
+ // gathering all the selected items
QStringList selected;
for (int i=0; i<list->count(); ++i) {
@@ -691,10 +509,14 @@ void InstanceManagerDialog::deleteInstance()
return;
}
+
+ // deleting
if (!doDelete(selected, (r == Recycle))) {
return;
}
+
+ // updating ui
updateInstances();
updateList();
}
@@ -707,6 +529,15 @@ void InstanceManagerDialog::setRestartOnSelect(bool b)
bool InstanceManagerDialog::doDelete(const QStringList& files, bool recycle)
{
+ // logging
+ for (auto&& f : files) {
+ if (recycle) {
+ log::info("will recycle {}", f);
+ } else {
+ log::info("will delete {}", f);
+ }
+ }
+
if (MOBase::shellDelete(files, recycle, this)) {
return true;
}
@@ -756,7 +587,7 @@ void InstanceManagerDialog::createNew()
updateInstances();
updateList();
- select(dlg.instanceName());
+ select(dlg.creationInfo().instanceName);
}
std::size_t InstanceManagerDialog::singleSelectionIndex() const
@@ -771,17 +602,7 @@ std::size_t InstanceManagerDialog::singleSelectionIndex() const
return static_cast<std::size_t>(sel.indexes()[0].row());
}
-InstanceInfo* InstanceManagerDialog::singleSelection()
-{
- const auto i = singleSelectionIndex();
- if (i == NoSelection) {
- return nullptr;
- }
-
- return m_instances[i].get();
-}
-
-const InstanceInfo* InstanceManagerDialog::singleSelection() const
+const Instance* InstanceManagerDialog::singleSelection() const
{
const auto i = singleSelectionIndex();
if (i == NoSelection) {
@@ -791,13 +612,13 @@ const InstanceInfo* InstanceManagerDialog::singleSelection() const
return m_instances[i].get();
}
-void InstanceManagerDialog::fillData(const InstanceInfo& ii)
+void InstanceManagerDialog::fillData(const Instance& ii)
{
ui->name->setText(ii.name());
- ui->location->setText(ii.location());
+ ui->location->setText(ii.directory());
ui->baseDirectory->setText(ii.baseDirectory());
ui->gameName->setText(ii.gameName());
- ui->gameDir->setText(ii.gamePath());
+ ui->gameDir->setText(ii.gameDirectory());
setButtonsEnabled(true);
const auto& m = InstanceManager::singleton();