From e43f6e6f377353aec849a4fd7b69315c538bcf94 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 28 Dec 2020 06:22:16 -0500 Subject: disallow renaming the active profile --- src/profilesdialog.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 14bf0da5..f1468688 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -250,6 +250,12 @@ void ProfilesDialog::on_renameButton_clicked() { Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); + if (currentProfile->name() == m_ActiveProfileName) { + QMessageBox::warning(this, tr("Renaming active profile"), + tr("The active profile cannot be renamed. Please change to a different profile first.")); + return; + } + bool valid = false; QString name; @@ -265,7 +271,7 @@ void ProfilesDialog::on_renameButton_clicked() } ui->profilesList->currentItem()->setText(name); - + QString oldName = currentProfile->name(); currentProfile->rename(name); -- cgit v1.3.1 From f01fe9ce7c17bc9697286b49f03534a45d3daaa4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 28 Dec 2020 06:37:50 -0500 Subject: refresh action, added in toolbar and menu --- src/mainwindow.cpp | 6 +++++- src/mainwindow.h | 1 + src/mainwindow.ui | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0f5b15e3..31b82d62 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -487,7 +487,6 @@ MainWindow::MainWindow(Settings &settings new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated())); new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated())); - new QShortcut(QKeySequence::Refresh, this, SLOT(refreshProfile_activated())); setFilterShortcuts(ui->modList, ui->modFilterEdit); setFilterShortcuts(ui->espList, ui->espFilterEdit); @@ -2292,6 +2291,11 @@ void MainWindow::on_actionInstallMod_triggered() installMod(); } +void MainWindow::on_action_Refresh_triggered() +{ + refreshProfile_activated(); +} + void MainWindow::on_actionAdd_Profile_triggered() { for (;;) { diff --git a/src/mainwindow.h b/src/mainwindow.h index eae150d6..f8910361 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -557,6 +557,7 @@ private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); void on_actionInstallMod_triggered(); + void on_action_Refresh_triggered(); void on_actionModify_Executables_triggered(); void on_actionNexus_triggered(); void on_actionNotifications_triggered(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 692246fd..14cbdda1 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1413,6 +1413,7 @@ p, li { white-space: pre-wrap; } + @@ -1482,6 +1483,7 @@ p, li { white-space: pre-wrap; } + @@ -1896,6 +1898,18 @@ p, li { white-space: pre-wrap; } Log + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + &Refresh + + + F5 + + -- cgit v1.3.1 From 91e1cc9ccaeed1ddb3b4c06ede213018000bf86d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 28 Dec 2020 06:59:39 -0500 Subject: update mod counts for creating/restoring backups --- src/mainwindow.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 31b82d62..08c8ef7c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2553,7 +2553,9 @@ void MainWindow::restoreBackup_clicked() if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); } + m_OrganizerCore.refresh(); + updateModCount(); } } } @@ -2703,7 +2705,9 @@ void MainWindow::backupMod_clicked() QMessageBox::information(this, tr("Failed"), tr("Failed to create backup.")); } + m_OrganizerCore.refresh(); + updateModCount(); } @@ -3321,6 +3325,8 @@ void MainWindow::refreshProfile_activated() void MainWindow::updateModCount() { + TimeThis tt("updateModCount"); + int activeCount = 0; int visActiveCount = 0; int backupCount = 0; -- cgit v1.3.1 From cc997c370465629c11b72ebef3ffde1bbd1fa46e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Dec 2020 02:10:33 -0500 Subject: editable combobox for output mod name --- src/editexecutablesdialog.cpp | 66 +++++++++++++++++++++++++++++++++++++++++-- src/editexecutablesdialog.h | 3 +- src/editexecutablesdialog.ui | 3 ++ src/pch.h | 1 + 4 files changed, 69 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index cd11d5fb..6e3cecff 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -71,6 +71,9 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget loadCustomOverwrites(); loadForcedLibraries(); + + QStringList modNames; + for (auto&& m : m_organizerCore.modList()->allMods()) { auto mod = ModInfo::getByName(m); if (!mod->hasAnyOfTheseFlags({ ModInfo::FLAG_FOREIGN, @@ -78,9 +81,16 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget ModInfo::FLAG_OVERWRITE, ModInfo::FLAG_SEPARATOR })) { ui->mods->addItem(m); + modNames.push_back(m); } } + auto* c = new QCompleter(modNames); + c->setCaseSensitivity(Qt::CaseInsensitive); + c->setCompletionMode(QCompleter::UnfilteredPopupCompletion); + ui->mods->setCompleter(c); + + fillList(); setDirty(false); @@ -170,9 +180,51 @@ EditExecutablesDialog::getForcedLibraries() const return m_forcedLibraries; } -void EditExecutablesDialog::commitChanges() +bool EditExecutablesDialog::checkOutputMods(const ExecutablesList& exes) +{ + // make sure the output mods for exes exist since the combobox is editable + // + // it'd be convenient for users to automatically create mods here if they're + // not found, but this is a can of worms: it would require a refresh + // because getIndex() still won't find it after calling + // OrganizerCore::createMod(), which is a problem if the user just clicked + // Apply and continued doing things + // + // triggering a refresh while this dialog is up doesn't sound like a very + // smart thing to do for now, so this just shows an error + + for (const auto& e : exes) { + auto modName = m_customOverwrites.find(e.title()); + + if (modName && modName->enabled) { + if (modName->value.isEmpty()) { + QMessageBox::critical( + this, tr("Empty output mod"), + tr("The output mod for %2 is empty.").arg(e.title())); + + return false; + } else if (ModInfo::getIndex(modName->value) == UINT_MAX) { + QMessageBox::critical( + this, tr("Output mod not found"), + tr("The output mod '%1' for %2 does not exist.") + .arg(modName->value).arg(e.title())); + + return false; + } + } + } + + return true; +} + +bool EditExecutablesDialog::commitChanges() { const auto newExecutables = getExecutablesList(); + + if (!checkOutputMods(newExecutables)) { + return false; + } + auto* profile = m_organizerCore.currentProfile(); // remove all the custom overwrites and forced libraries @@ -201,6 +253,8 @@ void EditExecutablesDialog::commitChanges() m_organizerCore.setExecutablesList(newExecutables); setDirty(false); + + return true; } void EditExecutablesDialog::setDirty(bool b) @@ -651,6 +705,11 @@ void EditExecutablesDialog::on_createFilesInMod_toggled(bool checked) ui->mods->setEnabled(checked); save(); + + if (checked) { + ui->mods->lineEdit()->selectAll(); + ui->mods->setFocus(); + } } void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) @@ -776,8 +835,9 @@ void EditExecutablesDialog::on_configureLibraries_clicked() void EditExecutablesDialog::on_buttons_clicked(QAbstractButton* b) { if (b == ui->buttons->button(QDialogButtonBox::Ok)) { - commitChanges(); - accept(); + if (commitChanges()) { + accept(); + } } else if (b == ui->buttons->button(QDialogButtonBox::Apply)) { commitChanges(); } else { diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index c8be0bf6..8da53e09 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -225,9 +225,10 @@ private: bool canMove(const QListWidgetItem* item, int direction); void move(QListWidgetItem* item, int direction); bool isTitleConflicting(const QString& s); - void commitChanges(); + bool commitChanges(); void setDirty(bool b); void selectIndex(int i); + bool checkOutputMods(const ExecutablesList& exes); void addFromFile(); void addEmpty(); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 6f50b3b3..d90fd8d4 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -387,6 +387,9 @@ Right now the only case I know of where this needs to be overwritten is for the false + + true + diff --git a/src/pch.h b/src/pch.h index 2a2246ba..bfc7f2d2 100644 --- a/src/pch.h +++ b/src/pch.h @@ -89,6 +89,7 @@ #include #include #include +#include #include #include #include -- cgit v1.3.1 From 33a177f1eda90b4d1fff92976635beaa59f85172 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Dec 2020 02:42:07 -0500 Subject: profiles dialog: - added select button - select new profile in the list after creating --- src/mainwindow.cpp | 17 ++- src/profilesdialog.cpp | 27 +++- src/profilesdialog.h | 10 +- src/profilesdialog.ui | 355 ++++++++++++++++++++++++++++--------------------- 4 files changed, 254 insertions(+), 155 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 08c8ef7c..cfce9bef 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1713,9 +1713,22 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) if (ui->profileBox->currentIndex() == 0) { ui->profileBox->setCurrentIndex(previousIndex); - ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore, this).exec(); + + std::optional newSelection; + + ProfilesDialog dlg(ui->profileBox->currentText(), m_OrganizerCore, this); + dlg.exec(); + newSelection = dlg.selectedProfile(); + while (!refreshProfiles()) { - ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore, this).exec(); + ProfilesDialog dlg(ui->profileBox->currentText(), m_OrganizerCore, this); + dlg.exec(); + newSelection = dlg.selectedProfile(); + } + + if (newSelection) { + ui->profileBox->setCurrentText(*newSelection); + activateSelectedProfile(); } } else { activateSelectedProfile(); diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index f1468688..d71a4531 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -115,11 +115,29 @@ void ProfilesDialog::showEvent(QShowEvent *event) } } -void ProfilesDialog::on_closeButton_clicked() +void ProfilesDialog::on_close_clicked() { close(); } +void ProfilesDialog::on_select_clicked() +{ + const Profile::Ptr currentProfile = ui->profilesList->currentItem() + ->data(Qt::UserRole) + .value(); + + if (!currentProfile) { + return; + } + + m_Selected = currentProfile->name(); + close(); +} + +std::optional ProfilesDialog::selectedProfile() const +{ + return m_Selected; +} QListWidgetItem *ProfilesDialog::addItem(const QString &name) { @@ -142,6 +160,7 @@ void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) newItem->setData(Qt::UserRole, QVariant::fromValue(profile)); ui->profilesList->addItem(newItem); m_FailState = false; + ui->profilesList->setCurrentItem(newItem); emit profileCreated(profile.get()); } catch (const std::exception&) { m_FailState = true; @@ -157,6 +176,7 @@ void ProfilesDialog::createProfile(const QString &name, const Profile &reference newItem->setData(Qt::UserRole, QVariant::fromValue(profile)); ui->profilesList->addItem(newItem); m_FailState = false; + ui->profilesList->setCurrentItem(newItem); emit profileCreated(profile.get()); } catch (const std::exception&) { m_FailState = true; @@ -348,6 +368,11 @@ void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current } } +void ProfilesDialog::on_profilesList_itemActivated(QListWidgetItem* item) +{ + on_select_clicked(); +} + void ProfilesDialog::on_localSavesBox_stateChanged(int state) { Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); diff --git a/src/profilesdialog.h b/src/profilesdialog.h index 08298d0e..005bc33e 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -63,6 +63,11 @@ public: **/ bool failed() const { return m_FailState; } + // if the dialog was closed with the 'select' button, returns the name of the + // selected profile; if the dialog was closed with 'cancel', returns empty + // + std::optional selectedProfile() const; + signals: /** @@ -95,7 +100,8 @@ private: private slots: - void on_closeButton_clicked(); + void on_close_clicked(); + void on_select_clicked(); void on_addProfileButton_clicked(); @@ -104,6 +110,7 @@ private slots: void on_copyProfileButton_clicked(); void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_profilesList_itemActivated(QListWidgetItem* item); void on_removeProfileButton_clicked(); @@ -119,6 +126,7 @@ private: bool m_FailState; MOBase::IPluginGame const *m_Game; QString m_ActiveProfileName; + std::optional m_Selected; }; #endif // PROFILESDIALOG_H diff --git a/src/profilesdialog.ui b/src/profilesdialog.ui index 644b0720..44ab0ee9 100644 --- a/src/profilesdialog.ui +++ b/src/profilesdialog.ui @@ -13,59 +13,74 @@ Profiles - + - - - - - List of Profiles - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + List of Profiles + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8.25pt;">This is the list of profiles. Each profile contains its own list and installation order of enabled mods (from a shared pool), its own list and load order of enabled plugins (esps/esms), a copy of the games ini-file and an optional savegame filter.</span></p></body></html> - - - - - - - <html><head/><body><p>If checked, save games are stored locally to this profile and will not appear when starting with a different profile.</p></body></html> - - - If checked, save games are local to this profile and will not appear when starting with a different profile. - - - Use profile-specific Save Games - - - - - - - <html><head/><body><p>If checked MO2 will use his own profile-specific game INI files, so that the &quot;Global&quot; ones in MyGames can be left vanilla. This different set of INI files is then offered to the game instead of the default one.</p></body></html> - - - <html><head/><body><p>If checked, MO2 will use a local set of game INI files (configuration and settings files), different from the default ones found in MyGames. This way changes to the INI settings will only affect this profile and the Global INI files can remain vanilla. MO2 will then show the profile INI files to the game instead of the Global ones.</p></body></html> - - - Use profile-specific Game INI Files - - - false - - - - - - - This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation. - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + + + + <html><head/><body><p>If checked, save games are stored locally to this profile and will not appear when starting with a different profile.</p></body></html> + + + If checked, save games are local to this profile and will not appear when starting with a different profile. + + + Use profile-specific Save Games + + + + + + + <html><head/><body><p>If checked MO2 will use his own profile-specific game INI files, so that the &quot;Global&quot; ones in MyGames can be left vanilla. This different set of INI files is then offered to the game instead of the default one.</p></body></html> + + + <html><head/><body><p>If checked, MO2 will use a local set of game INI files (configuration and settings files), different from the default ones found in MyGames. This way changes to the INI settings will only affect this profile and the Global INI files can remain vanilla. MO2 will then show the profile INI files to the game instead of the Global ones.</p></body></html> + + + Use profile-specific Game INI Files + + + false + + + + + + + This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation. + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> @@ -73,111 +88,149 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - - - Automatic Archive Invalidation - - - - + + + Automatic Archive Invalidation + + + + + + + + + + + Create a new profile from scratch + + + Create a new profile from scratch + + + Create + + + + + + + false + + + Clone the selected profile + + + This creates a new profile with the same settings and active mods as the selected one. + + + Copy + + + + + + + false + + + Delete the selected Profile. This can not be un-done! + + + Delete the selected Profile. This can not be un-done! + + + Remove + + + + + + + false + + + Rename + + + + + + + false + + + Transfer save games to the selected profile. + + + Transfer save games to the selected profile. + + + Transfer Saves + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + - - - - - Create a new profile from scratch - - - Create a new profile from scratch - - - Create - - - - - - - false - - - Clone the selected profile - - - This creates a new profile with the same settings and active mods as the selected one. - - - Copy - - - - - - - false - - - Delete the selected Profile. This can not be un-done! - - - Delete the selected Profile. This can not be un-done! - - - Remove - - - - - - - false - - - Rename - - - - - - - false - - - Transfer save games to the selected profile. - - - Transfer save games to the selected profile. - - - Transfer Saves - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - Close - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Select + + + + + + + Close + + + + + -- cgit v1.3.1