summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt11
-rw-r--r--src/editexecutablesdialog.cpp799
-rw-r--r--src/editexecutablesdialog.h199
-rw-r--r--src/editexecutablesdialog.ui755
-rw-r--r--src/executableslist.cpp449
-rw-r--r--src/executableslist.h204
-rw-r--r--src/filedialogmemory.cpp44
-rw-r--r--src/filedialogmemory.h23
-rw-r--r--src/mainwindow.cpp152
-rw-r--r--src/mainwindow.h2
-rw-r--r--src/organizercore.cpp157
-rw-r--r--src/organizercore.h2
-rw-r--r--src/pch.h1
-rw-r--r--src/profile.cpp4
-rw-r--r--src/profile.h4
-rw-r--r--src/resources/go-down.pngbin874 -> 937 bytes
-rw-r--r--src/resources/go-up.pngbin877 -> 974 bytes
17 files changed, 1799 insertions, 1007 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5623a851..645a5a73 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -293,7 +293,6 @@ set(core
categories
shared/directoryentry
directoryrefresher
- executableslist
installationmanager
instancemanager
loadmechanism
@@ -309,7 +308,6 @@ set(dialogs
activatemodsdialog
categoriesdialog
credentialsdialog
- editexecutablesdialog
filedialogmemory
forcedloaddialog
forcedloaddialogwidget
@@ -333,6 +331,11 @@ set(downloads
downloadmanager
)
+set(executables
+ executableslist
+ editexecutablesdialog
+)
+
set(locking
ilockedwaitingforprocess
lockeddialog
@@ -412,8 +415,8 @@ set(widgets
)
set(src_filters
- application core browser dialogs downloads locking modinfo modlist plugins
- previews profiles settings utilities widgets
+ application core browser dialogs downloads executables locking modinfo
+ modlist plugins previews profiles settings utilities widgets
)
foreach(filter in list ${src_filters})
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp
index 177661ff..8929d207 100644
--- a/src/editexecutablesdialog.cpp
+++ b/src/editexecutablesdialog.cpp
@@ -22,394 +22,677 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "filedialogmemory.h"
#include "stackdata.h"
#include "modlist.h"
+#include "forcedloaddialog.h"
+#include "organizercore.h"
+
#include <QMessageBox>
#include <Shellapi.h>
#include <utility.h>
-#include "forcedloaddialog.h"
#include <algorithm>
using namespace MOBase;
using namespace MOShared;
-EditExecutablesDialog::EditExecutablesDialog(
- const ExecutablesList &executablesList, const ModList &modList,
- Profile *profile, const IPluginGame *game, QWidget *parent)
+EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent)
: TutorableDialog("EditExecutables", parent)
, ui(new Ui::EditExecutablesDialog)
- , m_CurrentItem(nullptr)
- , m_ExecutablesList(executablesList)
- , m_Profile(profile)
- , m_GamePlugin(game)
+ , m_organizerCore(oc)
+ , m_originalExecutables(*oc.executablesList())
+ , m_executablesList(*oc.executablesList())
+ , m_settingUI(false)
{
ui->setupUi(this);
+ ui->splitter->setSizes({200, 1});
+ ui->splitter->setStretchFactor(0, 0);
+ ui->splitter->setStretchFactor(1, 1);
- refreshExecutablesWidget();
+ loadCustomOverwrites();
+ loadForcedLibraries();
- ui->newFilesModBox->addItems(modList.allMods());
+ ui->mods->addItems(m_organizerCore.modList()->allMods());
+ fillList();
+ setDirty(false);
- m_ForcedLibraries = m_Profile->determineForcedLibraries(ui->titleEdit->text());
+ // some widgets need to do more than just save() and have their own handler
+ connect(ui->binary, &QLineEdit::textChanged, [&]{ save(); });
+ connect(ui->workingDirectory, &QLineEdit::textChanged, [&]{ save(); });
+ connect(ui->arguments, &QLineEdit::textChanged, [&]{ save(); });
+ connect(ui->steamAppID, &QLineEdit::textChanged, [&]{ save(); });
+ connect(ui->mods, &QComboBox::currentTextChanged, [&]{ save(); });
+ connect(ui->useApplicationIcon, &QCheckBox::toggled, [&]{ save(); });
+ connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&]{ saveOrder(); });
}
-EditExecutablesDialog::~EditExecutablesDialog()
+EditExecutablesDialog::~EditExecutablesDialog() = default;
+
+
+void EditExecutablesDialog::loadCustomOverwrites()
{
- delete ui;
+ const auto* p = m_organizerCore.currentProfile();
+
+ for (const auto& e : m_executablesList) {
+ const auto s = p->setting("custom_overwrites", e.title()).toString();
+
+ if (!s.isEmpty()) {
+ m_customOverwrites.set(e.title(), true, s);
+ }
+ }
}
-ExecutablesList EditExecutablesDialog::getExecutablesList() const
+void EditExecutablesDialog::loadForcedLibraries()
{
- ExecutablesList newList;
- for (int i = 0; i < ui->executablesListBox->count(); ++i) {
- newList.addExecutable(m_ExecutablesList.find(ui->executablesListBox->item(i)->text()));
+ const auto* p = m_organizerCore.currentProfile();
+
+ for (const auto& e : m_executablesList) {
+ if (p->forcedLibrariesEnabled(e.title())) {
+ m_forcedLibraries.set(e.title(), true, p->determineForcedLibraries(e.title()));
+ }
}
- return newList;
}
-void EditExecutablesDialog::refreshExecutablesWidget()
+ExecutablesList EditExecutablesDialog::getExecutablesList() const
{
- ui->executablesListBox->clear();
- std::vector<Executable>::const_iterator current, end;
- m_ExecutablesList.getExecutables(current, end);
+ ExecutablesList newList;
+
+ // make sure the executables are in the same order as in the list
+ for (int i = 0; i < ui->list->count(); ++i) {
+ const auto& title = ui->list->item(i)->text();
+
+ auto itor = m_executablesList.find(title);
- for(; current != end; ++current) {
- QListWidgetItem *newItem = new QListWidgetItem(current->m_Title);
- newItem->setTextColor(current->isCustom() ? QColor(Qt::black) : QColor(Qt::darkGray));
- ui->executablesListBox->addItem(newItem);
+ if (itor == m_executablesList.end()) {
+ qWarning().nospace()
+ << "getExecutablesList(): executable '" << title << "' not found";
+
+ continue;
+ }
+
+ newList.setExecutable(*itor);
}
- ui->addButton->setEnabled(false);
- ui->removeButton->setEnabled(false);
+ return newList;
}
-
-void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &name)
+const EditExecutablesDialog::CustomOverwrites&
+EditExecutablesDialog::getCustomOverwrites() const
{
- updateButtonStates();
+ return m_customOverwrites;
}
-void EditExecutablesDialog::on_workingDirEdit_textChanged(const QString &dir)
+const EditExecutablesDialog::ForcedLibraries&
+EditExecutablesDialog::getForcedLibraries() const
{
- updateButtonStates();
+ return m_forcedLibraries;
}
-void EditExecutablesDialog::updateButtonStates()
+void EditExecutablesDialog::commitChanges()
{
- bool enabled = true;
+ const auto newExecutables = getExecutablesList();
+ auto* profile = m_organizerCore.currentProfile();
- QString filePath(ui->binaryEdit->text());
- QFileInfo fileInfo(filePath);
- if (!fileInfo.exists())
- enabled = false;
- if (!fileInfo.isFile())
- enabled = false;
+ // remove all the custom overwrites and forced libraries
+ for (const auto& e : m_originalExecutables) {
+ profile->removeSetting("custom_overwrites", e.title());
+ profile->removeForcedLibraries(e.title());
+ }
+
+ // set the new custom overwrites and forced libraries
+ for (const auto& e : newExecutables) {
+ if (auto modName=m_customOverwrites.find(e.title())) {
+ if (modName && modName->enabled) {
+ profile->storeSetting("custom_overwrites", e.title(), modName->value);
+ }
+ }
- QString dirPath(ui->workingDirEdit->text());
- if (!dirPath.isEmpty()) {
- QDir dirInfo(dirPath);
- if (!dirInfo.exists())
- enabled = false;
+ if (auto libraryList=m_forcedLibraries.find(e.title())) {
+ if (libraryList && libraryList->enabled && !libraryList->value.empty()) {
+ profile->setForcedLibrariesEnabled(e.title(), true);
+ profile->storeForcedLibraries(e.title(), libraryList->value);
+ }
+ }
}
- ui->addButton->setEnabled(enabled);
+ // set the new executables list
+ m_organizerCore.setExecutablesList(newExecutables);
+
+ setDirty(false);
}
-void EditExecutablesDialog::resetInput()
+void EditExecutablesDialog::setDirty(bool b)
{
- ui->binaryEdit->setText("");
- ui->titleEdit->setText("");
- ui->workingDirEdit->clear();
- ui->argumentsEdit->setText("");
- ui->appIDOverwriteEdit->clear();
- ui->overwriteAppIDBox->setChecked(false);
- ui->useAppIconCheckBox->setChecked(false);
- ui->newFilesModCheckBox->setChecked(false);
- ui->forceLoadCheckBox->setChecked(false);
- m_CurrentItem = nullptr;
+ if (auto* button=ui->buttons->button(QDialogButtonBox::Apply)) {
+ button->setEnabled(b);
+ }
}
-
-void EditExecutablesDialog::saveExecutable()
+QListWidgetItem* EditExecutablesDialog::selectedItem()
{
- m_ExecutablesList.updateExecutable(
- ui->titleEdit->text(),
- QDir::fromNativeSeparators(ui->binaryEdit->text()),
- ui->argumentsEdit->text(),
- QDir::fromNativeSeparators(ui->workingDirEdit->text()),
- ui->overwriteAppIDBox->isChecked() ?
- ui->appIDOverwriteEdit->text() : "",
- Executable::UseApplicationIcon | Executable::CustomExecutable,
- (ui->useAppIconCheckBox->isChecked() ?
- Executable::UseApplicationIcon : Executable::Flags())
- | Executable::CustomExecutable);
+ const auto selection = ui->list->selectedItems();
- if (ui->newFilesModCheckBox->isChecked()) {
- m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(),
- ui->newFilesModBox->currentText());
- }
- else {
- m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text());
+ if (selection.empty()) {
+ return nullptr;
}
- m_Profile->removeForcedLibraries(ui->titleEdit->text());
- m_Profile->storeForcedLibraries(ui->titleEdit->text(), m_ForcedLibraries);
- m_Profile->setForcedLibrariesEnabled(ui->titleEdit->text(), ui->forceLoadCheckBox->isChecked());
+ return selection[0];
}
-
-void EditExecutablesDialog::delayedRefresh()
+Executable* EditExecutablesDialog::selectedExe()
{
- QModelIndex index = ui->executablesListBox->currentIndex();
- resetInput();
- refreshExecutablesWidget();
- on_executablesListBox_clicked(index);
-}
+ auto* item = selectedItem();
+ if (!item) {
+ return nullptr;
+ }
+
+ const auto& title = item->text();
+ auto itor = m_executablesList.find(title);
+ if (itor == m_executablesList.end()) {
+ return nullptr;
+ }
+
+ return &*itor;
+}
-void EditExecutablesDialog::on_forceLoadButton_clicked()
+void EditExecutablesDialog::fillList()
{
- ForcedLoadDialog dialog(m_GamePlugin, this);
- dialog.setValues(m_ForcedLibraries);
- if (dialog.exec() == QDialog::Accepted) {
- m_ForcedLibraries = dialog.values();
+ ui->list->clear();
+
+ for(const auto& exe : m_executablesList) {
+ ui->list->addItem(createListItem(exe));
+ }
+
+ // select the first one in the list, if any
+ if (ui->list->count() > 0) {
+ ui->list->item(0)->setSelected(true);
+ } else {
+ updateUI(nullptr, nullptr);
}
}
-void EditExecutablesDialog::on_forceLoadCheckBox_toggled()
+QListWidgetItem* EditExecutablesDialog::createListItem(const Executable& exe)
{
- ui->forceLoadButton->setEnabled(ui->forceLoadCheckBox->isChecked());
+ return new QListWidgetItem(exe.title());
}
+void EditExecutablesDialog::updateUI(
+ const QListWidgetItem* item, const Executable* e)
+{
+ // the ui is currently being set, ignore changes
+ m_settingUI = true;
+
+ if (e) {
+ setEdits(*e);
+ } else {
+ clearEdits();
+ }
+
+ setButtons(item, e);
-void EditExecutablesDialog::on_addButton_clicked()
+ // any changes from now on are from the user
+ m_settingUI = false;
+}
+
+void EditExecutablesDialog::setButtons(
+ const QListWidgetItem* item, const Executable* e)
{
- if (executableChanged()) {
- saveExecutable();
+ // add and remove are always enabled
+
+ if (item) {
+ ui->up->setEnabled(canMove(item, -1));
+ ui->down->setEnabled(canMove(item, +1));
+ } else {
+ ui->up->setEnabled(false);
+ ui->down->setEnabled(false);
}
+}
- resetInput();
- refreshExecutablesWidget();
+void EditExecutablesDialog::clearEdits()
+{
+ ui->title->clear();
+ ui->title->setEnabled(false);
+ ui->binary->clear();
+ ui->binary->setEnabled(false);
+ ui->browseBinary->setEnabled(false);
+ ui->workingDirectory->clear();
+ ui->workingDirectory->setEnabled(false);
+ ui->browseWorkingDirectory->setEnabled(false);
+ ui->arguments->clear();
+ ui->arguments->setEnabled(false);
+ ui->overwriteSteamAppID->setEnabled(false);
+ ui->overwriteSteamAppID->setChecked(false);
+ ui->steamAppID->setEnabled(false);
+ ui->steamAppID->clear();
+ ui->createFilesInMod->setEnabled(false);
+ ui->createFilesInMod->setChecked(false);
+ ui->mods->setEnabled(false);
+ ui->mods->setCurrentIndex(-1);
+ ui->forceLoadLibraries->setEnabled(false);
+ ui->forceLoadLibraries->setChecked(false);
+ ui->configureLibraries->setEnabled(false);
+ ui->useApplicationIcon->setEnabled(false);
+ ui->useApplicationIcon->setChecked(false);
}
-void EditExecutablesDialog::on_browseButton_clicked()
+void EditExecutablesDialog::setEdits(const Executable& e)
{
- QString binaryName = FileDialogMemory::getOpenFileName(
- "editExecutableBinary", this, tr("Select a binary"), QString(),
- tr("Executable (%1)").arg("*.exe *.bat *.jar"));
+ ui->title->setText(e.title());
+ ui->binary->setText(QDir::toNativeSeparators(e.binaryInfo().absoluteFilePath()));
+ ui->workingDirectory->setText(QDir::toNativeSeparators(e.workingDirectory()));
+ ui->arguments->setText(e.arguments());
+ ui->overwriteSteamAppID->setChecked(!e.steamAppID().isEmpty());
+ ui->steamAppID->setEnabled(!e.steamAppID().isEmpty());
+ ui->steamAppID->setText(e.steamAppID());
+ ui->useApplicationIcon->setChecked(e.usesOwnIcon());
- if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) {
- QString binaryPath;
- { // try to find java automatically
- std::wstring binaryNameW = ToWString(binaryName);
- WCHAR buffer[MAX_PATH];
- if (::FindExecutableW(binaryNameW.c_str(), nullptr, buffer)
- > reinterpret_cast<HINSTANCE>(32)) {
- DWORD binaryType = 0UL;
- if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) {
- qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError());
- } else if (binaryType == SCS_32BIT_BINARY) {
- binaryPath = ToQString(buffer);
- }
- }
- }
- if (binaryPath.isEmpty()) {
- QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat);
- if (javaReg.contains("CurrentVersion")) {
- QString currentVersion = javaReg.value("CurrentVersion").toString();
- binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe");
+ {
+ int modIndex = -1;
+
+ const auto modName = m_customOverwrites.find(e.title());
+
+ if (modName && !modName->value.isEmpty()) {
+ modIndex = ui->mods->findText(modName->value);
+
+ if (modIndex == -1) {
+ qWarning().nospace()
+ << "executable '" << e.title() << "' uses mod '" << modName->value << "' "
+ << "as a custom overwrite, but that mod doesn't exist";
}
}
- if (binaryPath.isEmpty()) {
- QMessageBox::information(this, tr("Java (32-bit) required"),
- tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe "
- "from that installation as the binary."));
- } else {
- ui->binaryEdit->setText(binaryPath);
- }
- ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath()));
- ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\"");
+ const bool hasCustomOverwrites = (modName && modName->enabled);
+
+ ui->createFilesInMod->setChecked(hasCustomOverwrites);
+ ui->mods->setEnabled(hasCustomOverwrites);
+ ui->mods->setCurrentIndex(modIndex);
+ }
+
+ {
+ const auto libraryList = m_forcedLibraries.find(e.title());
+ const bool hasForcedLibraries = (libraryList && libraryList->enabled);
+
+ ui->forceLoadLibraries->setChecked(hasForcedLibraries);
+ ui->configureLibraries->setEnabled(hasForcedLibraries);
+ }
+
+ // always enabled
+ ui->title->setEnabled(true);
+ ui->binary->setEnabled(true);
+ ui->browseBinary->setEnabled(true);
+ ui->workingDirectory->setEnabled(true);
+ ui->browseWorkingDirectory->setEnabled(true);
+ ui->arguments->setEnabled(true);
+ ui->overwriteSteamAppID->setEnabled(true);
+ ui->useApplicationIcon->setEnabled(true);
+ ui->createFilesInMod->setEnabled(true);
+ ui->forceLoadLibraries->setEnabled(true);
+}
+
+void EditExecutablesDialog::save()
+{
+ if (m_settingUI) {
+ return;
+ }
+
+ auto* e = selectedExe();
+ if (!e) {
+ qWarning("trying to save but nothing is selected");
+ return;
+ }
+
+ // title may have changed, start with the stuff using it
+
+ // custom overwrites
+ if (ui->createFilesInMod->isChecked()) {
+ m_customOverwrites.set(e->title(), true, ui->mods->currentText());
+ } else {
+ m_customOverwrites.setEnabled(e->title(), false);
+ }
+
+ // forced libraries
+ m_forcedLibraries.setEnabled(e->title(), ui->forceLoadLibraries->isChecked());
+
+ // get the new title, but ignore it if it's conflicting with an already
+ // existing executable
+ QString newTitle = ui->title->text();
+ if (isTitleConflicting(newTitle)) {
+ newTitle = e->title();
+ }
+
+ if (e->title() != newTitle) {
+ // now rename both the custom overwrites and forced libraries if the title
+ // is being changed
+ m_customOverwrites.rename(e->title(), newTitle);
+ m_forcedLibraries.rename(e->title(), newTitle);
+
+ // save the new title
+ e->title(newTitle);
+ }
+
+ e->binaryInfo(ui->binary->text());
+ e->workingDirectory(ui->workingDirectory->text());
+ e->arguments(ui->arguments->text());
+
+ if (ui->overwriteSteamAppID->isChecked()) {
+ e->steamAppID(ui->steamAppID->text());
} else {
- ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName));
+ e->steamAppID("");
}
- if (ui->titleEdit->text().isEmpty()) {
- ui->titleEdit->setText(QFileInfo(binaryName).baseName());
+ if (ui->useApplicationIcon->isChecked()) {
+ e->flags(e->flags() | Executable::UseApplicationIcon);
+ } else {
+ e->flags(e->flags() & (~Executable::UseApplicationIcon));
}
+
+ setDirty(true);
}
-void EditExecutablesDialog::on_browseDirButton_clicked()
+void EditExecutablesDialog::saveOrder()
{
- QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this,
- tr("Select a directory"));
+ m_executablesList = getExecutablesList();
+ setDirty(true);
+}
+
+bool EditExecutablesDialog::canMove(const QListWidgetItem* item, int direction)
+{
+ if (!item) {
+ return false;
+ }
+
+ if (direction < 0) {
+ // moving up
+ return (ui->list->row(item) > 0);
- ui->workingDirEdit->setText(dirName);
+ } else if (direction > 0) {
+ // moving down
+ return (ui->list->row(item) < (ui->list->count() - 1));
+ }
+
+ return false;
}
-void EditExecutablesDialog::on_removeButton_clicked()
+void EditExecutablesDialog::move(QListWidgetItem* item, int direction)
{
- if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()),
- QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text());
- m_Profile->removeForcedLibraries(ui->titleEdit->text());
- m_ExecutablesList.remove(ui->titleEdit->text());
+ if (!canMove(item, direction)) {
+ return;
}
- resetInput();
- refreshExecutablesWidget();
+ const auto row = ui->list->row(item);
+
+ // removing item
+ ui->list->takeItem(row);
+ ui->list->insertItem(row + (direction > 0 ? 1 : -1), item);
+ item->setSelected(true);
+
+ setDirty(true);
}
-void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1)
+void EditExecutablesDialog::on_list_itemSelectionChanged()
{
- QPushButton *addButton = findChild<QPushButton*>("addButton");
- QPushButton *removeButton = findChild<QPushButton*>("removeButton");
+ updateUI(selectedItem(), selectedExe());
+}
- QListWidget *executablesWidget = findChild<QListWidget*>("executablesListBox");
+void EditExecutablesDialog::on_reset_clicked()
+{
+ const auto title = tr("Reset plugin executables");
- QList<QListWidgetItem*> existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString);
+ const auto text = tr(
+ "This will restore all the executables provided by the game plugin. If "
+ "there are existing executables with the same names, they will be "
+ "automatically renamed and left unchanged.");
- addButton->setEnabled(arg1.length() != 0);
+ const auto buttons = QMessageBox::Ok | QMessageBox::Cancel;
- if (existingItems.count() == 0) {
- addButton->setText(tr("Add"));
- removeButton->setEnabled(false);
- } else {
- // existing item. is it a custom one?
- addButton->setText(tr("Modify"));
- removeButton->setEnabled(true);
+ if (QMessageBox::question(this, title, text, buttons) != QMessageBox::Ok) {
+ return;
}
+
+ m_executablesList.resetFromPlugin(m_organizerCore.managedGame());
+ fillList();
+
+ setDirty(true);
}
+void EditExecutablesDialog::on_add_clicked()
+{
+ auto title = m_executablesList.makeNonConflictingTitle(tr("New Executable"));
+ if (!title) {
+ return;
+ }
+
+ const Executable e(*title);
-bool EditExecutablesDialog::executableChanged()
+ m_executablesList.setExecutable(e);
+
+ auto* item = createListItem(e);
+ ui->list->addItem(item);
+ item->setSelected(true);
+
+ setDirty(true);
+}
+
+void EditExecutablesDialog::on_remove_clicked()
{
- if (m_CurrentItem != nullptr) {
- Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text()));
+ auto* item = selectedItem();
+ if (!item) {
+ qWarning("trying to remove entry but nothing is selected");
+ return;
+ }
+
+ auto* exe = selectedExe();
+ if (!exe) {
+ qWarning("trying to remove entry but nothing is selected");
+ return;
+ }
+
+ const int currentRow = ui->list->row(item);
+ delete item;
- QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString();
- bool forcedLibrariesDirty = false;
- auto forcedLibaries = m_Profile->determineForcedLibraries(selectedExecutable.m_Title);
- forcedLibrariesDirty |= !std::equal(forcedLibaries.begin(), forcedLibaries.end(),
- m_ForcedLibraries.begin(), m_ForcedLibraries.end(),
- [](const ExecutableForcedLoadSetting &lhs, const ExecutableForcedLoadSetting &rhs)
- {
- return lhs.enabled() == rhs.enabled() &&
- lhs.forced() == rhs.forced() &&
- lhs.library() == rhs.library() &&
- lhs.process() == rhs.process();
- });
- forcedLibrariesDirty |= m_Profile->setting("forced_libraries", ui->titleEdit->text() + "/enabled", false).toBool() !=
- ui->forceLoadCheckBox->isChecked();
+ m_customOverwrites.remove(exe->title());
+ m_forcedLibraries.remove(exe->title());
- return selectedExecutable.m_Title != ui->titleEdit->text()
- || selectedExecutable.m_Arguments != ui->argumentsEdit->text()
- || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text()
- || !storedCustomOverwrite.isEmpty() != ui->newFilesModCheckBox->isChecked()
- || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->newFilesModBox->currentText())
- || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text())
- || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text())
- || selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked()
- || forcedLibrariesDirty
- ;
+ // removing from main list, must be done last because it invalidates the
+ // exe pointer
+ m_executablesList.remove(exe->title());
+
+
+ // reselecting the same row as before, or the last one
+ if (currentRow >= ui->list->count()) {
+ // that was the last item, select the new list item, if any
+ if (ui->list->count() > 0) {
+ ui->list->item(ui->list->count() - 1)->setSelected(true);
+ }
} else {
- QFileInfo fileInfo(ui->binaryEdit->text());
- return !ui->binaryEdit->text().isEmpty()
- && !ui->titleEdit->text().isEmpty()
- && fileInfo.exists()
- && fileInfo.isFile();
+ ui->list->item(currentRow)->setSelected(true);
}
+
+ setDirty(true);
}
-void EditExecutablesDialog::on_executablesListBox_itemSelectionChanged()
+
+void EditExecutablesDialog::on_up_clicked()
{
- if (ui->executablesListBox->selectedItems().size() == 0) {
- // deselected
- resetInput();
+ auto* item = selectedItem();
+ if (!item) {
+ return;
}
+
+ move(item, -1);
}
-void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked)
+void EditExecutablesDialog::on_down_clicked()
{
- ui->appIDOverwriteEdit->setEnabled(checked);
+ auto* item = selectedItem();
+ if (!item) {
+ return;
+ }
+
+ move(item, +1);
}
-void EditExecutablesDialog::on_closeButton_clicked()
+bool EditExecutablesDialog::isTitleConflicting(const QString& s)
{
- if (executableChanged()) {
- QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"),
- tr("You made changes to the current executable, do you want to save them?"),
- QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
- if (res == QMessageBox::Cancel) {
- return;
- } else if (res == QMessageBox::Yes) {
- saveExecutable();
- // the executable list returned to callers is generated from the user data in the widgets,
- // NOT the list we just saved
- refreshExecutablesWidget();
+ for (const auto& exe : m_executablesList) {
+ if (exe.title() == s) {
+ if (&exe != selectedExe()) {
+ // found an executable that's not the current one with the same title
+ return true;
+ }
}
}
- this->accept();
+
+ return false;
}
-void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &current)
+void EditExecutablesDialog::on_title_textChanged(const QString& s)
{
- if (current.isValid()) {
+ if (m_settingUI) {
+ return;
+ }
- if (executableChanged()) {
- QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"),
- tr("You made changes to the current executable, do you want to save them?"),
- QMessageBox::Yes | QMessageBox::No);
- if (res == QMessageBox::Yes) {
- saveExecutable();
+ // don't allow changing the title to something that already exists
+ if (isTitleConflicting(s)) {
+ return;
+ }
- //This is necessary if we're adding a new item, but it doesn't look very nice.
- //Ideally we'd end up with the correct row displayed
- ui->executablesListBox->selectionModel()->clearSelection();
- ui->executablesListBox->selectionModel()->select(current, QItemSelectionModel::SelectCurrent);
- QTimer::singleShot(50, this, SLOT(delayedRefresh()));
- return;
- }
- }
+ // must save before modifying the item in the list widget because saving
+ // relies on the item's text being the same as an item in m_executablesList
+ save();
+
+ // once the executable is saved, the list item must be changed to match the
+ // new name
+ if (auto* i=selectedItem()) {
+ i->setText(s);
+ }
+}
+
+void EditExecutablesDialog::on_overwriteSteamAppID_toggled(bool checked)
+{
+ if (m_settingUI) {
+ return;
+ }
- ui->executablesListBox->selectionModel()->clearSelection();
- ui->executablesListBox->selectionModel()->select(current, QItemSelectionModel::SelectCurrent);
+ ui->steamAppID->setEnabled(checked);
+ save();
+}
- m_CurrentItem = ui->executablesListBox->item(current.row());
+void EditExecutablesDialog::on_createFilesInMod_toggled(bool checked)
+{
+ if (m_settingUI) {
+ return;
+ }
- Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text()));
+ ui->mods->setEnabled(checked);
+ save();
+}
- ui->titleEdit->setText(selectedExecutable.m_Title);
- ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath()));
- ui->argumentsEdit->setText(selectedExecutable.m_Arguments);
- ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory));
- ui->removeButton->setEnabled(selectedExecutable.isCustom());
- ui->overwriteAppIDBox->setChecked(!selectedExecutable.m_SteamAppID.isEmpty());
- if (!selectedExecutable.m_SteamAppID.isEmpty()) {
- ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID);
- } else {
- ui->appIDOverwriteEdit->clear();
- }
- ui->useAppIconCheckBox->setChecked(selectedExecutable.usesOwnIcon());
+void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked)
+{
+ if (m_settingUI) {
+ return;
+ }
- int index = -1;
+ ui->configureLibraries->setEnabled(ui->forceLoadLibraries->isChecked());
+ save();
+}
- QString customOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString();
- if (!customOverwrite.isEmpty()) {
- index = ui->newFilesModBox->findText(customOverwrite);
- qDebug("find %s -> %d", qUtf8Printable(customOverwrite), index);
- }
+void EditExecutablesDialog::on_browseBinary_clicked()
+{
+ const QString binaryName = FileDialogMemory::getOpenFileName(
+ "editExecutableBinary", this, tr("Select a binary"), ui->binary->text(),
+ tr("Executable (%1)").arg("*.exe *.bat *.jar"));
- ui->newFilesModCheckBox->setChecked(index != -1);
- if (index != -1) {
- ui->newFilesModBox->setCurrentIndex(index);
+ if (binaryName.isNull()) {
+ // canceled
+ return;
+ }
+
+ // setting binary
+ if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) {
+ // special case for jar files, uses the system java installation
+ setJarBinary(binaryName);
+ } else {
+ ui->binary->setText(QDir::toNativeSeparators(binaryName));
+ }
+
+ // setting title if currently empty
+ if (ui->title->text().isEmpty()) {
+ const auto prefix = QFileInfo(binaryName).baseName();
+ const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix);
+
+ if (newTitle) {
+ ui->title->setText(*newTitle);
}
+ }
+
+ save();
+}
+
+void EditExecutablesDialog::on_browseWorkingDirectory_clicked()
+{
+ QString dirName = FileDialogMemory::getExistingDirectory(
+ "editExecutableDirectory", this, tr("Select a directory"),
+ ui->workingDirectory->text());
+
+ if (dirName.isNull()) {
+ // canceled
+ return;
+ }
+
+ ui->workingDirectory->setText(dirName);
+}
+
+void EditExecutablesDialog::on_configureLibraries_clicked()
+{
+ auto* e = selectedExe();
+ if (!e) {
+ qWarning("trying to configure libraries but nothing is selected");
+ return;
+ }
+
+ ForcedLoadDialog dialog(m_organizerCore.managedGame(), this);
+
+ if (auto libraryList=m_forcedLibraries.find(e->title())) {
+ dialog.setValues(libraryList->value);
+ }
- m_ForcedLibraries = m_Profile->determineForcedLibraries(ui->titleEdit->text());
- bool forcedLibraries = m_Profile->forcedLibrariesEnabled(ui->titleEdit->text());
- ui->forceLoadButton->setEnabled(forcedLibraries);
- ui->forceLoadCheckBox->setChecked(forcedLibraries);
+ if (dialog.exec() == QDialog::Accepted) {
+ m_forcedLibraries.setValue(e->title(), dialog.values());
+ save();
+ }
+}
+
+void EditExecutablesDialog::on_buttons_clicked(QAbstractButton* b)
+{
+ if (b == ui->buttons->button(QDialogButtonBox::Ok)) {
+ commitChanges();
+ accept();
+ } else if (b == ui->buttons->button(QDialogButtonBox::Apply)) {
+ commitChanges();
+ } else {
+ reject();
}
}
-void EditExecutablesDialog::on_newFilesModCheckBox_toggled(bool checked)
+void EditExecutablesDialog::setJarBinary(const QString& binaryName)
{
- ui->newFilesModBox->setEnabled(checked);
+ auto java = OrganizerCore::findJavaInstallation(binaryName);
+
+ if (java.isEmpty()) {
+ QMessageBox::information(
+ this, tr("Java (32-bit) required"),
+ tr("MO requires 32-bit java to run this application. If you already "
+ "have it installed, select javaw.exe from that installation as "
+ "the binary."));
+ }
+
+ // only save once
+
+ m_settingUI = true;
+ ui->binary->setText(java);
+ ui->workingDirectory->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath()));
+ ui->arguments->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\"");
+ m_settingUI = false;
+
+ save();
}
diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h
index bee3cba6..9715489e 100644
--- a/src/editexecutablesdialog.h
+++ b/src/editexecutablesdialog.h
@@ -22,106 +22,201 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "tutorabledialog.h"
#include <QListWidgetItem>
-#include <QTimer>
#include "executableslist.h"
#include "profile.h"
#include "iplugingame.h"
+#include <QTimer>
+#include <QAbstractButton>
+#include <optional>
namespace Ui {
class EditExecutablesDialog;
}
-
class ModList;
+class OrganizerCore;
-
-/**
- * @brief Dialog to manage the list of executables
+/** helper class to manage custom overwrites within the edit executables
+ * dialog, stores a T and a bool in map indexed by a QString
**/
-class EditExecutablesDialog : public MOBase::TutorableDialog
+template <class T>
+class ToggableMap
{
- Q_OBJECT
-
public:
+ struct Value
+ {
+ bool enabled;
+ T value;
+
+ Value(bool b, T&& v)
+ : enabled(b), value(std::forward<T>(v))
+ {
+ }
+ };
/**
- * @brief constructor
- *
- * @param executablesList current list of executables
- * @param parent parent widget
+ * returns the Value associated with the given title, or empty
**/
- explicit EditExecutablesDialog(const ExecutablesList &executablesList,
- const ModList &modList,
- Profile *profile,
- const MOBase::IPluginGame *game,
- QWidget *parent = 0);
+ std::optional<Value> find(const QString& title) const
+ {
+ auto itor = m_map.find(title);
+ if (itor == m_map.end()) {
+ return {};
+ }
- ~EditExecutablesDialog();
+ return itor->second;
+ }
/**
- * @brief retrieve the updated list of executables
- *
- * @return updated list of executables
+ * sets the given value, adds it if not found
**/
- ExecutablesList getExecutablesList() const;
+ void set(QString title, bool b, T value)
+ {
+ m_map.insert_or_assign(std::move(title), Value(b, std::move(value)));
+ }
- void saveExecutable();
+ /**
+ * sets whether the given value is enabled, inserts it if not found
+ **/
+ void setEnabled(const QString& title, bool b)
+ {
+ auto itor = m_map.find(title);
-private slots:
- void on_newFilesModCheckBox_toggled(bool checked);
+ if (itor == m_map.end()) {
+ m_map.emplace(title, Value(b, {}));
+ } else {
+ itor->second.enabled = b;
+ }
+ }
-private slots:
+ /**
+ * sets the given value, inserts it enabled if not found
+ **/
+ void setValue(const QString& title, T value)
+ {
+ auto itor = m_map.find(title);
- void on_binaryEdit_textChanged(const QString &arg1);
+ if (itor == m_map.end()) {
+ m_map.emplace(title, Value(true, std::move(value)));
+ } else {
+ itor->second.value = std::move(value);
+ }
+ }
- void on_workingDirEdit_textChanged(const QString &arg1);
+ /**
+ * renames the given value, ignored if not found
+ **/
+ void rename(const QString& oldTitle, QString newTitle)
+ {
+ auto itor = m_map.find(oldTitle);
+ if (itor == m_map.end()) {
+ return;
+ }
- void on_addButton_clicked();
+ // move to new title, erase old
+ m_map.emplace(std::move(newTitle), std::move(itor->second));
+ m_map.erase(itor);
+ }
- void on_browseButton_clicked();
+ /**
+ * removes the given value, ignored if not found
+ **/
+ void remove(const QString& title)
+ {
+ auto itor = m_map.find(title);
+ if (itor == m_map.end()) {
+ return;
+ }
- void on_removeButton_clicked();
+ m_map.erase(itor);
+ }
- void on_titleEdit_textChanged(const QString &arg1);
+private:
+ std::map<QString, Value> m_map;
+};
- void on_overwriteAppIDBox_toggled(bool checked);
- void on_browseDirButton_clicked();
+/**
+ * @brief Dialog to manage the list of executables
+ **/
+class EditExecutablesDialog : public MOBase::TutorableDialog
+{
+ Q_OBJECT
- void on_closeButton_clicked();
+public:
+ using CustomOverwrites = ToggableMap<QString>;
+ using ForcedLibraries = ToggableMap<QList<MOBase::ExecutableForcedLoadSetting>>;
- void delayedRefresh();
+ explicit EditExecutablesDialog(OrganizerCore& oc, QWidget* parent=nullptr);
- void on_executablesListBox_itemSelectionChanged();
+ ~EditExecutablesDialog();
- void on_executablesListBox_clicked(const QModelIndex &index);
+ ExecutablesList getExecutablesList() const;
+ const CustomOverwrites& getCustomOverwrites() const;
+ const ForcedLibraries& getForcedLibraries() const;
- void on_forceLoadButton_clicked();
+private slots:
+ void on_list_itemSelectionChanged();
- void on_forceLoadCheckBox_toggled();
+ void on_reset_clicked();
+ void on_add_clicked();
+ void on_remove_clicked();
+ void on_up_clicked();
+ void on_down_clicked();
-private:
+ void on_title_textChanged(const QString& s);
+ void on_overwriteSteamAppID_toggled(bool checked);
+ void on_createFilesInMod_toggled(bool checked);
+ void on_forceLoadLibraries_toggled(bool checked);
- void resetInput();
+ void on_browseBinary_clicked();
+ void on_browseWorkingDirectory_clicked();
+ void on_configureLibraries_clicked();
- void refreshExecutablesWidget();
+ void on_buttons_clicked(QAbstractButton* b);
- bool executableChanged();
+private:
+ std::unique_ptr<Ui::EditExecutablesDialog> ui;
+ OrganizerCore& m_organizerCore;
- void updateButtonStates();
+ // copy of the original executables, used to clear the current settings when
+ // committing changes
+ const ExecutablesList m_originalExecutables;
-private:
- Ui::EditExecutablesDialog *ui;
+ // current executable list
+ ExecutablesList m_executablesList;
+
+ // custom overwrites set in the dialog
+ CustomOverwrites m_customOverwrites;
+
+ // forced libraries set in the dialog
+ ForcedLibraries m_forcedLibraries;
- QListWidgetItem *m_CurrentItem;
+ // true when the change events being triggered are in response to loading
+ // the executable's data into the UI, not from a user change
+ bool m_settingUI;
- ExecutablesList m_ExecutablesList;
- Profile *m_Profile;
+ void loadCustomOverwrites();
+ void loadForcedLibraries();
- QList<MOBase::ExecutableForcedLoadSetting> m_ForcedLibraries;
+ QListWidgetItem* selectedItem();
+ Executable* selectedExe();
- const MOBase::IPluginGame *m_GamePlugin;
+ void fillList();
+ QListWidgetItem* createListItem(const Executable& exe);
+ void updateUI(const QListWidgetItem* item, const Executable* e);
+ void clearEdits();
+ void setEdits(const Executable& e);
+ void setButtons(const QListWidgetItem* item, const Executable* e);
+ void save();
+ void saveOrder();
+ bool canMove(const QListWidgetItem* item, int direction);
+ void move(QListWidgetItem* item, int direction);
+ void setJarBinary(const QString& binaryName);
+ bool isTitleConflicting(const QString& s);
+ void commitChanges();
+ void setDirty(bool b);
};
#endif // EDITEXECUTABLESDIALOG_H
diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui
index 4f9223d5..a42dbeed 100644
--- a/src/editexecutablesdialog.ui
+++ b/src/editexecutablesdialog.ui
@@ -6,323 +6,486 @@
<rect>
<x>0</x>
<y>0</y>
- <width>426</width>
- <height>460</height>
+ <width>710</width>
+ <height>440</height>
</rect>
</property>
- <property name="minimumSize">
- <size>
- <width>200</width>
- <height>200</height>
- </size>
- </property>
<property name="windowTitle">
<string>Modify Executables</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QListWidget" name="executablesListBox">
- <property name="toolTip">
- <string>List of configured executables</string>
- </property>
- <property name="whatsThis">
- <string>This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified.</string>
- </property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::InternalMove</enum>
- </property>
- <property name="defaultDropAction">
- <enum>Qt::TargetMoveAction</enum>
+ <widget class="QWidget" name="widget_3" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>100</height>
+ </size>
</property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string>Title</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="titleEdit">
- <property name="toolTip">
- <string>Name of the executable. This is only for display purposes.</string>
- </property>
- <property name="whatsThis">
- <string>Name of the executable. This is only for display purposes.</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Binary</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="binaryEdit">
- <property name="toolTip">
- <string>Binary to run</string>
- </property>
- <property name="whatsThis">
- <string>Binary to run</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="browseButton">
- <property name="toolTip">
- <string>Browse filesystem</string>
- </property>
- <property name="whatsThis">
- <string>Browse filesystem for the executable to run.</string>
- </property>
- <property name="text">
- <string>...</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_6">
- <item>
- <widget class="QLabel" name="label_4">
- <property name="text">
- <string>Start in</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="workingDirEdit"/>
- </item>
- <item>
- <widget class="QPushButton" name="browseDirButton">
- <property name="text">
- <string>...</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Arguments</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="argumentsEdit">
- <property name="toolTip">
- <string>Arguments to pass to the application</string>
- </property>
- <property name="whatsThis">
- <string>Arguments to pass to the application</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_5">
- <item>
- <widget class="QCheckBox" name="overwriteAppIDBox">
- <property name="toolTip">
- <string>Allow the Steam AppID to be used for this executable to be changed.</string>
- </property>
- <property name="whatsThis">
- <string>Allow the Steam AppID to be used for this executable to be changed.
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QSplitter" name="splitter">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="handleWidth">
+ <number>9</number>
+ </property>
+ <property name="childrenCollapsible">
+ <bool>false</bool>
+ </property>
+ <widget class="QWidget" name="widget_2" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Executables</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="add">
+ <property name="toolTip">
+ <string>Add an executable</string>
+ </property>
+ <property name="statusTip">
+ <string>Add an executable</string>
+ </property>
+ <property name="whatsThis">
+ <string>Add an executable</string>
+ </property>
+ <property name="text">
+ <string>Add</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/add</normaloff>:/MO/gui/add</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="remove">
+ <property name="toolTip">
+ <string>Remove the selected executable</string>
+ </property>
+ <property name="statusTip">
+ <string>Remove the selected executable</string>
+ </property>
+ <property name="whatsThis">
+ <string>Remove the selected executable</string>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/resources/list-remove.png</normaloff>:/MO/gui/resources/list-remove.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="up">
+ <property name="toolTip">
+ <string>Move the executable up in the list</string>
+ </property>
+ <property name="statusTip">
+ <string>Move the executable up in the list</string>
+ </property>
+ <property name="whatsThis">
+ <string>Move the executable up in the list</string>
+ </property>
+ <property name="text">
+ <string>Move the executable up in the list</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/resources/go-up.png</normaloff>:/MO/gui/resources/go-up.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="down">
+ <property name="toolTip">
+ <string>Move the executable down in the list</string>
+ </property>
+ <property name="statusTip">
+ <string>Move the executable down in the list</string>
+ </property>
+ <property name="whatsThis">
+ <string>Move the executable down in the list</string>
+ </property>
+ <property name="text">
+ <string>Move the executable down in the list</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/resources/go-down.png</normaloff>:/MO/gui/resources/go-down.png</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="reset">
+ <property name="toolTip">
+ <string>Adds the executables provided by the game plugin and moves any existing executables out of the way</string>
+ </property>
+ <property name="statusTip">
+ <string>Adds the executables provided by the game plugin and moves any existing executables out of the way</string>
+ </property>
+ <property name="whatsThis">
+ <string>Adds the executables provided by the game plugin and moves any existing executables out of the way</string>
+ </property>
+ <property name="text">
+ <string>Reset</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QListWidget" name="list">
+ <property name="toolTip">
+ <string>List of configured executables</string>
+ </property>
+ <property name="whatsThis">
+ <string>This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified.</string>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::InternalMove</enum>
+ </property>
+ <property name="defaultDropAction">
+ <enum>Qt::TargetMoveAction</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="widget_4" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>10</number>
+ </property>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="bottomMargin">
+ <number>20</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Title</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="title">
+ <property name="toolTip">
+ <string>Name of the executable. This is only for display purposes.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Name of the executable. This is only for display purposes.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Binary</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
+ <item>
+ <widget class="QLineEdit" name="binary">
+ <property name="toolTip">
+ <string>Binary to run</string>
+ </property>
+ <property name="whatsThis">
+ <string>Binary to run</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="browseBinary">
+ <property name="toolTip">
+ <string>Browse filesystem</string>
+ </property>
+ <property name="whatsThis">
+ <string>Browse filesystem for the executable to run.</string>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Start in</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="1,0">
+ <item>
+ <widget class="QLineEdit" name="workingDirectory"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="browseWorkingDirectory">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Arguments</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="arguments">
+ <property name="toolTip">
+ <string>Arguments to pass to the application</string>
+ </property>
+ <property name="whatsThis">
+ <string>Arguments to pass to the application</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QCheckBox" name="overwriteSteamAppID">
+ <property name="toolTip">
+ <string>Allow the Steam AppID to be used for this executable to be changed.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Allow the Steam AppID to be used for this executable to be changed.
Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game.
Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured.</string>
- </property>
- <property name="text">
- <string>Overwrite Steam AppID</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="appIDOverwriteEdit">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="toolTip">
- <string>Steam AppID to use for this executable that differs from the games AppID.</string>
- </property>
- <property name="whatsThis">
- <string>Steam AppID to use for this executable that differs from the games AppID.
+ </property>
+ <property name="text">
+ <string>Overwrite Steam AppID</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="steamAppID">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>Steam AppID to use for this executable that differs from the games AppID.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Steam AppID to use for this executable that differs from the games AppID.
Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850).
Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured.</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,1">
- <item>
- <widget class="QCheckBox" name="newFilesModCheckBox">
- <property name="toolTip">
- <string>If this is enabled, new files are created in the specified mod instead of the &quot;Overwrite&quot; mod.</string>
- </property>
- <property name="text">
- <string>Create Files in Mod instead of Overwrite (*)</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="newFilesModBox">
- <property name="enabled">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_8">
- <item>
- <widget class="QCheckBox" name="forceLoadCheckBox">
- <property name="toolTip">
- <string>If this is enabled, the configured libraries will be automatically loaded when this executable is launched.</string>
- </property>
- <property name="text">
- <string>Force Load Libraries (*)</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="forceLoadButton">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="text">
- <string>Configure Libraries</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QCheckBox" name="useAppIconCheckBox">
- <property name="text">
- <string>Use Application's Icon for shortcuts</string>
- </property>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,1">
+ <item>
+ <widget class="QCheckBox" name="createFilesInMod">
+ <property name="toolTip">
+ <string>If this is enabled, new files are created in the specified mod instead of the &quot;Overwrite&quot; mod.</string>
+ </property>
+ <property name="text">
+ <string>Create Files in Mod instead of Overwrite (*)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="mods">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <widget class="QCheckBox" name="forceLoadLibraries">
+ <property name="toolTip">
+ <string>If this is enabled, the configured libraries will be automatically loaded when this executable is launched.</string>
+ </property>
+ <property name="text">
+ <string>Force Load Libraries (*)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="configureLibraries">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Configure Libraries</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useApplicationIcon">
+ <property name="text">
+ <string>Use Application's Icon for shortcuts</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>(*) Profile Specific</string>
+ </property>
+ <property name="margin">
+ <number>5</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>(*) This setting is profile-specific</string>
+ <widget class="QDialogButtonBox" name="buttons">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QPushButton" name="addButton">
- <property name="toolTip">
- <string>Add an executable</string>
- </property>
- <property name="whatsThis">
- <string>Add an executable</string>
- </property>
- <property name="text">
- <string>Add</string>
- </property>
- <property name="icon">
- <iconset>
- <normaloff>:/new/guiresources/resources/list-add.png</normaloff>:/new/guiresources/resources/list-add.png</iconset>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="removeButton">
- <property name="toolTip">
- <string>Remove the selected executable</string>
- </property>
- <property name="whatsThis">
- <string>Remove the selected executable</string>
- </property>
- <property name="text">
- <string>Remove</string>
- </property>
- <property name="icon">
- <iconset>
- <normaloff>:/new/guiresources/resources/list-remove.png</normaloff>:/new/guiresources/resources/list-remove.png</iconset>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="closeButton">
- <property name="text">
- <string>Close</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
</layout>
</widget>
<tabstops>
- <tabstop>executablesListBox</tabstop>
- <tabstop>titleEdit</tabstop>
- <tabstop>binaryEdit</tabstop>
- <tabstop>browseButton</tabstop>
- <tabstop>workingDirEdit</tabstop>
- <tabstop>browseDirButton</tabstop>
- <tabstop>argumentsEdit</tabstop>
- <tabstop>overwriteAppIDBox</tabstop>
- <tabstop>appIDOverwriteEdit</tabstop>
- <tabstop>newFilesModCheckBox</tabstop>
- <tabstop>newFilesModBox</tabstop>
- <tabstop>useAppIconCheckBox</tabstop>
- <tabstop>addButton</tabstop>
- <tabstop>removeButton</tabstop>
- <tabstop>closeButton</tabstop>
+ <tabstop>binary</tabstop>
+ <tabstop>browseBinary</tabstop>
+ <tabstop>workingDirectory</tabstop>
+ <tabstop>browseWorkingDirectory</tabstop>
+ <tabstop>overwriteSteamAppID</tabstop>
+ <tabstop>steamAppID</tabstop>
+ <tabstop>createFilesInMod</tabstop>
+ <tabstop>mods</tabstop>
</tabstops>
- <resources/>
+ <resources>
+ <include location="resources.qrc"/>
+ </resources>
<connections/>
</ui>
diff --git a/src/executableslist.cpp b/src/executableslist.cpp
index 4b2e380b..077d2a93 100644
--- a/src/executableslist.cpp
+++ b/src/executableslist.cpp
@@ -33,193 +33,418 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
+ExecutablesList::iterator ExecutablesList::begin()
+{
+ return m_Executables.begin();
+}
-ExecutablesList::ExecutablesList()
+ExecutablesList::const_iterator ExecutablesList::begin() const
{
+ return m_Executables.begin();
}
-ExecutablesList::~ExecutablesList()
+ExecutablesList::iterator ExecutablesList::end()
{
+ return m_Executables.end();
}
-void ExecutablesList::init(IPluginGame const *game)
+ExecutablesList::const_iterator ExecutablesList::end() const
{
- Q_ASSERT(game != nullptr);
+ return m_Executables.end();
+}
+
+std::size_t ExecutablesList::size() const
+{
+ return m_Executables.size();
+}
+
+bool ExecutablesList::empty() const
+{
+ return m_Executables.empty();
+}
+
+void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings)
+{
+ qDebug("setting up configured executables");
+
m_Executables.clear();
- for (const ExecutableInfo &info : game->executables()) {
- if (info.isValid()) {
- addExecutableInternal(info.title(),
- info.binary().absoluteFilePath(),
- info.arguments().join(" "),
- info.workingDirectory().absolutePath(),
- info.steamAppID());
+
+ // whether the executable list in the .ini is still using the old custom
+ // executables from 2.2.0, see upgradeFromCustom()
+ bool needsUpgrade = false;
+
+ int numCustomExecutables = settings.beginReadArray("customExecutables");
+ for (int i = 0; i < numCustomExecutables; ++i) {
+ settings.setArrayIndex(i);
+
+ Executable::Flags flags;
+ if (settings.value("toolbar", false).toBool())
+ flags |= Executable::ShowInToolbar;
+ if (settings.value("ownicon", false).toBool())
+ flags |= Executable::UseApplicationIcon;
+
+ if (settings.contains("custom")) {
+ // the "custom" setting only exists in older versions
+ needsUpgrade = true;
}
- }
- ExecutableInfo explorerpp = ExecutableInfo("Explore Virtual Folder", QFileInfo(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe" ))
- .withArgument(QString("\"%1\"").arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath())));
- if (explorerpp.isValid()) {
- addExecutableInternal(explorerpp.title(),
- explorerpp.binary().absoluteFilePath(),
- explorerpp.arguments().join(" "),
- explorerpp.workingDirectory().absolutePath(),
- explorerpp.steamAppID());
+ setExecutable(Executable()
+ .title(settings.value("title").toString())
+ .binaryInfo(settings.value("binary").toString())
+ .arguments(settings.value("arguments").toString())
+ .steamAppID(settings.value("steamAppID", "").toString())
+ .workingDirectory(settings.value("workingDirectory", "").toString())
+ .flags(flags));
}
+ settings.endArray();
+
+ addFromPlugin(game, IgnoreExisting);
+
+ if (needsUpgrade)
+ upgradeFromCustom(game);
}
-void ExecutablesList::getExecutables(std::vector<Executable>::iterator &begin, std::vector<Executable>::iterator &end)
+void ExecutablesList::store(QSettings& settings)
{
- begin = m_Executables.begin();
- end = m_Executables.end();
+ settings.remove("customExecutables");
+ settings.beginWriteArray("customExecutables");
+
+ int count = 0;
+
+ for (const auto& item : *this) {
+ settings.setArrayIndex(count++);
+
+ settings.setValue("title", item.title());
+ settings.setValue("toolbar", item.isShownOnToolbar());
+ settings.setValue("ownicon", item.usesOwnIcon());
+ settings.setValue("binary", item.binaryInfo().absoluteFilePath());
+ settings.setValue("arguments", item.arguments());
+ settings.setValue("workingDirectory", item.workingDirectory());
+ settings.setValue("steamAppID", item.steamAppID());
+ }
+
+ settings.endArray();
}
-void ExecutablesList::getExecutables(std::vector<Executable>::const_iterator &begin,
- std::vector<Executable>::const_iterator &end) const
+std::vector<Executable> ExecutablesList::getPluginExecutables(
+ MOBase::IPluginGame const *game) const
{
- begin = m_Executables.begin();
- end = m_Executables.end();
+ Q_ASSERT(game != nullptr);
+
+ std::vector<Executable> v;
+
+ for (const ExecutableInfo &info : game->executables()) {
+ if (!info.isValid()) {
+ continue;
+ }
+
+ v.push_back({info, Executable::UseApplicationIcon});
+ }
+
+ const QFileInfo eppBin(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe");
+
+ if (eppBin.exists()) {
+ const auto args = QString("\"%1\"")
+ .arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath()));
+
+ const auto exe = Executable()
+ .title("Explore Virtual Folder")
+ .binaryInfo(eppBin)
+ .arguments(args)
+ .workingDirectory(eppBin.absolutePath())
+ .flags(Executable::UseApplicationIcon);
+
+ v.push_back(exe);
+ }
+
+ return v;
}
-const Executable &ExecutablesList::find(const QString &title) const
+void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game)
{
- for (Executable const &exe : m_Executables) {
- if (exe.m_Title == title) {
- return exe;
- }
+ qDebug("resetting plugin executables");
+
+ Q_ASSERT(game != nullptr);
+
+ for (const auto& exe : getPluginExecutables(game)) {
+ setExecutable(exe, MoveExisting);
}
- throw std::runtime_error(QString("invalid executable name: %1").arg(title).toLocal8Bit().constData());
}
+void ExecutablesList::addFromPlugin(IPluginGame const *game, SetFlags flags)
+{
+ Q_ASSERT(game != nullptr);
-Executable &ExecutablesList::find(const QString &title)
+ for (const auto& exe : getPluginExecutables(game)) {
+ setExecutable(exe, flags);
+ }
+}
+
+const Executable &ExecutablesList::get(const QString &title) const
{
- for (Executable &exe : m_Executables) {
- if (exe.m_Title == title) {
+ for (const auto& exe : m_Executables) {
+ if (exe.title() == title) {
return exe;
}
}
- throw std::runtime_error(QString("invalid executable name: %1").arg(title).toLocal8Bit().constData());
+
+ throw std::runtime_error(QString("executable not found: %1").arg(title).toLocal8Bit().constData());
}
+Executable &ExecutablesList::get(const QString &title)
+{
+ return const_cast<Executable&>(std::as_const(*this).get(title));
+}
-Executable &ExecutablesList::findByBinary(const QFileInfo &info)
+Executable &ExecutablesList::getByBinary(const QFileInfo &info)
{
for (Executable &exe : m_Executables) {
- if (info == exe.m_BinaryInfo) {
+ if (exe.binaryInfo() == info) {
return exe;
}
}
throw std::runtime_error("invalid info");
}
-
-std::vector<Executable>::iterator ExecutablesList::findExe(const QString &title)
+ExecutablesList::iterator ExecutablesList::find(const QString &title)
{
- for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
- if (iter->m_Title == title) {
- return iter;
- }
- }
- return m_Executables.end();
+ return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; });
}
+ExecutablesList::const_iterator ExecutablesList::find(const QString &title) const
+{
+ return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; });
+}
bool ExecutablesList::titleExists(const QString &title) const
{
- auto test = [&] (const Executable &exe) { return exe.m_Title == title; };
+ auto test = [&] (const Executable &exe) { return exe.title() == title; };
return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end();
}
+void ExecutablesList::setExecutable(const Executable &exe)
+{
+ setExecutable(exe, MergeExisting);
+}
-void ExecutablesList::addExecutable(const Executable &executable)
+void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags)
{
- auto existingExe = findExe(executable.m_Title);
- if (existingExe != m_Executables.end()) {
- *existingExe = executable;
+ auto itor = find(exe.title());
+
+ if (itor != end()) {
+ if (flags == IgnoreExisting) {
+ return;
+ }
+
+ if (flags == MoveExisting) {
+ const auto newTitle = makeNonConflictingTitle(exe.title());
+ if (!newTitle) {
+ qCritical().nospace()
+ << "executable '" << exe.title() << "' was in the way but could "
+ << "not be renamed";
+
+ return;
+ }
+
+ qWarning().nospace()
+ << "executable '" << itor->title() << "' was in the way and was "
+ << "renamed to '" << *newTitle << "'";
+
+ itor->title(*newTitle);
+ itor = end();
+ }
+ }
+
+ if (itor == m_Executables.end()) {
+ m_Executables.push_back(exe);
} else {
- m_Executables.push_back(executable);
+ itor->mergeFrom(exe);
}
}
+void ExecutablesList::remove(const QString &title)
+{
+ auto itor = find(title);
+ if (itor != m_Executables.end()) {
+ m_Executables.erase(itor);
+ }
+}
-void ExecutablesList::updateExecutable(const QString &title,
- const QString &executableName,
- const QString &arguments,
- const QString &workingDirectory,
- const QString &steamAppID,
- Executable::Flags mask,
- Executable::Flags flags)
+std::optional<QString> ExecutablesList::makeNonConflictingTitle(
+ const QString& prefix)
{
- QFileInfo file(executableName);
- QDir dir(workingDirectory);
- auto existingExe = findExe(title);
- flags &= mask;
+ const int max = 100;
- if (existingExe != m_Executables.end()) {
- existingExe->m_Title = title;
- existingExe->m_Flags &= ~mask;
- existingExe->m_Flags |= flags;
- // for pre-configured executables don't overwrite settings we didn't store
- if (flags & Executable::CustomExecutable) {
- if (file.exists()) {
- // don't overwrite a valid binary with an invalid one
- existingExe->m_BinaryInfo = file;
- }
- if (dir.exists()) {
- // don't overwrite a valid working directory with an invalid one
- existingExe->m_WorkingDirectory = workingDirectory;
- }
- existingExe->m_Arguments = arguments;
- existingExe->m_SteamAppID = steamAppID;
+ QString title = prefix;
+
+ for (int i=1; i<max; ++i) {
+ if (!titleExists(title)) {
+ return title;
}
- } else {
- Executable newExe;
- newExe.m_Title = title;
- newExe.m_BinaryInfo = file;
- newExe.m_Arguments = arguments;
- newExe.m_WorkingDirectory = workingDirectory;
- newExe.m_SteamAppID = steamAppID;
- newExe.m_Flags = Executable::CustomExecutable | flags;
- m_Executables.push_back(newExe);
+
+ title = prefix + QString(" (%1)").arg(i);
}
-}
+ qCritical().nospace()
+ << "ran out of executable titles for prefix '" << prefix << "'";
-void ExecutablesList::remove(const QString &title)
+ return {};
+}
+
+void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game)
{
- for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
- if (iter->isCustom() && (iter->m_Title == title)) {
- m_Executables.erase(iter);
- break;
+ qDebug() << "upgrading executables list";
+
+ Q_ASSERT(game != nullptr);
+
+ // prior to 2.2.1, plugin executables were special in the sense that they
+ // did not store certain settings, like the binary info and working directory;
+ // those were filled in when MO started, but never saved
+ //
+ // this interferes with the new executables list, which is completely
+ // customizable, because plugin executables are only added to the list when
+ // they don't exist at all and are ignored otherwise, leaving some of the
+ // fields completely blank
+ //
+ // when the "custom" setting is found in the .ini file (see load()), it is
+ // assumed that the older scheme is still present; in that case, the plugin
+ // executables force their binary info and working directory into the existing
+ // executables one last time
+ //
+ // from that point on, plugin executables are ignored unless they're
+ // completely missing from the list, allowing users to customize them as they
+ // want
+
+ for (const auto& exe : getPluginExecutables(game)) {
+ auto itor = find(exe.title());
+ if (itor == end()){
+ continue;
+ }
+
+ if (!itor->binaryInfo().exists()) {
+ itor->binaryInfo(exe.binaryInfo());
+ }
+
+ if (itor->workingDirectory().isEmpty()) {
+ itor->workingDirectory(exe.workingDirectory());
}
}
}
-void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName,
- const QString &arguments, const QString &workingDirectory,
- const QString &steamAppID)
+Executable::Executable(QString title)
+ : m_title(title)
{
- QFileInfo file(executableName);
- if (file.exists()) {
- Executable newExe;
- newExe.m_BinaryInfo = file;
- newExe.m_Title = title;
- newExe.m_Arguments = arguments;
- newExe.m_WorkingDirectory = workingDirectory;
- newExe.m_SteamAppID = steamAppID;
- newExe.m_Flags = Executable::UseApplicationIcon;
- m_Executables.push_back(newExe);
- }
}
+Executable::Executable(const MOBase::ExecutableInfo& info, Flags flags) :
+ m_title(info.title()),
+ m_binaryInfo(info.binary()),
+ m_arguments(info.arguments().join(" ")),
+ m_steamAppID(info.steamAppID()),
+ m_workingDirectory(info.workingDirectory().absolutePath()),
+ m_flags(flags)
+{
+}
+
+const QString& Executable::title() const
+{
+ return m_title;
+}
+
+const QFileInfo& Executable::binaryInfo() const
+{
+ return m_binaryInfo;
+}
+
+const QString& Executable::arguments() const
+{
+ return m_arguments;
+}
+
+const QString& Executable::steamAppID() const
+{
+ return m_steamAppID;
+}
+
+const QString& Executable::workingDirectory() const
+{
+ return m_workingDirectory;
+}
+
+Executable::Flags Executable::flags() const
+{
+ return m_flags;
+}
+
+Executable& Executable::title(const QString& s)
+{
+ m_title = s;
+ return *this;
+}
+
+Executable& Executable::binaryInfo(const QFileInfo& fi)
+{
+ m_binaryInfo = fi;
+ return *this;
+}
+
+Executable& Executable::arguments(const QString& s)
+{
+ m_arguments = s;
+ return *this;
+}
+
+Executable& Executable::steamAppID(const QString& s)
+{
+ m_steamAppID = s;
+ return *this;
+}
+
+Executable& Executable::workingDirectory(const QString& s)
+{
+ m_workingDirectory = s;
+ return *this;
+}
+
+Executable& Executable::flags(Flags f)
+{
+ m_flags = f;
+ return *this;
+}
+
+bool Executable::isShownOnToolbar() const
+{
+ return m_flags.testFlag(ShowInToolbar);
+}
-void Executable::showOnToolbar(bool state)
+void Executable::setShownOnToolbar(bool state)
{
if (state) {
- m_Flags |= ShowInToolbar;
+ m_flags |= ShowInToolbar;
} else {
- m_Flags &= ~ShowInToolbar;
+ m_flags &= ~ShowInToolbar;
}
}
+
+bool Executable::usesOwnIcon() const
+{
+ return m_flags.testFlag(UseApplicationIcon);
+}
+
+void Executable::mergeFrom(const Executable& other)
+{
+ // flags on plugin executables that the user is allowed to change
+ const auto allow = ShowInToolbar;
+
+ // this happens after executables are loaded from settings and plugin
+ // executables are being added, or when users are modifying executables
+
+ m_title = other.title();
+ m_binaryInfo = other.binaryInfo();
+ m_arguments = other.arguments();
+ m_steamAppID = other.steamAppID();
+ m_workingDirectory = other.workingDirectory();
+ m_flags = other.flags();
+}
diff --git a/src/executableslist.h b/src/executableslist.h
index 0534c09e..2d1dd28e 100644
--- a/src/executableslist.h
+++ b/src/executableslist.h
@@ -23,41 +23,61 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "executableinfo.h"
#include <vector>
+#include <optional>
#include <QFileInfo>
#include <QMetaType>
-namespace MOBase { class IPluginGame; }
+namespace MOBase { class IPluginGame; class ExecutableInfo; }
/*!
* @brief Information about an executable
**/
-struct Executable {
- QString m_Title;
- QFileInfo m_BinaryInfo;
- QString m_Arguments;
- QString m_SteamAppID;
- QString m_WorkingDirectory;
-
- enum Flag {
- CustomExecutable = 0x01,
+class Executable
+{
+public:
+ enum Flag
+ {
ShowInToolbar = 0x02,
- UseApplicationIcon = 0x04,
-
- AllFlags = 0xff //I know, I know
+ UseApplicationIcon = 0x04
};
- Q_DECLARE_FLAGS(Flags, Flag)
+ Q_DECLARE_FLAGS(Flags, Flag);
+
+ Executable(QString title={});
+
+ /**
+ * @brief Executable from plugin
+ */
+ Executable(const MOBase::ExecutableInfo& info, Flags flags);
- Flags m_Flags;
+ const QString& title() const;
+ const QFileInfo& binaryInfo() const;
+ const QString& arguments() const;
+ const QString& steamAppID() const;
+ const QString& workingDirectory() const;
+ Flags flags() const;
- bool isCustom() const { return m_Flags.testFlag(CustomExecutable); }
+ Executable& title(const QString& s);
+ Executable& binaryInfo(const QFileInfo& fi);
+ Executable& arguments(const QString& s);
+ Executable& steamAppID(const QString& s);
+ Executable& workingDirectory(const QString& s);
+ Executable& flags(Flags f);
- bool isShownOnToolbar() const { return m_Flags.testFlag(ShowInToolbar); }
+ bool isShownOnToolbar() const;
+ void setShownOnToolbar(bool state);
+ bool usesOwnIcon() const;
- void showOnToolbar(bool state);
+ void mergeFrom(const Executable& other);
- bool usesOwnIcon() const { return m_Flags.testFlag(UseApplicationIcon); }
+private:
+ QString m_title;
+ QFileInfo m_binaryInfo;
+ QString m_arguments;
+ QString m_steamAppID;
+ QString m_workingDirectory;
+ Flags m_flags;
};
@@ -66,28 +86,44 @@ struct Executable {
**/
class ExecutablesList {
public:
+ using vector_type = std::vector<Executable>;
+ using iterator = vector_type::iterator;
+ using const_iterator = vector_type::const_iterator;
/**
- * @brief constructor
- *
- **/
- ExecutablesList();
+ * standard container interface
+ */
+ iterator begin();
+ const_iterator begin() const;
+ iterator end();
+ const_iterator end() const;
+ std::size_t size() const;
+ bool empty() const;
- ~ExecutablesList();
+ /**
+ * @brief initializes the list from the settings and the given plugin
+ **/
+ void load(const MOBase::IPluginGame* game, QSettings& settings);
/**
- * @brief initialise the list with the executables preconfigured for this game
+ * @brief re-adds all the executables from the plugin and renames existing
+ * executables that are in the way
**/
- void init(MOBase::IPluginGame const *game);
+ void resetFromPlugin(MOBase::IPluginGame const *game);
/**
- * @brief find an executable by its name
+ * @brief writes the current list to the settings
+ */
+ void store(QSettings& settings);
+
+ /**
+ * @brief get an executable by name
*
* @param title the title of the executable to look up
* @return the executable
- * @exception runtime_error will throw an exception if the name is not correct
+ * @exception runtime_error will throw an exception if the executable is not found
**/
- const Executable &find(const QString &title) const;
+ const Executable &get(const QString &title) const;
/**
* @brief find an executable by its name
@@ -96,7 +132,7 @@ public:
* @return the executable
* @exception runtime_error will throw an exception if the name is not correct
**/
- Executable &find(const QString &title);
+ Executable &get(const QString &title);
/**
* @brief find an executable by a fileinfo structure
@@ -104,7 +140,13 @@ public:
* @return the executable
* @exception runtime_error will throw an exception if the name is not correct
*/
- Executable &findByBinary(const QFileInfo &info);
+ Executable &getByBinary(const QFileInfo &info);
+
+ /**
+ * @brief returns an iterator for the given executable by title, or end()
+ */
+ iterator find(const QString &title);
+ const_iterator find(const QString &title) const;
/**
* @brief determine if an executable exists
@@ -117,85 +159,61 @@ public:
* @brief add a new executable to the list
* @param executable
*/
- void addExecutable(const Executable &executable);
+ void setExecutable(const Executable &executable);
/**
- * @brief add a new executable to the list
+ * @brief remove the executable with the specified file name. This needs to
+ * be an absolute file path
*
- * @param title name displayed in the UI
- * @param executableName the actual filename to execute
- * @param arguments arguments to pass to the executable
+ * @param title title of the executable to remove
+ * @note if the executable name is invalid, nothing happens. There is no way
+ * to determine if this was successful
**/
- void addExecutable(const QString &title,
- const QString &executableName,
- const QString &arguments,
- const QString &workingDirectory,
- const QString &steamAppID,
- Executable::Flags flags)
- {
- updateExecutable(title, executableName, arguments, workingDirectory,
- steamAppID, Executable::AllFlags, flags);
- }
+ void remove(const QString &title);
/**
- * @brief Update an executable to the list
- *
- * @param title name displayed in the UI
- * @param executableName the actual filename to execute
- * @param arguments arguments to pass to the executable
- * @param closeMO if true, MO will be closed when the binary is started
- **/
- void updateExecutable(const QString &title,
- const QString &executableName,
- const QString &arguments,
- const QString &workingDirectory,
- const QString &steamAppID,
- Executable::Flags mask,
- Executable::Flags flags);
+ * returns a title that starts with the given prefix and does not clash with
+ * an existing executable, may fail
+ */
+ std::optional<QString> makeNonConflictingTitle(const QString& prefix);
+
+private:
+ enum SetFlags
+ {
+ // executables having the same name as existing ones are ignored
+ IgnoreExisting = 1,
+
+ // executables having the same name are merged
+ MergeExisting,
+
+ // an existing executable with the same name is renamed
+ MoveExisting
+ };
+
+ std::vector<Executable> m_Executables;
+
/**
- * @brief remove the executable with the specified file name. This needs to be an absolute file path
- *
- * @param title title of the executable to remove
- * @note if the executable name is invalid, nothing happens. There is no way to determine if this was successful
+ * @brief add the executables preconfigured for this game
**/
- void remove(const QString &title);
+ void addFromPlugin(MOBase::IPluginGame const *game, SetFlags flags);
/**
- * @brief retrieve begin and end iterators of the configured executables
- *
- * @param begin iterator to the first executable
- * @param end iterator one past the last executable
- **/
- void getExecutables(std::vector<Executable>::const_iterator &begin, std::vector<Executable>::const_iterator &end) const;
+ * @brief add a new executable to the list
+ * @param executable
+ */
+ void setExecutable(const Executable &exe, SetFlags flags);
/**
- * @brief retrieve begin and end iterators of the configured executables
- *
- * @param begin iterator to the first executable
- * @param end iterator one past the last executable
+ * returns the executables provided by the game plugin
**/
- void getExecutables(std::vector<Executable>::iterator &begin, std::vector<Executable>::iterator &end);
+ std::vector<Executable> getPluginExecutables(
+ MOBase::IPluginGame const *game) const;
/**
- * @brief get the number of executables (custom or otherwise)
+ * called when MO is still using the old custom executables from 2.2.0
**/
- size_t size() const {
- return m_Executables.size();
- }
-
-private:
-
- std::vector<Executable>::iterator findExe(const QString &title);
-
- void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments,
- const QString &workingDirectory,
- const QString &steamAppID);
-
-private:
-
- std::vector<Executable> m_Executables;
-
+ void upgradeFromCustom(const MOBase::IPluginGame* game);
};
Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags)
diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp
index 6b440d0f..554a6235 100644
--- a/src/filedialogmemory.cpp
+++ b/src/filedialogmemory.cpp
@@ -56,33 +56,51 @@ void FileDialogMemory::restore(QSettings &settings)
}
-QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent,
- const QString &caption, const QString &dir,
- const QString &filter, QString *selectedFilter,
- QFileDialog::Options options)
+QString FileDialogMemory::getOpenFileName(
+ const QString &dirID, QWidget *parent, const QString &caption,
+ const QString &dir, const QString &filter, QString *selectedFilter,
+ QFileDialog::Options options)
{
- std::pair<std::map<QString, QString>::iterator, bool> currentDir =
- instance().m_Cache.insert(std::make_pair(dirID, dir));
+ QString currentDir = dir;
+
+ if (currentDir.isEmpty()) {
+ auto itor = instance().m_Cache.find(dirID);
+ if (itor != instance().m_Cache.end()) {
+ currentDir = itor->first;
+ }
+ }
+
+ QString result = QFileDialog::getOpenFileName(
+ parent, caption, currentDir, filter, selectedFilter, options);
- QString result = QFileDialog::getOpenFileName(parent, caption, currentDir.first->second,
- filter, selectedFilter, options);
if (!result.isNull()) {
instance().m_Cache[dirID] = QFileInfo(result).path();
}
+
return result;
}
-QString FileDialogMemory::getExistingDirectory(const QString &dirID, QWidget *parent,
- const QString &caption, const QString &dir, QFileDialog::Options options)
+QString FileDialogMemory::getExistingDirectory(
+ const QString &dirID, QWidget *parent, const QString &caption,
+ const QString &dir, QFileDialog::Options options)
{
- std::pair<std::map<QString, QString>::iterator, bool> currentDir =
- instance().m_Cache.insert(std::make_pair(dirID, dir));
+ QString currentDir = dir;
+
+ if (currentDir.isEmpty()) {
+ auto itor = instance().m_Cache.find(dirID);
+ if (itor != instance().m_Cache.end()) {
+ currentDir = itor->first;
+ }
+ }
+
+ QString result = QFileDialog::getExistingDirectory(
+ parent, caption, currentDir, options);
- QString result = QFileDialog::getExistingDirectory(parent, caption, currentDir.first->second, options);
if (!result.isNull()) {
instance().m_Cache[dirID] = QFileInfo(result).path();
}
+
return result;
}
diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h
index b2bfdb53..81d7ba40 100644
--- a/src/filedialogmemory.h
+++ b/src/filedialogmemory.h
@@ -29,30 +29,25 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
class FileDialogMemory
{
-
public:
-
static void save(QSettings &settings);
static void restore(QSettings &settings);
- static QString getOpenFileName(const QString &dirID, QWidget *parent = 0, const QString &caption = QString(),
- const QString &dir = QString(), const QString &filter = QString(),
- QString *selectedFilter = 0, QFileDialog::Options options = 0);
+ static QString getOpenFileName(
+ const QString &dirID, QWidget *parent = 0, const QString &caption = QString(),
+ const QString &dir = QString(), const QString &filter = QString(),
+ QString *selectedFilter = 0, QFileDialog::Options options = 0);
- static QString getExistingDirectory(const QString &dirID, QWidget *parent = 0, const QString &caption = QString(),
- const QString &dir = QString(),
- QFileDialog::Options options = QFileDialog::ShowDirsOnly);
+ static QString getExistingDirectory(
+ const QString &dirID, QWidget *parent = 0, const QString &caption = QString(),
+ const QString &dir = QString(),
+ QFileDialog::Options options = QFileDialog::ShowDirsOnly);
private:
+ std::map<QString, QString> m_Cache;
FileDialogMemory();
-
static FileDialogMemory &instance();
-
-private:
-
- std::map<QString, QString> m_Cache;
-
};
#endif // FILEDIALOGMEMORY_H
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 205ab7cc..4ba5e6ad 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -733,18 +733,15 @@ void MainWindow::updatePinnedExecutables()
bool hasLinks = false;
- std::vector<Executable>::iterator begin, end;
- m_OrganizerCore.executablesList()->getExecutables(begin, end);
-
- for (auto iter = begin; iter != end; ++iter) {
- if (iter->isShownOnToolbar()) {
+ for (const auto& exe : *m_OrganizerCore.executablesList()) {
+ if (exe.isShownOnToolbar()) {
hasLinks = true;
QAction *exeAction = new QAction(
- iconForExecutable(iter->m_BinaryInfo.filePath()), iter->m_Title);
+ iconForExecutable(exe.binaryInfo().filePath()), exe.title());
- exeAction->setObjectName(QString("custom__") + iter->m_Title);
- exeAction->setStatusTip(iter->m_BinaryInfo.filePath());
+ exeAction->setObjectName(QString("custom__") + exe.title());
+ exeAction->setStatusTip(exe.binaryInfo().filePath());
if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) {
qDebug("failed to connect trigger?");
@@ -1504,24 +1501,42 @@ void MainWindow::registerModPage(IPluginModPage *modPage)
void MainWindow::startExeAction()
{
QAction *action = qobject_cast<QAction*>(sender());
- if (action != nullptr) {
- const Executable &selectedExecutable(m_OrganizerCore.executablesList()->find(action->text()));
- QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString();
- auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.m_Title);
- if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.m_Title)) {
- forcedLibraries.clear();
- }
- m_OrganizerCore.spawnBinary(
- selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
- selectedExecutable.m_WorkingDirectory.length() != 0
- ? selectedExecutable.m_WorkingDirectory
- : selectedExecutable.m_BinaryInfo.absolutePath(),
- selectedExecutable.m_SteamAppID,
- customOverwrite,
- forcedLibraries);
- } else {
+
+ if (action == nullptr) {
qCritical("not an action?");
+ return;
+ }
+
+ const auto& list = *m_OrganizerCore.executablesList();
+
+ const auto title = action->text();
+ auto itor = list.find(title);
+
+ if (itor == list.end()) {
+ qWarning().nospace()
+ << "startExeAction(): executable '" << title << "' not found";
+
+ return;
+ }
+
+ const Executable& exe = *itor;
+ auto& profile = *m_OrganizerCore.currentProfile();
+
+ QString customOverwrite = profile.setting("custom_overwrites", exe.title()).toString();
+ auto forcedLibraries = profile.determineForcedLibraries(exe.title());
+
+ if (!profile.forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries.clear();
}
+
+ m_OrganizerCore.spawnBinary(
+ exe.binaryInfo(), exe.arguments(),
+ exe.workingDirectory().length() != 0
+ ? exe.workingDirectory()
+ : exe.binaryInfo().absolutePath(),
+ exe.steamAppID(),
+ customOverwrite,
+ forcedLibraries);
}
@@ -1789,12 +1804,12 @@ void MainWindow::refreshExecutablesList()
QAbstractItemModel *model = executablesList->model();
- std::vector<Executable>::const_iterator current, end;
- m_OrganizerCore.executablesList()->getExecutables(current, end);
- for(int i = 0; current != end; ++current, ++i) {
- QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath());
- executablesList->addItem(icon, current->m_Title);
+ int i = 0;
+ for (const auto& exe : *m_OrganizerCore.executablesList()) {
+ QIcon icon = iconForExecutable(exe.binaryInfo().filePath());
+ executablesList->addItem(icon, exe.title());
model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole);
+ ++i;
}
setExecutableIndex(1);
@@ -2359,17 +2374,17 @@ void MainWindow::on_startButton_clicked() {
ui->startButton->setEnabled(false);
try {
const Executable &selectedExecutable(getSelectedExecutable());
- QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString();
- auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.m_Title);
- if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.m_Title)) {
+ QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.title()).toString();
+ auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.title());
+ if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.title())) {
forcedLibraries.clear();
}
m_OrganizerCore.spawnBinary(
- selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
- selectedExecutable.m_WorkingDirectory.length() != 0
- ? selectedExecutable.m_WorkingDirectory
- : selectedExecutable.m_BinaryInfo.absolutePath(),
- selectedExecutable.m_SteamAppID,
+ selectedExecutable.binaryInfo(), selectedExecutable.arguments(),
+ selectedExecutable.workingDirectory().length() != 0
+ ? selectedExecutable.workingDirectory()
+ : selectedExecutable.binaryInfo().absolutePath(),
+ selectedExecutable.steamAppID(),
customOverwrite,
forcedLibraries);
} catch (...) {
@@ -2464,22 +2479,24 @@ static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments,
bool MainWindow::modifyExecutablesDialog()
{
bool result = false;
+
try {
- EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(),
- *m_OrganizerCore.modList(),
- m_OrganizerCore.currentProfile(),
- m_OrganizerCore.managedGame());
+ const auto oldExecutables = *m_OrganizerCore.executablesList();
+
+ EditExecutablesDialog dialog(m_OrganizerCore, this);
+
QSettings &settings = m_OrganizerCore.settings().directInterface();
QString key = QString("geometry/%1").arg(dialog.objectName());
+
if (settings.contains(key)) {
dialog.restoreGeometry(settings.value(key).toByteArray());
}
- if (dialog.exec() == QDialog::Accepted) {
- m_OrganizerCore.setExecutablesList(dialog.getExecutablesList());
- result = true;
- }
+
+ result = (dialog.exec() == QDialog::Accepted);
+
settings.setValue(key, dialog.saveGeometry());
refreshExecutablesList();
+ updatePinnedExecutables();
} catch (const std::exception &e) {
reportError(e.what());
}
@@ -5204,7 +5221,7 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos)
void MainWindow::linkToolbar()
{
Executable &exe(getSelectedExecutable());
- exe.showOnToolbar(!exe.isShownOnToolbar());
+ exe.setShownOnToolbar(!exe.isShownOnToolbar());
ui->linkButton->menu()->actions().at(static_cast<int>(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link"));
updatePinnedExecutables();
}
@@ -5212,7 +5229,7 @@ void MainWindow::linkToolbar()
namespace {
QString getLinkfile(const QString &dir, const Executable &exec)
{
- return QDir::fromNativeSeparators(dir) + "/" + exec.m_Title + ".lnk";
+ return QDir::fromNativeSeparators(dir) + "/" + exec.title() + ".lnk";
}
QString getDesktopLinkfile(const Executable &exec)
@@ -5241,12 +5258,12 @@ void MainWindow::addWindowsLink(const ShortcutType mapping)
} else {
QFileInfo const exeInfo(qApp->applicationFilePath());
// create link
- QString executable = QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath());
+ QString executable = QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath());
std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
std::wstring parameter = ToWString(
- QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.m_Title));
- std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.m_Title));
+ QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.title()));
+ std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.title()));
std::wstring iconFile = ToWString(executable);
std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath()));
@@ -5492,12 +5509,12 @@ void MainWindow::addAsExecutable()
if (!name.isEmpty()) {
//Note: If this already exists, you'll lose custom settings
- m_OrganizerCore.executablesList()->addExecutable(name,
- binaryInfo.absoluteFilePath(),
- arguments,
- targetInfo.absolutePath(),
- QString(),
- Executable::CustomExecutable);
+ m_OrganizerCore.executablesList()->setExecutable(Executable()
+ .title(name)
+ .binaryInfo(binaryInfo)
+ .arguments(arguments)
+ .workingDirectory(targetInfo.absolutePath()));
+
refreshExecutablesList();
}
@@ -6389,13 +6406,18 @@ void MainWindow::unlockESPIndex()
void MainWindow::removeFromToolbar()
{
- try {
- Executable &exe = m_OrganizerCore.executablesList()->find(m_ContextAction->text());
- exe.showOnToolbar(false);
- } catch (const std::runtime_error&) {
- qDebug("executable doesn't exist any more");
+ const auto& title = m_ContextAction->text();
+ auto& list = *m_OrganizerCore.executablesList();
+
+ auto itor = list.find(title);
+ if (itor == list.end()) {
+ qWarning().nospace()
+ << "removeFromToolbar(): executable '" << title << "' not found";
+
+ return;
}
+ itor->setShownOnToolbar(false);
updatePinnedExecutables();
}
@@ -6520,14 +6542,14 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index)
const Executable &MainWindow::getSelectedExecutable() const
{
- QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex());
- return m_OrganizerCore.executablesList()->find(name);
+ const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex());
+ return m_OrganizerCore.executablesList()->get(name);
}
Executable &MainWindow::getSelectedExecutable()
{
- QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex());
- return m_OrganizerCore.executablesList()->find(name);
+ const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex());
+ return m_OrganizerCore.executablesList()->get(name);
}
void MainWindow::on_linkButton_pressed()
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 88389738..b6283a26 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -33,7 +33,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
//Note the commented headers here can be replaced with forward references,
//when I get round to cleaning up main.cpp
-struct Executable;
+class Executable;
class CategoryFactory;
class LockedDialogBase;
class OrganizerCore;
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index 892162f6..c724e57f 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -365,34 +365,17 @@ QString OrganizerCore::commitSettings(const QString &iniFile)
QSettings::Status OrganizerCore::storeSettings(const QString &fileName)
{
QSettings settings(fileName, QSettings::IniFormat);
+
if (m_UserInterface != nullptr) {
m_UserInterface->storeSettings(settings);
}
+
if (m_CurrentProfile != nullptr) {
settings.setValue("selected_profile",
m_CurrentProfile->name().toUtf8().constData());
}
- settings.remove("customExecutables");
- settings.beginWriteArray("customExecutables");
- std::vector<Executable>::const_iterator current, end;
- m_ExecutablesList.getExecutables(current, end);
- int count = 0;
- for (; current != end; ++current) {
- const Executable &item = *current;
- settings.setArrayIndex(count++);
- settings.setValue("title", item.m_Title);
- settings.setValue("custom", item.isCustom());
- settings.setValue("toolbar", item.isShownOnToolbar());
- settings.setValue("ownicon", item.usesOwnIcon());
- if (item.isCustom()) {
- settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath());
- settings.setValue("arguments", item.m_Arguments);
- settings.setValue("workingDirectory", item.m_WorkingDirectory);
- settings.setValue("steamAppID", item.m_SteamAppID);
- }
- }
- settings.endArray();
+ m_ExecutablesList.store(settings);
FileDialogMemory::save(settings);
@@ -508,30 +491,7 @@ void OrganizerCore::updateExecutablesList(QSettings &settings)
return;
}
- m_ExecutablesList.init(managedGame());
-
- qDebug("setting up configured executables");
-
- int numCustomExecutables = settings.beginReadArray("customExecutables");
- for (int i = 0; i < numCustomExecutables; ++i) {
- settings.setArrayIndex(i);
-
- Executable::Flags flags;
- if (settings.value("custom", true).toBool())
- flags |= Executable::CustomExecutable;
- if (settings.value("toolbar", false).toBool())
- flags |= Executable::ShowInToolbar;
- if (settings.value("ownicon", false).toBool())
- flags |= Executable::UseApplicationIcon;
-
- m_ExecutablesList.addExecutable(
- settings.value("title").toString(), settings.value("binary").toString(),
- settings.value("arguments").toString(),
- settings.value("workingDirectory", "").toString(),
- settings.value("steamAppID", "").toString(), flags);
- }
-
- settings.endArray();
+ m_ExecutablesList.load(managedGame(), settings);
// TODO this has nothing to do with executables list move to an appropriate
// function!
@@ -1222,6 +1182,34 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const
return res;
}
+QString OrganizerCore::findJavaInstallation(const QString& jarFile)
+{
+ if (!jarFile.isEmpty()) {
+ // try to find java automatically based on the given jar file
+ std::wstring jarFileW = jarFile.toStdWString();
+
+ WCHAR buffer[MAX_PATH];
+ if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) {
+ DWORD binaryType = 0UL;
+ if (!::GetBinaryTypeW(buffer, &binaryType)) {
+ qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError());
+ } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) {
+ return QString::fromWCharArray(buffer);
+ }
+ }
+ }
+
+ // second attempt: look to the registry
+ QSettings reg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat);
+ if (reg.contains("CurrentVersion")) {
+ QString currentVersion = reg.value("CurrentVersion").toString();
+ return reg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe");
+ }
+
+ // not found
+ return {};
+}
+
bool OrganizerCore::getFileExecutionContext(
QWidget* parent, const QFileInfo &targetInfo,
QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type)
@@ -1239,44 +1227,22 @@ bool OrganizerCore::getFileExecutionContext(
type = FileExecutionTypes::Executable;
return true;
} else if (extension.compare("jar", Qt::CaseInsensitive) == 0) {
- // types that need to be injected into
- std::wstring targetPathW = targetInfo.absoluteFilePath().toStdWString();
- QString binaryPath;
+ auto java = findJavaInstallation(targetInfo.absoluteFilePath());
- { // try to find java automatically
- WCHAR buffer[MAX_PATH];
- if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) {
- DWORD binaryType = 0UL;
- if (!::GetBinaryTypeW(buffer, &binaryType)) {
- qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError());
- } else if (binaryType == SCS_32BIT_BINARY) {
- binaryPath = QString::fromWCharArray(buffer);
- }
- }
+ if (java.isEmpty()) {
+ java = QFileDialog::getOpenFileName(
+ parent, QObject::tr("Select binary"),
+ QString(), QObject::tr("Binary") + " (*.exe)");
}
- if (binaryPath.isEmpty() && (extension == "jar")) {
- // second attempt: look to the registry
- QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat);
- if (javaReg.contains("CurrentVersion")) {
- QString currentVersion = javaReg.value("CurrentVersion").toString();
- binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe");
- }
- }
- if (binaryPath.isEmpty()) {
- binaryPath = QFileDialog::getOpenFileName(
- parent, QObject::tr("Select binary"), QString(), QObject::tr("Binary") + " (*.exe)");
- }
- if (binaryPath.isEmpty()) {
+
+ if (java.isEmpty()) {
return false;
}
- binaryInfo = QFileInfo(binaryPath);
- if (extension == "jar") {
- arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
- } else {
- arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
- }
+ binaryInfo = QFileInfo(java);
+ arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
type = FileExecutionTypes::Executable;
+
return true;
} else {
type = FileExecutionTypes::Other;
@@ -1735,19 +1701,20 @@ HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut)
.arg(shortcut.instance(),shortcut.executable())
.toLocal8Bit().constData());
- Executable& exe = m_ExecutablesList.find(shortcut.executable());
+ const Executable& exe = m_ExecutablesList.get(shortcut.executable());
+
auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable());
if (!m_CurrentProfile->forcedLibrariesEnabled(shortcut.executable())) {
forcedLibaries.clear();
}
return spawnBinaryDirect(
- exe.m_BinaryInfo, exe.m_Arguments,
+ exe.binaryInfo(), exe.arguments(),
m_CurrentProfile->name(),
- exe.m_WorkingDirectory.length() != 0
- ? exe.m_WorkingDirectory
- : exe.m_BinaryInfo.absolutePath(),
- exe.m_SteamAppID,
+ exe.workingDirectory().length() != 0
+ ? exe.workingDirectory()
+ : exe.binaryInfo().absolutePath(),
+ exe.steamAppID(),
"",
forcedLibaries);
}
@@ -1786,13 +1753,13 @@ HANDLE OrganizerCore::startApplication(const QString &executable,
currentDirectory = binary.absolutePath();
}
try {
- const Executable &exe = m_ExecutablesList.findByBinary(binary);
- steamAppID = exe.m_SteamAppID;
+ const Executable &exe = m_ExecutablesList.getByBinary(binary);
+ steamAppID = exe.steamAppID();
customOverwrite
- = m_CurrentProfile->setting("custom_overwrites", exe.m_Title)
+ = m_CurrentProfile->setting("custom_overwrites", exe.title())
.toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.m_Title)) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.m_Title);
+ if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
}
} catch (const std::runtime_error &) {
// nop
@@ -1800,20 +1767,20 @@ HANDLE OrganizerCore::startApplication(const QString &executable,
} else {
// only a file name, search executables list
try {
- const Executable &exe = m_ExecutablesList.find(executable);
- steamAppID = exe.m_SteamAppID;
+ const Executable &exe = m_ExecutablesList.get(executable);
+ steamAppID = exe.steamAppID();
customOverwrite
- = m_CurrentProfile->setting("custom_overwrites", exe.m_Title)
+ = m_CurrentProfile->setting("custom_overwrites", exe.title())
.toString();
- if (m_CurrentProfile->forcedLibrariesEnabled(exe.m_Title)) {
- forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.m_Title);
+ if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) {
+ forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title());
}
if (arguments == "") {
- arguments = exe.m_Arguments;
+ arguments = exe.arguments();
}
- binary = exe.m_BinaryInfo;
+ binary = exe.binaryInfo();
if (cwd.length() == 0) {
- currentDirectory = exe.m_WorkingDirectory;
+ currentDirectory = exe.workingDirectory();
}
} catch (const std::runtime_error &) {
qWarning("\"%s\" not set up as executable",
diff --git a/src/organizercore.h b/src/organizercore.h
index 8ed34e24..a4a57496 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -147,6 +147,8 @@ public:
void doAfterLogin(const std::function<void()> &function) { m_PostLoginTasks.append(function); }
+ static QString findJavaInstallation(const QString& jarFile={});
+
static bool getFileExecutionContext(
QWidget* parent, const QFileInfo &targetInfo,
QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type);
diff --git a/src/pch.h b/src/pch.h
index e86c0da6..1d8df43a 100644
--- a/src/pch.h
+++ b/src/pch.h
@@ -13,6 +13,7 @@
#include <list>
#include <map>
#include <memory>
+#include <optional>
#include <regex>
#include <set>
#include <sstream>
diff --git a/src/profile.cpp b/src/profile.cpp
index 4ccaa641..ef387027 100644
--- a/src/profile.cpp
+++ b/src/profile.cpp
@@ -982,7 +982,7 @@ int Profile::getPriorityMinimum() const
return m_ModIndexByPriority.begin()->first;
}
-bool Profile::forcedLibrariesEnabled(const QString &executable)
+bool Profile::forcedLibrariesEnabled(const QString &executable) const
{
return setting("forced_libraries", executable + "/enabled", false).toBool();
}
@@ -992,7 +992,7 @@ void Profile::setForcedLibrariesEnabled(const QString &executable, bool enabled)
storeSetting("forced_libraries", executable + "/enabled", enabled);
}
-QList<ExecutableForcedLoadSetting> Profile::determineForcedLibraries(const QString &executable)
+QList<ExecutableForcedLoadSetting> Profile::determineForcedLibraries(const QString &executable) const
{
QList<ExecutableForcedLoadSetting> results;
diff --git a/src/profile.h b/src/profile.h
index a7ba7e91..bc7964f8 100644
--- a/src/profile.h
+++ b/src/profile.h
@@ -330,9 +330,9 @@ public:
int getPriorityMinimum() const;
- bool forcedLibrariesEnabled(const QString &executable);
+ bool forcedLibrariesEnabled(const QString &executable) const;
void setForcedLibrariesEnabled(const QString &executable, bool enabled);
- QList<MOBase::ExecutableForcedLoadSetting> determineForcedLibraries(const QString &executable);
+ QList<MOBase::ExecutableForcedLoadSetting> determineForcedLibraries(const QString &executable) const;
void storeForcedLibraries(const QString &executable, const QList<MOBase::ExecutableForcedLoadSetting> &values);
void removeForcedLibraries(const QString &executable);
diff --git a/src/resources/go-down.png b/src/resources/go-down.png
index af237881..bf0ce4fd 100644
--- a/src/resources/go-down.png
+++ b/src/resources/go-down.png
Binary files differ
diff --git a/src/resources/go-up.png b/src/resources/go-up.png
index b0a0cd72..a4b4e022 100644
--- a/src/resources/go-up.png
+++ b/src/resources/go-up.png
Binary files differ