From aa9a1fc07fb612547c1d1c5074d669b2dd258af9 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 13:04:27 -0500 Subject: Refactor obsolete methods --- src/browserdialog.cpp | 5 ++--- src/downloadmanager.cpp | 9 +++++++-- src/listdialog.cpp | 2 +- src/main.cpp | 4 ++-- src/moapplication.cpp | 2 +- src/modinfobackup.h | 2 ++ src/modinfodialogconflicts.cpp | 9 ++++----- src/modinfodialogfiletree.h | 3 +++ src/modinfodialognexus.h | 2 ++ src/modinfoforeign.cpp | 2 +- src/modinforegular.cpp | 2 +- src/modinfoseparator.h | 2 ++ src/modlist.cpp | 2 +- src/overwriteinfodialog.cpp | 1 - src/pluginlist.cpp | 2 +- src/previewgenerator.cpp | 5 +---- src/profile.cpp | 2 +- src/transfersavesdialog.cpp | 2 +- 18 files changed, 33 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index e186ad63..c34d211f 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -138,9 +138,8 @@ void BrowserDialog::maximizeWidth() int contentWidth = getCurrentView()->page()->contentsSize().width(); - QDesktopWidget screen; - int currentScreen = screen.screenNumber(this); - int screenWidth = screen.screenGeometry(currentScreen).size().width(); + QScreen* screen = this->window()->windowHandle()->screen(); + int screenWidth = screen->geometry().size().width(); int targetWidth = std::min(std::max(viewportWidth, contentWidth) + frameWidth, screenWidth); this->resize(targetWidth, height()); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 648b102a..ec1faed4 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -717,7 +717,7 @@ void DownloadManager::refreshAlphabeticalTranslation() m_AlphabeticalTranslation.push_back(pos); } - qSort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), LessThanWrapper(this)); + std::sort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), LessThanWrapper(this)); } @@ -1155,7 +1155,12 @@ QDateTime DownloadManager::getFileTime(int index) const DownloadInfo *info = m_ActiveDownloads.at(index); if (!info->m_Created.isValid()) { - info->m_Created = QFileInfo(info->m_Output).created(); + QFileInfo fileInfo(info->m_Output); + info->m_Created = fileInfo.birthTime(); + if (!info->m_Created.isValid()) + info->m_Created = fileInfo.metadataChangeTime(); + if (!info->m_Created.isValid()) + info->m_Created = fileInfo.lastModified(); } return info->m_Created; diff --git a/src/listdialog.cpp b/src/listdialog.cpp index 52bba80d..b9857070 100644 --- a/src/listdialog.cpp +++ b/src/listdialog.cpp @@ -61,7 +61,7 @@ void ListDialog::on_filterEdit_textChanged(QString filter) if (newChoices.length() == 1) { QListWidgetItem *item = ui->choiceList->item(0); - ui->choiceList->setItemSelected(item, true); + item->setSelected(true); ui->choiceList->setCurrentItem(item); } diff --git a/src/main.cpp b/src/main.cpp index c6c87a64..b83e581b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -646,8 +646,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, const int monitor = settings.value("window_monitor").toInt(); if (monitor != -1) { - QDesktopWidget* desktop = QApplication::desktop(); - const QPoint center = desktop->availableGeometry(monitor).center(); + QGuiApplication::screens().at(monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); splash.move(center - splash.rect().center()); } } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 5652833a..e07db437 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -50,7 +50,7 @@ public: if(element == QStyle::PE_IndicatorItemViewItemDrop) { painter->setRenderHint(QPainter::Antialiasing, true); - QColor col(option->palette.foreground().color()); + QColor col(option->palette.windowText().color()); QPen pen(col); pen.setWidth(2); col.setAlpha(50); diff --git a/src/modinfobackup.h b/src/modinfobackup.h index cab613c9..393c2e38 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -6,6 +6,8 @@ class ModInfoBackup : public ModInfoRegular { + Q_OBJECT + friend class ModInfo; public: diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 511d48ad..6dd1fe4e 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -7,7 +7,6 @@ using namespace MOShared; using namespace MOBase; -namespace shell = MOBase::shell; // if there are more than 50 selected items in the conflict tree, don't bother // checking whether menu items apply to them, just show all of them @@ -935,7 +934,7 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); return ConflictItem( - ToQString(altString), std::move(relativeName), QString::null, index, + ToQString(altString), std::move(relativeName), QString(), index, std::move(fileName), true, std::move(origin), archive); } @@ -943,8 +942,8 @@ ConflictItem GeneralConflictsTab::createNoConflictItem( FileEntry::Index index, bool archive, QString fileName, QString relativeName) { return ConflictItem( - QString::null, std::move(relativeName), QString::null, index, - std::move(fileName), false, QString::null, archive); + QString(), std::move(relativeName), QString(), index, + std::move(fileName), false, QString(), archive); } ConflictItem GeneralConflictsTab::createOverwrittenItem( @@ -958,7 +957,7 @@ ConflictItem GeneralConflictsTab::createOverwrittenItem( QString altOrigin = after; return ConflictItem( - QString::null, std::move(relativeName), std::move(after), + QString(), std::move(relativeName), std::move(after), index, std::move(fileName), true, std::move(altOrigin), archive); } diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index f9fa62d4..42773899 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -2,9 +2,12 @@ #define MODINFODIALOGFILETREE_H #include "modinfodialogtab.h" +#include class FileTreeTab : public ModInfoDialogTab { + Q_OBJECT; + public: FileTreeTab(ModInfoDialogTabContext cx); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 7f894dbf..1cfa2057 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -32,6 +32,8 @@ signals: class NexusTab : public ModInfoDialogTab { + Q_OBJECT; + public: NexusTab(ModInfoDialogTabContext cx); diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp index b599d4eb..7312d5b7 100644 --- a/src/modinfoforeign.cpp +++ b/src/modinfoforeign.cpp @@ -55,7 +55,7 @@ ModInfoForeign::ModInfoForeign(const QString &modName, PluginContainer *pluginContainer) : ModInfoWithConflictInfo(pluginContainer, directoryStructure), m_ReferenceFile(referenceFile), m_Archives(archives) { - m_CreationTime = QFileInfo(referenceFile).created(); + m_CreationTime = QFileInfo(referenceFile).birthTime(); switch (modType) { case ModInfo::EModType::MOD_DLC: m_Name = tr("DLC: ") + modName; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 448447e1..12137bcb 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -39,7 +39,7 @@ ModInfoRegular::ModInfoRegular(PluginContainer *pluginContainer, const IPluginGa , m_NexusBridge(pluginContainer) { testValid(); - m_CreationTime = QFileInfo(path.absolutePath()).created(); + m_CreationTime = QFileInfo(path.absolutePath()).birthTime(); // read out the meta-file for information readMeta(); if (m_GameName.compare(game->gameShortName(), Qt::CaseInsensitive) != 0) diff --git a/src/modinfoseparator.h b/src/modinfoseparator.h index 3560d786..4b1e5217 100644 --- a/src/modinfoseparator.h +++ b/src/modinfoseparator.h @@ -6,6 +6,8 @@ class ModInfoSeparator: public ModInfoRegular { + Q_OBJECT; + friend class ModInfo; public: diff --git a/src/modlist.cpp b/src/modlist.cpp index e2398cd6..abe4fd5d 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1322,7 +1322,7 @@ bool ModList::moveSelection(QAbstractItemView *itemView, int direction) QModelIndexList rows = selectionModel->selectedRows(); if (direction > 0) { for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); + rows.swapItemsAt(i, rows.size() - i - 1); } } for (QModelIndex idx : rows) { diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 0a884ac9..a9ef8bef 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -32,7 +32,6 @@ using namespace MOBase; class MyFileSystemModel : public QFileSystemModel { - public: MyFileSystemModel(QObject *parent) : QFileSystemModel(parent), m_RegularColumnCount(0) {} diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 2edb92f5..85160a88 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1304,7 +1304,7 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) } if (keyEvent->key() == Qt::Key_Down) { for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); + rows.swapItemsAt(i, rows.size() - i - 1); } } for (QModelIndex idx : rows) { diff --git a/src/previewgenerator.cpp b/src/previewgenerator.cpp index 280d69aa..f317393e 100644 --- a/src/previewgenerator.cpp +++ b/src/previewgenerator.cpp @@ -22,14 +22,11 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include PreviewGenerator::PreviewGenerator() { - - QDesktopWidget desk; - m_MaxSize = desk.screenGeometry().size() * 0.8; + m_MaxSize = QGuiApplication::primaryScreen()->size() * 0.8; } void PreviewGenerator::registerPlugin(MOBase::IPluginPreview *plugin) diff --git a/src/profile.cpp b/src/profile.cpp index ef387027..4ac15333 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -899,7 +899,7 @@ void Profile::rename(const QString &newName) { QDir profileDir(Settings::instance().getProfileDirectory()); profileDir.rename(name(), newName); - m_Directory = profileDir.absoluteFilePath(newName); + m_Directory.setPath(profileDir.absoluteFilePath(newName)); } QVariant Profile::setting(const QString §ion, const QString &name, diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index 130df14f..451f56d4 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -61,7 +61,7 @@ public: virtual QDateTime getCreationTime() const override { - return QFileInfo(m_File).created(); + return QFileInfo(m_File).birthTime(); } virtual QString getSaveGroupIdentifier() const override -- cgit v1.3.1 From 3a2f056e8adf07f31ea1f6b2856a6d57da1f4777 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 14:12:38 -0500 Subject: More refactoring --- src/browserdialog.cpp | 1 - src/main.cpp | 5 ++++- src/mainwindow.cpp | 7 ++++--- src/modinfodialogconflicts.cpp | 2 +- src/pch.h | 1 - 5 files changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index c34d211f..70190433 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -35,7 +35,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include diff --git a/src/main.cpp b/src/main.cpp index b83e581b..1e60483f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -645,10 +645,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (settings.contains("window_monitor")) { const int monitor = settings.value("window_monitor").toInt(); - if (monitor != -1) { + if (monitor != -1 && QGuiApplication::screens().size() > monitor) { QGuiApplication::screens().at(monitor)->geometry().center(); const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); splash.move(center - splash.rect().center()); + } else { + const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); + splash.move(center - splash.rect().center()); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7681b482..9726b27b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -99,7 +99,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -1340,7 +1339,7 @@ void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) } m_CurrentSaveView->setSave(save); - QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); + QRect screenRect = m_CurrentSaveView->window()->windowHandle()->screen()->geometry(); QPoint pos = QCursor::pos(); if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { @@ -2279,7 +2278,9 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("menubar_visible", m_menuBarVisible); settings.setValue("statusbar_visible", m_statusBarVisible); settings.setValue("window_split", ui->splitter->saveState()); - settings.setValue("window_monitor", QApplication::desktop()->screenNumber(this)); + QScreen *screen = this->window()->windowHandle()->screen(); + int screenId = QGuiApplication::screens().indexOf(screen); + settings.setValue("window_monitor", screenId); settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 6dd1fe4e..698c8534 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -1224,5 +1224,5 @@ std::optional AdvancedConflictsTab::createItem( return ConflictItem( std::move(beforeQS), std::move(relativeName), std::move(afterQS), - index, std::move(fileName), hasAlts, QString::null, archive); + index, std::move(fileName), hasAlts, QString(), archive); } diff --git a/src/pch.h b/src/pch.h index 1d8df43a..9640b09d 100644 --- a/src/pch.h +++ b/src/pch.h @@ -91,7 +91,6 @@ #include #include #include -#include #include #include #include -- cgit v1.3.1 From 7879ea2c623521115eafeef4ad788eef8c18cc2c Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 4 Jul 2019 15:09:49 -0500 Subject: Fix issue if widget isn't on a window --- src/mainwindow.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9726b27b..6c6eeee7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1339,7 +1339,12 @@ void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) } m_CurrentSaveView->setSave(save); - QRect screenRect = m_CurrentSaveView->window()->windowHandle()->screen()->geometry(); + QWindow *window = m_CurrentSaveView->window()->windowHandle(); + QRect screenRect; + if (window == nullptr) + screenRect = QGuiApplication::primaryScreen()->geometry(); + else + screenRect = window->screen()->geometry(); QPoint pos = QCursor::pos(); if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { -- cgit v1.3.1 From 724945ab33864c1fd8d3162bdd85ca66f8bb311d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 7 Jul 2019 16:29:11 -0400 Subject: removed useless validationFailed() callback in MainWindow moved the log that was in it to organizer core --- src/mainwindow.cpp | 6 ------ src/mainwindow.h | 2 -- src/organizercore.cpp | 2 ++ 3 files changed, 2 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 451ec688..9586cb93 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -399,7 +399,6 @@ MainWindow::MainWindow(QSettings &initSettings connect(NexusInterface::instance(&pluginContainer), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); connect(NexusInterface::instance(&pluginContainer), SIGNAL(nxmDownloadURLsAvailable(QString,int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(QString,int,int,QVariant,QVariant,int))); connect(NexusInterface::instance(&pluginContainer), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusApi())); - connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(validateFailed(QString)), this, SLOT(validationFailed(QString))); connect( NexusInterface::instance(&pluginContainer)->getAccessManager(), @@ -3095,11 +3094,6 @@ void MainWindow::untrack_clicked() }); } -void MainWindow::validationFailed(const QString &error) -{ - qDebug("Nexus API validation failed: %s", qUtf8Printable(error)); -} - void MainWindow::windowTutorialFinished(const QString &windowName) { m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); diff --git a/src/mainwindow.h b/src/mainwindow.h index eee269cf..80508787 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -510,8 +510,6 @@ private slots: // nexus related void checkModsForUpdates(); - void validationFailed(const QString &message); - void linkClicked(const QString &url); void updateAvailable(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 81ec7f43..65c8eb81 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2484,6 +2484,8 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) void OrganizerCore::loginFailed(const QString &message) { + qDebug("Nexus API validation failed: %s", qUtf8Printable(message)); + if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), tr("Login failed, try again?")) == QMessageBox::Yes) { -- cgit v1.3.1 From c38e864c92b7958099d74048fdfc88a5d3b40dcc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 7 Jul 2019 17:48:48 -0400 Subject: set the progress dialog's parent to main window as soon as it's available --- src/main.cpp | 3 +++ src/nxmaccessmanager.cpp | 37 ++++++++++++++++++++++++++++++++----- src/nxmaccessmanager.h | 5 ++++- 3 files changed, 39 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index f2571685..0b078f03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -697,6 +697,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, // set up main window and its data structures MainWindow mainWindow(settings, organizer, pluginContainer); + NexusInterface::instance(&pluginContainer) + ->getAccessManager()->setTopLevelWidget(&mainWindow); + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index ee6c03f1..5c72d3e2 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -47,7 +47,9 @@ namespace { NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) + , m_TopLevel(nullptr) , m_ValidateReply(nullptr) + , m_ProgressDialog(nullptr) , m_MOVersion(moVersion) { m_ValidateTimeout.setSingleShot(true); @@ -70,6 +72,20 @@ NXMAccessManager::~NXMAccessManager() } } +void NXMAccessManager::setTopLevelWidget(QWidget* w) +{ + m_TopLevel = w; + + if (m_ProgressDialog) { + const auto wasVisible = m_ProgressDialog->isVisible(); + + m_ProgressDialog->hide(); + m_ProgressDialog->setParent(w, m_ProgressDialog->windowFlags() | Qt::Dialog); + m_ProgressDialog->setModal(false); + m_ProgressDialog->setVisible(wasVisible); + } +} + QNetworkReply *NXMAccessManager::createRequest( QNetworkAccessManager::Operation operation, const QNetworkRequest &request, QIODevice *device) @@ -124,7 +140,11 @@ void NXMAccessManager::startValidationCheck() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", m_MOVersion.toUtf8()); - m_ProgressDialog = new QProgressDialog(nullptr); + if (!m_ProgressDialog) { + m_ProgressDialog = new QProgressDialog(m_TopLevel); + m_ProgressDialog->setModal(false); + } + m_ProgressDialog->setLabelText(tr("Validating Nexus Connection")); QList buttons = m_ProgressDialog->findChildren(); buttons.at(0)->setEnabled(false); @@ -142,16 +162,23 @@ void NXMAccessManager::startValidationCheck() bool NXMAccessManager::validated() const { if (m_ValidateState == VALIDATE_CHECKING) { - QProgressDialog progress; - progress.setLabelText(tr("Validating Nexus Connection")); + if (!m_ProgressDialog) { + m_ProgressDialog = new QProgressDialog(m_TopLevel); + m_ProgressDialog->setModal(false); + } + + m_ProgressDialog->setLabelText(tr("Validating Nexus Connection")); QList buttons = m_ProgressDialog->findChildren(); buttons.at(0)->setEnabled(false); - progress.show(); + m_ProgressDialog->show(); while (m_ValidateState == VALIDATE_CHECKING) { QCoreApplication::processEvents(); QThread::msleep(100); } - progress.hide(); + + m_ProgressDialog->hide(); + m_ProgressDialog->deleteLater(); + m_ProgressDialog = nullptr; } return m_ValidateState == VALIDATE_VALID; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 1bdeae40..16dde612 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -41,6 +41,8 @@ public: ~NXMAccessManager(); + void setTopLevelWidget(QWidget* w); + bool validated() const; bool validateAttempted() const; @@ -94,9 +96,10 @@ protected: QIODevice *device); private: + QWidget* m_TopLevel; QTimer m_ValidateTimeout; QNetworkReply *m_ValidateReply; - QProgressDialog *m_ProgressDialog { nullptr }; + mutable QProgressDialog* m_ProgressDialog; QString m_MOVersion; -- cgit v1.3.1 From ea7ad772bbd971e398869d9b74b38d62c814bb02 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 7 Jul 2019 18:07:10 -0400 Subject: moved progress dialog to its own class --- src/nxmaccessmanager.cpp | 111 ++++++++++++++++++++++++++--------------------- src/nxmaccessmanager.h | 22 +++++++++- 2 files changed, 82 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 5c72d3e2..4d9b2a92 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -41,19 +41,64 @@ along with Mod Organizer. If not, see . using namespace MOBase; -namespace { - QString const nexusBaseUrl("https://api.nexusmods.com/v1"); +const QString NexusBaseUrl("https://api.nexusmods.com/v1"); +const std::chrono::seconds ValidationTimeout(10); + + +ValidationProgressDialog::ValidationProgressDialog() + : m_dialogHolder(new QDialog), m_dialog(nullptr), m_bar(nullptr) +{ + m_dialog = m_dialogHolder.get(); + m_bar = new QProgressBar; + + auto* label = new QLabel(tr("Validating Nexus Connection")); + label->setAlignment(Qt::AlignHCenter); + + auto* vbox = new QVBoxLayout(m_dialog); + vbox->addWidget(label); + vbox->addWidget(m_bar); + + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); + connect(buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); + vbox->addWidget(buttons); +} + +void ValidationProgressDialog::setParentWidget(QWidget* w) +{ + // will be deleted by the parent + m_dialogHolder.release(); + + const auto wasVisible = m_dialog->isVisible(); + + m_dialog->hide(); + m_dialog->setParent(w, m_dialog->windowFlags() | Qt::Dialog); + m_dialog->setModal(false); + m_dialog->setVisible(wasVisible); +} + +void ValidationProgressDialog::show() +{ + m_dialog->show(); +} + +void ValidationProgressDialog::hide() +{ + m_dialog->hide(); +} + +void ValidationProgressDialog::onButton(QAbstractButton* b) +{ } + NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) - , m_TopLevel(nullptr) , m_ValidateReply(nullptr) - , m_ProgressDialog(nullptr) , m_MOVersion(moVersion) { m_ValidateTimeout.setSingleShot(true); - m_ValidateTimeout.setInterval(30000); + m_ValidateTimeout.setInterval(ValidationTimeout); + connect(&m_ValidateTimeout, SIGNAL(timeout()), this, SLOT(validateTimeout())); setCookieJar(new PersistentCookieJar( QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); @@ -74,16 +119,7 @@ NXMAccessManager::~NXMAccessManager() void NXMAccessManager::setTopLevelWidget(QWidget* w) { - m_TopLevel = w; - - if (m_ProgressDialog) { - const auto wasVisible = m_ProgressDialog->isVisible(); - - m_ProgressDialog->hide(); - m_ProgressDialog->setParent(w, m_ProgressDialog->windowFlags() | Qt::Dialog); - m_ProgressDialog->setModal(false); - m_ProgressDialog->setVisible(wasVisible); - } + m_ProgressDialog.setParentWidget(w); } QNetworkReply *NXMAccessManager::createRequest( @@ -109,7 +145,7 @@ QNetworkReply *NXMAccessManager::createRequest( void NXMAccessManager::showCookies() const { - QUrl url(nexusBaseUrl + "/"); + QUrl url(NexusBaseUrl + "/"); for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { qDebug("%s - %s (expires: %s)", cookie.name().constData(), cookie.value().constData(), @@ -130,7 +166,7 @@ void NXMAccessManager::clearCookies() void NXMAccessManager::startValidationCheck() { qDebug("Checking Nexus API Key..."); - QString requestString = nexusBaseUrl + "/users/validate"; + QString requestString = NexusBaseUrl + "/users/validate"; QNetworkRequest request(requestString); request.setRawHeader("APIKEY", m_ApiKey.toUtf8()); @@ -140,15 +176,8 @@ void NXMAccessManager::startValidationCheck() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", m_MOVersion.toUtf8()); - if (!m_ProgressDialog) { - m_ProgressDialog = new QProgressDialog(m_TopLevel); - m_ProgressDialog->setModal(false); - } + m_ProgressDialog.show(); - m_ProgressDialog->setLabelText(tr("Validating Nexus Connection")); - QList buttons = m_ProgressDialog->findChildren(); - buttons.at(0)->setEnabled(false); - m_ProgressDialog->show(); QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback m_ValidateReply = get(request); @@ -162,23 +191,14 @@ void NXMAccessManager::startValidationCheck() bool NXMAccessManager::validated() const { if (m_ValidateState == VALIDATE_CHECKING) { - if (!m_ProgressDialog) { - m_ProgressDialog = new QProgressDialog(m_TopLevel); - m_ProgressDialog->setModal(false); - } + m_ProgressDialog.show(); - m_ProgressDialog->setLabelText(tr("Validating Nexus Connection")); - QList buttons = m_ProgressDialog->findChildren(); - buttons.at(0)->setEnabled(false); - m_ProgressDialog->show(); while (m_ValidateState == VALIDATE_CHECKING) { QCoreApplication::processEvents(); QThread::msleep(100); } - m_ProgressDialog->hide(); - m_ProgressDialog->deleteLater(); - m_ProgressDialog = nullptr; + m_ProgressDialog.hide(); } return m_ValidateState == VALIDATE_VALID; @@ -257,11 +277,8 @@ void NXMAccessManager::clearApiKey() void NXMAccessManager::validateTimeout() { m_ValidateTimeout.stop(); - if (m_ProgressDialog != nullptr) { - m_ProgressDialog->hide(); - m_ProgressDialog->deleteLater(); - m_ProgressDialog = nullptr; - } + m_ProgressDialog.hide(); + m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; @@ -277,11 +294,8 @@ void NXMAccessManager::validateTimeout() void NXMAccessManager::validateError(QNetworkReply::NetworkError) { m_ValidateTimeout.stop(); - if (m_ProgressDialog != nullptr) { - m_ProgressDialog->hide(); - m_ProgressDialog->deleteLater(); - m_ProgressDialog = nullptr; - } + m_ProgressDialog.hide(); + m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; @@ -300,10 +314,7 @@ void NXMAccessManager::validateError(QNetworkReply::NetworkError) void NXMAccessManager::validateFinished() { m_ValidateTimeout.stop(); - if (m_ProgressDialog != nullptr) { - m_ProgressDialog->deleteLater(); - m_ProgressDialog = nullptr; - } + m_ProgressDialog.hide(); if (m_ValidateReply != nullptr) { QJsonDocument jdoc = QJsonDocument::fromJson(m_ValidateReply->readAll()); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 16dde612..89438316 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -29,6 +29,26 @@ along with Mod Organizer. If not, see . namespace MOBase { class IPluginGame; } +class ValidationProgressDialog : QObject +{ + Q_OBJECT; + +public: + ValidationProgressDialog(); + + void setParentWidget(QWidget* w); + void show(); + void hide(); + +private: + std::unique_ptr m_dialogHolder; + QDialog* m_dialog; + QProgressBar* m_bar; + + void onButton(QAbstractButton* b); +}; + + /** * @brief access manager extended to handle nxm links **/ @@ -99,7 +119,7 @@ private: QWidget* m_TopLevel; QTimer m_ValidateTimeout; QNetworkReply *m_ValidateReply; - mutable QProgressDialog* m_ProgressDialog; + mutable ValidationProgressDialog m_ProgressDialog; QString m_MOVersion; -- cgit v1.3.1 From 89d0528a002a3cd7f0db47b2db53bdaf0e9c494d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 7 Jul 2019 18:22:36 -0400 Subject: progress dialog now shows elapsed time in progress bar --- src/nxmaccessmanager.cpp | 43 ++++++++++++++++++++++++++++++++----------- src/nxmaccessmanager.h | 11 ++++++++--- src/pch.h | 1 + 3 files changed, 41 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 4d9b2a92..d6f6290e 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -40,16 +40,20 @@ along with Mod Organizer. If not, see . #include using namespace MOBase; +using namespace std::chrono_literals; const QString NexusBaseUrl("https://api.nexusmods.com/v1"); -const std::chrono::seconds ValidationTimeout(10); +const auto ValidationTimeout = 10s; -ValidationProgressDialog::ValidationProgressDialog() - : m_dialogHolder(new QDialog), m_dialog(nullptr), m_bar(nullptr) +ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) + : m_timeout(t), m_dialog(nullptr), m_bar(nullptr), m_timer(nullptr) { + m_dialogHolder.reset(new QDialog); m_dialog = m_dialogHolder.get(); + m_bar = new QProgressBar; + m_bar->setTextVisible(false); auto* label = new QLabel(tr("Validating Nexus Connection")); label->setAlignment(Qt::AlignHCenter); @@ -76,13 +80,24 @@ void ValidationProgressDialog::setParentWidget(QWidget* w) m_dialog->setVisible(wasVisible); } -void ValidationProgressDialog::show() +void ValidationProgressDialog::start() { + if (!m_timer) { + m_timer = new QTimer(m_dialog); + connect(m_timer, &QTimer::timeout, [&]{ onTimer(); }); + m_timer->setInterval(100ms); + } + + m_bar->setRange(0, m_timeout.count()); + m_elapsed.start(); + m_timer->start(); + m_dialog->show(); } -void ValidationProgressDialog::hide() +void ValidationProgressDialog::stop() { + m_timer->stop(); m_dialog->hide(); } @@ -90,10 +105,16 @@ void ValidationProgressDialog::onButton(QAbstractButton* b) { } +void ValidationProgressDialog::onTimer() +{ + m_bar->setValue(m_elapsed.elapsed() / 1000); +} + NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) , m_ValidateReply(nullptr) + , m_ProgressDialog(ValidationTimeout) , m_MOVersion(moVersion) { m_ValidateTimeout.setSingleShot(true); @@ -176,7 +197,7 @@ void NXMAccessManager::startValidationCheck() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", m_MOVersion.toUtf8()); - m_ProgressDialog.show(); + m_ProgressDialog.start(); QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback @@ -191,14 +212,14 @@ void NXMAccessManager::startValidationCheck() bool NXMAccessManager::validated() const { if (m_ValidateState == VALIDATE_CHECKING) { - m_ProgressDialog.show(); + m_ProgressDialog.start(); while (m_ValidateState == VALIDATE_CHECKING) { QCoreApplication::processEvents(); QThread::msleep(100); } - m_ProgressDialog.hide(); + m_ProgressDialog.stop(); } return m_ValidateState == VALIDATE_VALID; @@ -277,7 +298,7 @@ void NXMAccessManager::clearApiKey() void NXMAccessManager::validateTimeout() { m_ValidateTimeout.stop(); - m_ProgressDialog.hide(); + m_ProgressDialog.stop(); m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; @@ -294,7 +315,7 @@ void NXMAccessManager::validateTimeout() void NXMAccessManager::validateError(QNetworkReply::NetworkError) { m_ValidateTimeout.stop(); - m_ProgressDialog.hide(); + m_ProgressDialog.stop(); m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; @@ -314,7 +335,7 @@ void NXMAccessManager::validateError(QNetworkReply::NetworkError) void NXMAccessManager::validateFinished() { m_ValidateTimeout.stop(); - m_ProgressDialog.hide(); + m_ProgressDialog.stop(); if (m_ValidateReply != nullptr) { QJsonDocument jdoc = QJsonDocument::fromJson(m_ValidateReply->readAll()); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 89438316..b20cb6e8 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include namespace MOBase { class IPluginGame; } @@ -34,18 +35,22 @@ class ValidationProgressDialog : QObject Q_OBJECT; public: - ValidationProgressDialog(); + ValidationProgressDialog(std::chrono::seconds timeout); void setParentWidget(QWidget* w); - void show(); - void hide(); + void start(); + void stop(); private: + std::chrono::seconds m_timeout; std::unique_ptr m_dialogHolder; QDialog* m_dialog; QProgressBar* m_bar; + QTimer* m_timer; + QElapsedTimer m_elapsed; void onButton(QAbstractButton* b); + void onTimer(); }; diff --git a/src/pch.h b/src/pch.h index 1d8df43a..955939ba 100644 --- a/src/pch.h +++ b/src/pch.h @@ -98,6 +98,7 @@ #include #include #include +#include #include #include #include -- cgit v1.3.1 From d9003f9e5407396fdd552c5df3d0be76ab9a16a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 7 Jul 2019 19:00:34 -0400 Subject: made ValidationProgressDialog a QDialog to handle close events allow hiding dialog and make it appear again if needed don't use an event loop in validated(), it causes all sorts of problems because it can be reentrant if another logged in action is done while the dialog is visible --- src/nxmaccessmanager.cpp | 58 +++++++++++++++++++++++++----------------------- src/nxmaccessmanager.h | 12 +++++++--- 2 files changed, 39 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index d6f6290e..a68dea54 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -46,63 +46,72 @@ const QString NexusBaseUrl("https://api.nexusmods.com/v1"); const auto ValidationTimeout = 10s; -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) - : m_timeout(t), m_dialog(nullptr), m_bar(nullptr), m_timer(nullptr) +ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : + m_timeout(t), m_bar(nullptr), m_buttons(nullptr), + m_timer(nullptr) { - m_dialogHolder.reset(new QDialog); - m_dialog = m_dialogHolder.get(); - m_bar = new QProgressBar; m_bar->setTextVisible(false); auto* label = new QLabel(tr("Validating Nexus Connection")); label->setAlignment(Qt::AlignHCenter); - auto* vbox = new QVBoxLayout(m_dialog); + auto* vbox = new QVBoxLayout(this); vbox->addWidget(label); vbox->addWidget(m_bar); - auto* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); - connect(buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); - vbox->addWidget(buttons); + m_buttons = new QDialogButtonBox; + m_buttons->addButton(tr("Hide"), QDialogButtonBox::RejectRole); + connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); + vbox->addWidget(m_buttons); } void ValidationProgressDialog::setParentWidget(QWidget* w) { - // will be deleted by the parent - m_dialogHolder.release(); - - const auto wasVisible = m_dialog->isVisible(); + const auto wasVisible = isVisible(); - m_dialog->hide(); - m_dialog->setParent(w, m_dialog->windowFlags() | Qt::Dialog); - m_dialog->setModal(false); - m_dialog->setVisible(wasVisible); + hide(); + setParent(w, windowFlags() | Qt::Dialog); + setModal(false); + setVisible(wasVisible); } void ValidationProgressDialog::start() { if (!m_timer) { - m_timer = new QTimer(m_dialog); + m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, [&]{ onTimer(); }); m_timer->setInterval(100ms); } m_bar->setRange(0, m_timeout.count()); + m_bar->setValue(0); + m_elapsed.start(); m_timer->start(); - m_dialog->show(); + show(); } void ValidationProgressDialog::stop() { m_timer->stop(); - m_dialog->hide(); + hide(); +} + +void ValidationProgressDialog::closeEvent(QCloseEvent* e) +{ + hide(); + e->ignore(); } void ValidationProgressDialog::onButton(QAbstractButton* b) { + if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { + hide(); + } else { + qCritical() << "validation dialog: unknown button pressed"; + } } void ValidationProgressDialog::onTimer() @@ -212,14 +221,7 @@ void NXMAccessManager::startValidationCheck() bool NXMAccessManager::validated() const { if (m_ValidateState == VALIDATE_CHECKING) { - m_ProgressDialog.start(); - - while (m_ValidateState == VALIDATE_CHECKING) { - QCoreApplication::processEvents(); - QThread::msleep(100); - } - - m_ProgressDialog.stop(); + m_ProgressDialog.show(); } return m_ValidateState == VALIDATE_VALID; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index b20cb6e8..4467ea8d 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -26,11 +26,12 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include namespace MOBase { class IPluginGame; } -class ValidationProgressDialog : QObject +class ValidationProgressDialog : private QDialog { Q_OBJECT; @@ -38,14 +39,19 @@ public: ValidationProgressDialog(std::chrono::seconds timeout); void setParentWidget(QWidget* w); + void start(); void stop(); + using QDialog::show; + +protected: + void closeEvent(QCloseEvent* e) override; + private: std::chrono::seconds m_timeout; - std::unique_ptr m_dialogHolder; - QDialog* m_dialog; QProgressBar* m_bar; + QDialogButtonBox* m_buttons; QTimer* m_timer; QElapsedTimer m_elapsed; -- cgit v1.3.1 From fa6602816160f6ac959527ce843dee22b1272603 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 9 Jul 2019 00:47:29 -0400 Subject: dialog should be on the heap, gets deleted by the main window --- src/nxmaccessmanager.cpp | 19 +++++++++---------- src/nxmaccessmanager.h | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index a68dea54..cabe07a9 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -46,9 +46,8 @@ const QString NexusBaseUrl("https://api.nexusmods.com/v1"); const auto ValidationTimeout = 10s; -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : - m_timeout(t), m_bar(nullptr), m_buttons(nullptr), - m_timer(nullptr) +ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) + : m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr) { m_bar = new QProgressBar; m_bar->setTextVisible(false); @@ -123,7 +122,7 @@ void ValidationProgressDialog::onTimer() NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) , m_ValidateReply(nullptr) - , m_ProgressDialog(ValidationTimeout) + , m_ProgressDialog(new ValidationProgressDialog(ValidationTimeout)) , m_MOVersion(moVersion) { m_ValidateTimeout.setSingleShot(true); @@ -149,7 +148,7 @@ NXMAccessManager::~NXMAccessManager() void NXMAccessManager::setTopLevelWidget(QWidget* w) { - m_ProgressDialog.setParentWidget(w); + m_ProgressDialog->setParentWidget(w); } QNetworkReply *NXMAccessManager::createRequest( @@ -206,7 +205,7 @@ void NXMAccessManager::startValidationCheck() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", m_MOVersion.toUtf8()); - m_ProgressDialog.start(); + m_ProgressDialog->start(); QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback @@ -221,7 +220,7 @@ void NXMAccessManager::startValidationCheck() bool NXMAccessManager::validated() const { if (m_ValidateState == VALIDATE_CHECKING) { - m_ProgressDialog.show(); + m_ProgressDialog->show(); } return m_ValidateState == VALIDATE_VALID; @@ -300,7 +299,7 @@ void NXMAccessManager::clearApiKey() void NXMAccessManager::validateTimeout() { m_ValidateTimeout.stop(); - m_ProgressDialog.stop(); + m_ProgressDialog->stop(); m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; @@ -317,7 +316,7 @@ void NXMAccessManager::validateTimeout() void NXMAccessManager::validateError(QNetworkReply::NetworkError) { m_ValidateTimeout.stop(); - m_ProgressDialog.stop(); + m_ProgressDialog->stop(); m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; @@ -337,7 +336,7 @@ void NXMAccessManager::validateError(QNetworkReply::NetworkError) void NXMAccessManager::validateFinished() { m_ValidateTimeout.stop(); - m_ProgressDialog.stop(); + m_ProgressDialog->stop(); if (m_ValidateReply != nullptr) { QJsonDocument jdoc = QJsonDocument::fromJson(m_ValidateReply->readAll()); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 4467ea8d..f53f6648 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -130,7 +130,7 @@ private: QWidget* m_TopLevel; QTimer m_ValidateTimeout; QNetworkReply *m_ValidateReply; - mutable ValidationProgressDialog m_ProgressDialog; + mutable ValidationProgressDialog* m_ProgressDialog; QString m_MOVersion; -- cgit v1.3.1 From f44663d795b5ba4adb9b11474f21337de12a9af5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 10 Jul 2019 21:43:31 -0400 Subject: handle context menu on pending downloads --- src/downloadlistwidget.cpp | 69 ++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index c75ecdd2..e2a6f321 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -201,40 +201,49 @@ void DownloadListWidget::onCustomContextMenu(const QPoint &point) QModelIndex index = indexAt(point); bool hidden = false; - if (index.row() >= 0) { - m_ContextRow = qobject_cast(model())->mapToSource(index).row(); - DownloadManager::DownloadState state = m_Manager->getState(m_ContextRow); - hidden = m_Manager->isHidden(m_ContextRow); - - if (state >= DownloadManager::STATE_READY) { - menu.addAction(tr("Install"), this, SLOT(issueInstall())); - if (m_Manager->isInfoIncomplete(m_ContextRow)) - menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfoMd5())); - else - menu.addAction(tr("Visit on Nexus"), this, SLOT(issueVisitOnNexus())); - menu.addAction(tr("Open File"), this, SLOT(issueOpenFile())); - menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); + try + { + if (index.row() >= 0) { + m_ContextRow = qobject_cast(model())->mapToSource(index).row(); + DownloadManager::DownloadState state = m_Manager->getState(m_ContextRow); + + hidden = m_Manager->isHidden(m_ContextRow); + + if (state >= DownloadManager::STATE_READY) { + menu.addAction(tr("Install"), this, SLOT(issueInstall())); + if (m_Manager->isInfoIncomplete(m_ContextRow)) + menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfoMd5())); + else + menu.addAction(tr("Visit on Nexus"), this, SLOT(issueVisitOnNexus())); + menu.addAction(tr("Open File"), this, SLOT(issueOpenFile())); + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); - menu.addSeparator(); + menu.addSeparator(); - menu.addAction(tr("Delete"), this, SLOT(issueDelete())); - if (hidden) - menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); - else - menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView())); - } else if (state == DownloadManager::STATE_DOWNLOADING) { - menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); - menu.addAction(tr("Pause"), this, SLOT(issuePause())); - menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); - } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) - || (state == DownloadManager::STATE_PAUSING)) { - menu.addAction(tr("Delete"), this, SLOT(issueDelete())); - menu.addAction(tr("Resume"), this, SLOT(issueResume())); - menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); - } + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); + if (hidden) + menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); + else + menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView())); + } else if (state == DownloadManager::STATE_DOWNLOADING) { + menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); + menu.addAction(tr("Pause"), this, SLOT(issuePause())); + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); + } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) + || (state == DownloadManager::STATE_PAUSING)) { + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); + menu.addAction(tr("Resume"), this, SLOT(issueResume())); + menu.addAction(tr("Show in Folder"), this, SLOT(issueOpenInDownloadsFolder())); + } - menu.addSeparator(); + menu.addSeparator(); + } + } catch(std::exception&) + { + // this happens when the download index is not found, ignore it and don't + // display download-specific actions } + menu.addAction(tr("Delete Installed Downloads..."), this, SLOT(issueDeleteCompleted())); menu.addAction(tr("Delete Uninstalled Downloads..."), this, SLOT(issueDeleteUninstalled())); menu.addAction(tr("Delete All Downloads..."), this, SLOT(issueDeleteAll())); -- cgit v1.3.1 From 5e5681709ab878f3aa6cf1344af44e8ae9544987 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 11 Jul 2019 01:55:31 -0400 Subject: reworked the nexus connection panel moved most of the stuff into a new NexusSSOLogin class --- src/nxmaccessmanager.cpp | 2 +- src/nxmaccessmanager.h | 1 + src/settingsdialog.cpp | 321 +++++++++++++++++++++++++++++++------------ src/settingsdialog.h | 62 +++++++-- src/settingsdialog.ui | 351 ++++++++++++++++++++++++++++++++--------------- 5 files changed, 531 insertions(+), 206 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index cabe07a9..8bbfd536 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -43,7 +43,7 @@ using namespace MOBase; using namespace std::chrono_literals; const QString NexusBaseUrl("https://api.nexusmods.com/v1"); -const auto ValidationTimeout = 10s; +const std::chrono::seconds NXMAccessManager::ValidationTimeout = 10s; ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index f53f6648..cbec0530 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -67,6 +67,7 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: + static const std::chrono::seconds ValidationTimeout; explicit NXMAccessManager(QObject *parent, const QString &moVersion); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 95e4ceb0..d1ace6a5 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -49,6 +49,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; +const QString NexusSSO("wss://sso.nexusmods.com"); +const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); class NexusManualKeyDialog : public QDialog { @@ -98,35 +100,177 @@ private: }; + +NexusSSOLogin::NexusSSOLogin() + : m_keyReceived(false), m_active(false) +{ + QObject::connect( + &m_socket, &QWebSocket::connected, + [&]{ onConnected(); }); + + QObject::connect( + &m_socket, qOverload(&QWebSocket::error), + [&](auto&& e){ onError(e); }); + + QObject::connect( + &m_socket, &QWebSocket::textMessageReceived, + [&](auto&& s){ onMessage(s); }); + + QObject::connect( + &m_socket, &QWebSocket::disconnected, + [&]{ onDisconnected(); }); + + QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); +} + +void NexusSSOLogin::start() +{ + m_active = true; + setState(ConnectingToSSO); + m_timeout.start(NXMAccessManager::ValidationTimeout); + m_socket.open(NexusSSO); +} + +void NexusSSOLogin::cancel() +{ + abort(); + setState(Cancelled); +} + +void NexusSSOLogin::close() +{ + m_active = false; + m_timeout.stop(); + m_socket.close(); +} + +void NexusSSOLogin::abort() +{ + m_active = false; + m_timeout.stop(); + m_socket.abort(); +} + +bool NexusSSOLogin::isActive() const +{ + return m_active; +} + +void NexusSSOLogin::setState(States s, const QString& error) +{ + if (stateChanged) { + stateChanged(s, error); + } +} + +void NexusSSOLogin::onConnected() +{ + setState(WaitingForToken); + + m_keyReceived = false; + + //if (m_guid.isEmpty()) { + boost::uuids::random_generator generator; + boost::uuids::uuid sessionId = generator(); + m_guid = boost::uuids::to_string(sessionId).c_str(); + //} + + QJsonObject data; + data.insert(QString("id"), QJsonValue(m_guid)); + //data.insert(QString("token"), QJsonValue(m_token)); + data.insert(QString("protocol"), 2); + + const QString message = QJsonDocument(data).toJson(); + m_socket.sendTextMessage(message); +} + +void NexusSSOLogin::onMessage(const QString& s) +{ + const QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8()); + const QVariantMap root = doc.object().toVariantMap(); + + if (!root["success"].toBool()) { + close(); + + setState(Error, QString("There was a problem with SSO initialization: %1") + .arg(root["error"].toString())); + + return; + } + + const QVariantMap data = root["data"].toMap(); + + if (data.contains("connection_token")) { + // first answer + m_token = data["connection_token"].toString(); + + // open browser + const auto url = NexusSSOPage.arg(m_guid); + shell::OpenLink(url); + + m_timeout.stop(); + setState(WaitingForBrowser); + } else { + // second answer + const auto key = data["api_key"].toString(); + close(); + + if (keyChanged) { + keyChanged(key); + } + + setState(Finished); + } +} + +void NexusSSOLogin::onDisconnected() +{ + if (m_active) { + m_active = false; + + if (!m_keyReceived) { + setState(ClosedByRemote); + } + } +} + +void NexusSSOLogin::onError(QAbstractSocket::SocketError e) +{ + if (m_active) { + setState(Error, m_socket.errorString()); + } +} + +void NexusSSOLogin::onTimeout() +{ + abort(); + setState(Timeout); +} + + SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) , m_PluginContainer(pluginContainer) - , m_nexusLogin(new QWebSocket) - , m_KeyReceived(false) - , m_KeyCleared(false) + , m_keyChanged(false) , m_GeometriesReset(false) { + m_nexusLogin.keyChanged = [&](auto&& s){ onKeyChanged(s); }; + m_nexusLogin.stateChanged = [&](auto&& s, auto&& e){ onStateChanged(s, e); }; + ui->setupUi(this); ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); - connect(m_nexusLogin, SIGNAL(connected()), this, SLOT(dispatchLogin())); - connect(m_nexusLogin, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(authError(QAbstractSocket::SocketError))); - connect(m_nexusLogin, SIGNAL(textMessageReceived(const QString &)), this, SLOT(receiveApiKey(const QString &))); - connect(m_nexusLogin, SIGNAL(disconnected()), this, SLOT(completeApiConnection())); - m_loginTimer.callOnTimeout(this, &SettingsDialog::loginPing); updateNexusButtons(); } SettingsDialog::~SettingsDialog() { - m_loginTimer.stop(); - m_nexusLogin->close(); disconnect(this); delete ui; } @@ -188,7 +332,7 @@ bool SettingsDialog::getResetGeometries() bool SettingsDialog::getApiKeyChanged() { - return m_KeyReceived || m_KeyCleared; + return m_keyChanged; } void SettingsDialog::on_categoriesBtn_clicked() @@ -391,7 +535,11 @@ void SettingsDialog::on_resetDialogsButton_clicked() void SettingsDialog::on_nexusConnect_clicked() { - fetchNexusApiKey(); + if (m_nexusLogin.isActive()) { + m_nexusLogin.cancel(); + } else { + fetchNexusApiKey(); + } } void SettingsDialog::on_nexusManualKey_clicked() @@ -415,92 +563,95 @@ void SettingsDialog::on_nexusManualKey_clicked() void SettingsDialog::fetchNexusApiKey() { - QUrl url = QUrl("wss://sso.nexusmods.com"); - m_nexusLogin->open(url); + ui->nexusLog->clear(); + m_nexusLogin.start(); updateNexusButtons(); } -void SettingsDialog::dispatchLogin() +void SettingsDialog::onKeyChanged(const QString& key) { - m_KeyReceived = false; - QJsonObject login; - if (m_UUID.isEmpty()) { - boost::uuids::random_generator generator; - boost::uuids::uuid sessionId = generator(); - m_UUID = boost::uuids::to_string(sessionId).c_str(); + if (key.isEmpty()) { + clearKey(); + } else { + setKey(key); } - login.insert(QString("id"), QJsonValue(m_UUID)); - login.insert(QString("token"), QJsonValue(m_AuthToken)); - login.insert(QString("protocol"), 2); - QJsonDocument loginDoc(login); - QString finalMessage(loginDoc.toJson()); - m_nexusLogin->sendTextMessage(finalMessage); - QDesktopServices::openUrl(QUrl(QString("https://www.nexusmods.com/sso?id=%1&application=%2").arg(m_UUID).arg("modorganizer2"))); - m_loginTimer.start(30000); } -void SettingsDialog::loginPing() +void SettingsDialog::onStateChanged(NexusSSOLogin::States s, const QString& e) { - if (m_nexusLogin->isValid()) { - m_nexusLogin->ping(); - m_totalPings++; - } - if (m_totalPings >= 60) { - m_loginTimer.stop(); - m_totalPings = 0; - m_nexusLogin->close(QWebSocketProtocol::CloseCodeGoingAway, "Timeout: No response received after thirty minutes. Cancelling request."); - } -} + QString log; -void SettingsDialog::authError(QAbstractSocket::SocketError error) -{ - auto errorInfo = m_nexusLogin->errorString(); - qCritical() << "An error occurred: " << errorInfo; -} + switch (s) + { + case NexusSSOLogin::Idle: + { + break; + } -void SettingsDialog::receiveApiKey(const QString &response) -{ - QJsonDocument responseDoc = QJsonDocument::fromJson(response.toUtf8()); - QVariantMap responseData = responseDoc.object().toVariantMap(); - if (responseData["success"].toBool()) { - QVariantMap data = responseData["data"].toMap(); - if (data.contains("connection_token")) { - m_AuthToken = data["connection_token"].toString(); - } else { - const auto key = data["api_key"].toString(); + case NexusSSOLogin::ConnectingToSSO: + { + log = tr("Connecting to Nexus..."); + break; + } + + case NexusSSOLogin::WaitingForToken: + { + log = tr("Waiting for Nexus..."); + break; + } - m_nexusLogin->close(); - m_loginTimer.stop(); - m_totalPings = 0; + case NexusSSOLogin::WaitingForBrowser: + { + log = tr("Opened browser, waiting for user..."); + break; + } - if (key.isEmpty()) { - clearKey(); - } else { - setKey(key); - } + case NexusSSOLogin::Finished: + { + log = tr("Connected."); + break; } - } else { - QString error("There was a problem with SSO initialization: %1"); - qCritical() << error.arg(responseData["error"].toString()); - m_nexusLogin->close(); - } -} -void SettingsDialog::completeApiConnection() -{ - if (!m_KeyReceived && !m_loginTimer.isActive()) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), - tr("Failed to retrieve a Nexus API key! Please try again. " - "A browser window should open asking you to authorize.")); + case NexusSSOLogin::Timeout: + { + log = QObject::tr( + "No answer from Nexus.\n" + "A firewall might be blocking Mod Organizer."); - // try again - fetchNexusApiKey(); + break; + } + + case NexusSSOLogin::ClosedByRemote: + { + log = QObject::tr("Nexus closed the connection."); + break; + } + + case NexusSSOLogin::Cancelled: + { + log = QObject::tr("Cancelled."); + break; + } + + case NexusSSOLogin::Error: + { + log = tr("Error: %1.").arg(e); + break; + } } + + if (!log.isEmpty()) { + for (auto&& line : log.split("\n")) { + ui->nexusLog->addItem(line); + } + } + + updateNexusButtons(); } bool SettingsDialog::setKey(const QString& key) { - m_KeyReceived = true; + m_keyChanged = true; const bool ret = m_settings->setNexusApiKey(key); updateNexusButtons(); return ret; @@ -508,7 +659,7 @@ bool SettingsDialog::setKey(const QString& key) bool SettingsDialog::clearKey() { - m_KeyCleared = true; + m_keyChanged = true; const auto ret = m_settings->clearNexusApiKey(); updateNexusButtons(); @@ -530,22 +681,22 @@ void SettingsDialog::testApiKey() void SettingsDialog::updateNexusButtons() { - if (m_nexusLogin->state() != QAbstractSocket::UnconnectedState) { + if (m_nexusLogin.isActive()) { // api key is in the process of being retrieved - ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 30 minutes."); - ui->nexusConnect->setEnabled(false); + ui->nexusConnect->setText(tr("Cancel")); + ui->nexusConnect->setEnabled(true); ui->nexusDisconnect->setEnabled(false); ui->nexusManualKey->setEnabled(false); } else if (m_settings->hasNexusApiKey()) { // api key is present - ui->nexusConnect->setText("Nexus API Key Stored"); + ui->nexusConnect->setText(tr("Connect to Nexus")); ui->nexusConnect->setEnabled(false); ui->nexusDisconnect->setEnabled(true); ui->nexusManualKey->setEnabled(false); } else { // api key not present - ui->nexusConnect->setText("Connect to Nexus"); + ui->nexusConnect->setText(tr("Connect to Nexus")); ui->nexusConnect->setEnabled(true); ui->nexusDisconnect->setEnabled(false); ui->nexusManualKey->setEnabled(true); @@ -622,6 +773,8 @@ void SettingsDialog::on_clearCacheButton_clicked() void SettingsDialog::on_nexusDisconnect_clicked() { clearKey(); + ui->nexusLog->clear(); + ui->nexusLog->addItem(tr("Disconnected.")); } void SettingsDialog::normalizePath(QLineEdit *lineEdit) diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 858a36d4..aee447d7 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -35,6 +35,52 @@ namespace Ui { class SettingsDialog; } +class NexusSSOLogin +{ +public: + enum States + { + Idle, + ConnectingToSSO, + WaitingForToken, + WaitingForBrowser, + Finished, + Timeout, + ClosedByRemote, + Cancelled, + Error + }; + + std::function keyChanged; + std::function stateChanged; + + NexusSSOLogin(); + + void start(); + void cancel(); + + bool isActive() const; + +private: + QWebSocket m_socket; + QString m_guid; + bool m_keyReceived; + QString m_token; + bool m_active; + QTimer m_timeout; + + void setState(States s, const QString& error={}); + + void close(); + void abort(); + + void onConnected(); + void onMessage(const QString& s); + void onDisconnected(); + void onError(QAbstractSocket::SocketError e); + void onTimeout(); +}; + /** * dialog used to change settings for Mod Organizer. On top of the * settings managed by the "Settings" class, this offers a button to open the @@ -127,11 +173,6 @@ private slots: void on_resetGeometryBtn_clicked(); void deleteBlacklistItem(); - void dispatchLogin(); - void loginPing(); - void authError(QAbstractSocket::SocketError error); - void receiveApiKey(const QString &apiKey); - void completeApiConnection(); private: Ui::SettingsDialog *ui; @@ -145,16 +186,11 @@ private: QColor m_ContainsColor; QColor m_ContainedColor; - bool m_KeyReceived; - bool m_KeyCleared; bool m_GeometriesReset; - QString m_UUID; - QString m_AuthToken; + bool m_keyChanged; QString m_ExecutableBlacklist; - QWebSocket *m_nexusLogin; - QTimer m_loginTimer; - int m_totalPings = 0; + NexusSSOLogin m_nexusLogin; bool setKey(const QString& key); bool clearKey(); @@ -162,6 +198,8 @@ private: void fetchNexusApiKey(); void testApiKey(); + void onKeyChanged(const QString& key); + void onStateChanged(NexusSSOLogin::States s, const QString& e); }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index faaf1653..dfbde943 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -451,111 +451,244 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Nexus - + - - - Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things. - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things. Clicking &quot;Connect to Nexus&quot; will open a Nexus webpage to authorise Mod Organizer. You will need to be logged into your Nexus account. The authorisation is stored in the Windows Credential Manager. Your Nexus username and password are not required or stored by Mod Organizer.</p></body></html> - + - Nexus + Nexus Connection - + - - - - - Connect to Nexus - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Connect to Nexus + + + + + + + Manually enter the API key and try to login + + + Enter API Key Manually + + + + + + + Clear the stored Nexus API key and force reauthorization. + + + Disconnect from Nexus + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - Manually enter the API key and try to login - - - Enter API Key Manually - - - - - - - Clear the stored Nexus API key and force reauthorization. - - - Disconnect from Nexus - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - - - - Remove cache and cookies. - - - Clear Cache - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractScrollArea::AdjustToContents + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Nexus Account + + + + + + User ID + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + id + + + + + + + Username + + + + + + + username + + + + + + + Account + + + + + + + account + + + + + + + + + + Statistics + + + + + + Daily requests + + + + + + + Hourly requests + + + + + + + hourly requests + + + + + + + Requests queued + + + + + + + queued + + + + + + + daily requests + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + @@ -618,6 +751,20 @@ p, li { white-space: pre-wrap; } + + + + Remove cache and cookies. + + + Clear Cache + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + @@ -682,19 +829,6 @@ p, li { white-space: pre-wrap; } - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -1359,7 +1493,6 @@ programs you are intentionally running. browseProfilesDirBtn overwriteDirEdit browseOverwriteDirBtn - clearCacheButton associateButton knownServersList preferredServersList -- cgit v1.3.1 From ad29525e982f83343dbd70e17b914e0a30adb662 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 11 Jul 2019 02:31:59 -0400 Subject: moved NexusSSOLogin to nxmaccessmanager added logs for manual key validation, will need to rewrite a few things --- src/nxmaccessmanager.cpp | 166 +++++++++++++++++++++++++++++++++++++-- src/nxmaccessmanager.h | 64 ++++++++++++++- src/settingsdialog.cpp | 198 +++++++---------------------------------------- src/settingsdialog.h | 52 +------------ 4 files changed, 250 insertions(+), 230 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 8bbfd536..a274261d 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -44,6 +44,8 @@ using namespace std::chrono_literals; const QString NexusBaseUrl("https://api.nexusmods.com/v1"); const std::chrono::seconds NXMAccessManager::ValidationTimeout = 10s; +const QString NexusSSO("wss://sso.nexusmods.com"); +const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) @@ -94,7 +96,10 @@ void ValidationProgressDialog::start() void ValidationProgressDialog::stop() { - m_timer->stop(); + if (m_timer) { + m_timer->stop(); + } + hide(); } @@ -119,6 +124,153 @@ void ValidationProgressDialog::onTimer() } +NexusSSOLogin::NexusSSOLogin() + : m_keyReceived(false), m_active(false) +{ + QObject::connect( + &m_socket, &QWebSocket::connected, + [&]{ onConnected(); }); + + QObject::connect( + &m_socket, qOverload(&QWebSocket::error), + [&](auto&& e){ onError(e); }); + + QObject::connect( + &m_socket, &QWebSocket::textMessageReceived, + [&](auto&& s){ onMessage(s); }); + + QObject::connect( + &m_socket, &QWebSocket::disconnected, + [&]{ onDisconnected(); }); + + QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); +} + +void NexusSSOLogin::start() +{ + m_active = true; + setState(ConnectingToSSO); + m_timeout.start(NXMAccessManager::ValidationTimeout); + m_socket.open(NexusSSO); +} + +void NexusSSOLogin::cancel() +{ + abort(); + setState(Cancelled); +} + +void NexusSSOLogin::close() +{ + m_active = false; + m_timeout.stop(); + m_socket.close(); +} + +void NexusSSOLogin::abort() +{ + m_active = false; + m_timeout.stop(); + m_socket.abort(); +} + +bool NexusSSOLogin::isActive() const +{ + return m_active; +} + +void NexusSSOLogin::setState(States s, const QString& error) +{ + if (stateChanged) { + stateChanged(s, error); + } +} + +void NexusSSOLogin::onConnected() +{ + setState(WaitingForToken); + + m_keyReceived = false; + + //if (m_guid.isEmpty()) { + boost::uuids::random_generator generator; + boost::uuids::uuid sessionId = generator(); + m_guid = boost::uuids::to_string(sessionId).c_str(); + //} + + QJsonObject data; + data.insert(QString("id"), QJsonValue(m_guid)); + //data.insert(QString("token"), QJsonValue(m_token)); + data.insert(QString("protocol"), 2); + + const QString message = QJsonDocument(data).toJson(); + m_socket.sendTextMessage(message); +} + +void NexusSSOLogin::onMessage(const QString& s) +{ + const QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8()); + const QVariantMap root = doc.object().toVariantMap(); + + if (!root["success"].toBool()) { + close(); + + setState(Error, QString("There was a problem with SSO initialization: %1") + .arg(root["error"].toString())); + + return; + } + + const QVariantMap data = root["data"].toMap(); + + if (data.contains("connection_token")) { + // first answer + m_token = data["connection_token"].toString(); + + // open browser + const auto url = NexusSSOPage.arg(m_guid); + shell::OpenLink(url); + + m_timeout.stop(); + setState(WaitingForBrowser); + } else { + // second answer + const auto key = data["api_key"].toString(); + close(); + + if (keyChanged) { + keyChanged(key); + } + + setState(Finished); + } +} + +void NexusSSOLogin::onDisconnected() +{ + if (m_active) { + m_active = false; + + if (!m_keyReceived) { + setState(ClosedByRemote); + } + } +} + +void NexusSSOLogin::onError(QAbstractSocket::SocketError e) +{ + if (m_active) { + setState(Error, m_socket.errorString()); + } +} + +void NexusSSOLogin::onTimeout() +{ + abort(); + setState(Timeout); +} + + NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) , m_ValidateReply(nullptr) @@ -192,7 +344,7 @@ void NXMAccessManager::clearCookies() } } -void NXMAccessManager::startValidationCheck() +void NXMAccessManager::startValidationCheck(bool showProgress) { qDebug("Checking Nexus API Key..."); QString requestString = NexusBaseUrl + "/users/validate"; @@ -205,7 +357,9 @@ void NXMAccessManager::startValidationCheck() request.setRawHeader("Application-Name", "MO2"); request.setRawHeader("Application-Version", m_MOVersion.toUtf8()); - m_ProgressDialog->start(); + if (showProgress) { + m_ProgressDialog->start(); + } QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback @@ -245,13 +399,13 @@ bool NXMAccessManager::validateWaiting() const } -void NXMAccessManager::apiCheck(const QString &apiKey, bool force) +void NXMAccessManager::apiCheck(const QString &apiKey, ApiCheckFlags flags) { if (m_ValidateReply != nullptr) { return; } - if (force) { + if (flags & Force) { m_ValidateState = VALIDATE_NOT_CHECKED; } @@ -261,7 +415,7 @@ void NXMAccessManager::apiCheck(const QString &apiKey, bool force) } m_ApiKey = apiKey; - startValidationCheck(); + startValidationCheck((flags & HideProgress) == 0); } diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index cbec0530..08a799f9 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include namespace MOBase { class IPluginGame; } @@ -60,6 +61,52 @@ private: }; +class NexusSSOLogin +{ +public: + enum States + { + ConnectingToSSO, + WaitingForToken, + WaitingForBrowser, + Finished, + Timeout, + ClosedByRemote, + Cancelled, + Error + }; + + std::function keyChanged; + std::function stateChanged; + + NexusSSOLogin(); + + void start(); + void cancel(); + + bool isActive() const; + +private: + QWebSocket m_socket; + QString m_guid; + bool m_keyReceived; + QString m_token; + bool m_active; + QTimer m_timeout; + + void setState(States s, const QString& error={}); + + void close(); + void abort(); + + void onConnected(); + void onMessage(const QString& s); + void onDisconnected(); + void onError(QAbstractSocket::SocketError e); + void onTimeout(); +}; + + /** * @brief access manager extended to handle nxm links **/ @@ -67,6 +114,15 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: + enum ApiCheckFlagsEnum + { + NoFlags = 0, + Force, + HideProgress + }; + + Q_DECLARE_FLAGS(ApiCheckFlags, ApiCheckFlagsEnum) + static const std::chrono::seconds ValidationTimeout; explicit NXMAccessManager(QObject *parent, const QString &moVersion); @@ -80,7 +136,7 @@ public: bool validateAttempted() const; bool validateWaiting() const; - void apiCheck(const QString &apiKey, bool force=false); + void apiCheck(const QString &apiKey, ApiCheckFlags flags=NoFlags); void showCookies() const; @@ -91,8 +147,6 @@ public: QString apiKey() const; void clearApiKey(); - void startValidationCheck(); - void refuseValidation(); signals: @@ -145,6 +199,10 @@ private: VALIDATE_REFUSED, VALIDATE_VALID } m_ValidateState = VALIDATE_NOT_CHECKED; + + void startValidationCheck(bool showProgress); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(NXMAccessManager::ApiCheckFlags); + #endif // NXMACCESSMANAGER_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index d1ace6a5..0131d20b 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -28,7 +28,6 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "instancemanager.h" #include "nexusinterface.h" -#include "nxmaccessmanager.h" #include "plugincontainer.h" #include @@ -49,9 +48,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; -const QString NexusSSO("wss://sso.nexusmods.com"); -const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); - class NexusManualKeyDialog : public QDialog { public: @@ -100,154 +96,6 @@ private: }; - -NexusSSOLogin::NexusSSOLogin() - : m_keyReceived(false), m_active(false) -{ - QObject::connect( - &m_socket, &QWebSocket::connected, - [&]{ onConnected(); }); - - QObject::connect( - &m_socket, qOverload(&QWebSocket::error), - [&](auto&& e){ onError(e); }); - - QObject::connect( - &m_socket, &QWebSocket::textMessageReceived, - [&](auto&& s){ onMessage(s); }); - - QObject::connect( - &m_socket, &QWebSocket::disconnected, - [&]{ onDisconnected(); }); - - QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); -} - -void NexusSSOLogin::start() -{ - m_active = true; - setState(ConnectingToSSO); - m_timeout.start(NXMAccessManager::ValidationTimeout); - m_socket.open(NexusSSO); -} - -void NexusSSOLogin::cancel() -{ - abort(); - setState(Cancelled); -} - -void NexusSSOLogin::close() -{ - m_active = false; - m_timeout.stop(); - m_socket.close(); -} - -void NexusSSOLogin::abort() -{ - m_active = false; - m_timeout.stop(); - m_socket.abort(); -} - -bool NexusSSOLogin::isActive() const -{ - return m_active; -} - -void NexusSSOLogin::setState(States s, const QString& error) -{ - if (stateChanged) { - stateChanged(s, error); - } -} - -void NexusSSOLogin::onConnected() -{ - setState(WaitingForToken); - - m_keyReceived = false; - - //if (m_guid.isEmpty()) { - boost::uuids::random_generator generator; - boost::uuids::uuid sessionId = generator(); - m_guid = boost::uuids::to_string(sessionId).c_str(); - //} - - QJsonObject data; - data.insert(QString("id"), QJsonValue(m_guid)); - //data.insert(QString("token"), QJsonValue(m_token)); - data.insert(QString("protocol"), 2); - - const QString message = QJsonDocument(data).toJson(); - m_socket.sendTextMessage(message); -} - -void NexusSSOLogin::onMessage(const QString& s) -{ - const QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8()); - const QVariantMap root = doc.object().toVariantMap(); - - if (!root["success"].toBool()) { - close(); - - setState(Error, QString("There was a problem with SSO initialization: %1") - .arg(root["error"].toString())); - - return; - } - - const QVariantMap data = root["data"].toMap(); - - if (data.contains("connection_token")) { - // first answer - m_token = data["connection_token"].toString(); - - // open browser - const auto url = NexusSSOPage.arg(m_guid); - shell::OpenLink(url); - - m_timeout.stop(); - setState(WaitingForBrowser); - } else { - // second answer - const auto key = data["api_key"].toString(); - close(); - - if (keyChanged) { - keyChanged(key); - } - - setState(Finished); - } -} - -void NexusSSOLogin::onDisconnected() -{ - if (m_active) { - m_active = false; - - if (!m_keyReceived) { - setState(ClosedByRemote); - } - } -} - -void NexusSSOLogin::onError(QAbstractSocket::SocketError e) -{ - if (m_active) { - setState(Error, m_socket.errorString()); - } -} - -void NexusSSOLogin::onTimeout() -{ - abort(); - setState(Timeout); -} - - SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) @@ -259,11 +107,22 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti m_nexusLogin.keyChanged = [&](auto&& s){ onKeyChanged(s); }; m_nexusLogin.stateChanged = [&](auto&& s, auto&& e){ onStateChanged(s, e); }; + connect( + NexusInterface::instance(m_PluginContainer)->getAccessManager(), + &NXMAccessManager::validateSuccessful, + [&]{ onManualKeyValidation(true, ""); }); + + connect( + NexusInterface::instance(m_PluginContainer)->getAccessManager(), + &NXMAccessManager::validateFailed, + [&](auto&& e){ onManualKeyValidation(false, e); }); + + ui->setupUi(this); ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); - QShortcut *delShortcut - = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + QShortcut *delShortcut = new QShortcut( + QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); updateNexusButtons(); @@ -556,7 +415,11 @@ void SettingsDialog::on_nexusManualKey_clicked() clearKey(); } else { if (setKey(key)) { - testApiKey(); + ui->nexusLog->clear(); + ui->nexusLog->addItem(tr("Checking API key...")); + + NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck( + key, NXMAccessManager::Force | NXMAccessManager::HideProgress); } } } @@ -583,11 +446,6 @@ void SettingsDialog::onStateChanged(NexusSSOLogin::States s, const QString& e) switch (s) { - case NexusSSOLogin::Idle: - { - break; - } - case NexusSSOLogin::ConnectingToSSO: { log = tr("Connecting to Nexus..."); @@ -649,6 +507,15 @@ void SettingsDialog::onStateChanged(NexusSSOLogin::States s, const QString& e) updateNexusButtons(); } +void SettingsDialog::onManualKeyValidation(bool success, const QString& e) +{ + if (success) { + ui->nexusLog->addItem("Connected."); + } else { + ui->nexusLog->addItem("Error: " + e); + } +} + bool SettingsDialog::setKey(const QString& key) { m_keyChanged = true; @@ -668,17 +535,6 @@ bool SettingsDialog::clearKey() return ret; } -void SettingsDialog::testApiKey() -{ - QString key; - if (!m_settings->getNexusApiKey(key)) { - qWarning().nospace() << "can't test API key, nothing stored"; - return; - } - - NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(key, true); -} - void SettingsDialog::updateNexusButtons() { if (m_nexusLogin.isActive()) { diff --git a/src/settingsdialog.h b/src/settingsdialog.h index aee447d7..507214dd 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,12 +21,9 @@ along with Mod Organizer. If not, see . #define SETTINGSDIALOG_H #include "tutorabledialog.h" +#include "nxmaccessmanager.h" #include -#include -#include -#include #include -#include class PluginContainer; class Settings; @@ -35,51 +32,6 @@ namespace Ui { class SettingsDialog; } -class NexusSSOLogin -{ -public: - enum States - { - Idle, - ConnectingToSSO, - WaitingForToken, - WaitingForBrowser, - Finished, - Timeout, - ClosedByRemote, - Cancelled, - Error - }; - - std::function keyChanged; - std::function stateChanged; - - NexusSSOLogin(); - - void start(); - void cancel(); - - bool isActive() const; - -private: - QWebSocket m_socket; - QString m_guid; - bool m_keyReceived; - QString m_token; - bool m_active; - QTimer m_timeout; - - void setState(States s, const QString& error={}); - - void close(); - void abort(); - - void onConnected(); - void onMessage(const QString& s); - void onDisconnected(); - void onError(QAbstractSocket::SocketError e); - void onTimeout(); -}; /** * dialog used to change settings for Mod Organizer. On top of the @@ -197,9 +149,9 @@ private: void updateNexusButtons(); void fetchNexusApiKey(); - void testApiKey(); void onKeyChanged(const QString& key); void onStateChanged(NexusSSOLogin::States s, const QString& e); + void onManualKeyValidation(bool success, const QString& e); }; #endif // SETTINGSDIALOG_H -- cgit v1.3.1 From c0a52bc1ca3bcde8107f7cd2b8924c1dda487b1c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 11 Jul 2019 05:06:42 -0400 Subject: extracted api key validator from NXMAccessManager api key now part of ApiUserAccount --- src/apiuseraccount.cpp | 11 ++ src/apiuseraccount.h | 13 +- src/nexusinterface.cpp | 25 ++- src/nexusinterface.h | 1 + src/nxmaccessmanager.cpp | 441 +++++++++++++++++++++++++++++++---------------- src/nxmaccessmanager.h | 86 ++++++--- src/organizercore.cpp | 3 +- 7 files changed, 403 insertions(+), 177 deletions(-) (limited to 'src') diff --git a/src/apiuseraccount.cpp b/src/apiuseraccount.cpp index b901e41a..596f8aa7 100644 --- a/src/apiuseraccount.cpp +++ b/src/apiuseraccount.cpp @@ -5,6 +5,11 @@ APIUserAccount::APIUserAccount() { } +const QString& APIUserAccount::apiKey() const +{ + return m_key; +} + const QString& APIUserAccount::id() const { return m_id; @@ -25,6 +30,12 @@ const APILimits& APIUserAccount::limits() const return m_limits; } +APIUserAccount& APIUserAccount::apiKey(const QString& key) +{ + m_key = key; + return *this; +} + APIUserAccount& APIUserAccount::id(const QString& id) { m_id = id; diff --git a/src/apiuseraccount.h b/src/apiuseraccount.h index 8a238d71..7dd16128 100644 --- a/src/apiuseraccount.h +++ b/src/apiuseraccount.h @@ -60,6 +60,12 @@ public: APIUserAccount(); + + /** + * api key + */ + const QString& apiKey() const; + /** * user id */ @@ -81,6 +87,11 @@ public: const APILimits& limits() const; + /** + * sets the api key + */ + APIUserAccount& apiKey(const QString& key); + /** * sets the user id */ @@ -120,7 +131,7 @@ public: bool exhausted() const; private: - QString m_id, m_name; + QString m_key, m_id, m_name; APIUserAccountTypes m_type; APILimits m_limits; APIStats m_stats; diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index ee9acf2c..2bcd72f3 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -207,13 +207,28 @@ APILimits NexusInterface::defaultAPILimits() } APILimits NexusInterface::parseLimits(const QNetworkReply* reply) +{ + return parseLimits(reply->rawHeaderPairs()); +} + +APILimits NexusInterface::parseLimits( + const QList& headers) { APILimits limits; - limits.maxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt(); - limits.remainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt(); - limits.maxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt(); - limits.remainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt(); + for (const auto& pair : headers) { + const auto name = QString(pair.first).toLower(); + + if (name == "x-rl-daily-limit") { + limits.maxDailyRequests = pair.second.toInt(); + } else if (name == "x-rl-daily-remaining") { + limits.remainingDailyRequests = pair.second.toInt(); + } else if (name == "x-rl-hourly-limit") { + limits.maxHourlyRequests = pair.second.toInt(); + } else if (name == "x-rl-hourly-remaining") { + limits.remainingHourlyRequests = pair.second.toInt(); + } + } return limits; } @@ -765,7 +780,7 @@ void NexusInterface::nextRequest() QNetworkRequest request(url); request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); - request.setRawHeader("APIKEY", m_AccessManager->apiKey().toUtf8()); + request.setRawHeader("APIKEY", m_User.apiKey().toUtf8()); request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m_AccessManager->userAgent(info.m_SubModule)); request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Protocol-Version", "1.0.0"); diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 6e768149..0b1763c4 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -151,6 +151,7 @@ public: public: static APILimits defaultAPILimits(); static APILimits parseLimits(const QNetworkReply* reply); + static APILimits parseLimits(const QList& headers); ~NexusInterface(); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index a274261d..c0a6c227 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -127,6 +127,9 @@ void ValidationProgressDialog::onTimer() NexusSSOLogin::NexusSSOLogin() : m_keyReceived(false), m_active(false) { + m_timeout.setInterval(NXMAccessManager::ValidationTimeout); + m_timeout.setSingleShot(true); + QObject::connect( &m_socket, &QWebSocket::connected, [&]{ onConnected(); }); @@ -135,6 +138,10 @@ NexusSSOLogin::NexusSSOLogin() &m_socket, qOverload(&QWebSocket::error), [&](auto&& e){ onError(e); }); + QObject::connect( + &m_socket, &QWebSocket::sslErrors, + [&](auto&& errors){ onSslErrors(errors); }); + QObject::connect( &m_socket, &QWebSocket::textMessageReceived, [&](auto&& s){ onMessage(s); }); @@ -150,21 +157,25 @@ void NexusSSOLogin::start() { m_active = true; setState(ConnectingToSSO); - m_timeout.start(NXMAccessManager::ValidationTimeout); + m_timeout.start(); m_socket.open(NexusSSO); } void NexusSSOLogin::cancel() { - abort(); - setState(Cancelled); + if (m_active) { + abort(); + setState(Cancelled); + } } void NexusSSOLogin::close() { - m_active = false; - m_timeout.stop(); - m_socket.close(); + if (m_active) { + m_active = false; + m_timeout.stop(); + m_socket.close(); + } } void NexusSSOLogin::abort() @@ -261,6 +272,16 @@ void NexusSSOLogin::onError(QAbstractSocket::SocketError e) { if (m_active) { setState(Error, m_socket.errorString()); + close(); + } +} + +void NexusSSOLogin::onSslErrors(const QList& errors) +{ + if (m_active) { + for (const auto& e : errors) { + setState(Error, e.errorString()); + } } } @@ -271,18 +292,207 @@ void NexusSSOLogin::onTimeout() } +NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) + : m_manager(am), m_reply(nullptr), m_active(false) +{ + m_timeout.setInterval(NXMAccessManager::ValidationTimeout); + m_timeout.setSingleShot(true); + + QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); +} + +NexusKeyValidator::~NexusKeyValidator() +{ + abort(); +} + +void NexusKeyValidator::start(const QString& key) +{ + if (m_reply) { + abort(); + return; + } + + qDebug("Checking Nexus API Key..."); + setState(Connecting); + + const QString requestUrl(NexusBaseUrl + "/users/validate"); + QNetworkRequest request(requestUrl); + + request.setRawHeader("APIKEY", key.toUtf8()); + request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m_manager.userAgent().toUtf8()); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + request.setRawHeader("Protocol-Version", "1.0.0"); + request.setRawHeader("Application-Name", "MO2"); + request.setRawHeader("Application-Version", m_manager.MOVersion().toUtf8()); + + m_reply = m_manager.get(request); + if (!m_reply) { + setState(Error, QObject::tr("Failed to request %1").arg(requestUrl)); + return; + } + + m_active = true; + m_timeout.start(NXMAccessManager::ValidationTimeout); + + QObject::connect( + m_reply, &QNetworkReply::finished, + [&]{ onFinished(); }); + + QObject::connect( + m_reply, &QNetworkReply::sslErrors, + [&](auto&& errors){ onSslErrors(errors); }); +} + +void NexusKeyValidator::cancel() +{ + if (m_active) { + abort(); + setState(Cancelled); + } +} + +bool NexusKeyValidator::isActive() const +{ + return m_active; +} + +void NexusKeyValidator::close() +{ + m_active = false; + m_timeout.stop(); + + if (m_reply) { + m_reply->disconnect(); + m_reply->deleteLater(); + m_reply = nullptr; + } +} + +void NexusKeyValidator::abort() +{ + m_active = false; + m_timeout.stop(); + + if (m_reply) { + m_reply->disconnect(); + m_reply->abort(); + m_reply->deleteLater(); + m_reply = nullptr; + } +} + +void NexusKeyValidator::setState(States s, const QString& error) +{ + if (stateChanged) { + stateChanged(s, error); + } +} + +void NexusKeyValidator::onFinished() +{ + if (!m_reply) { + // shouldn't happen + return; + } + + m_timeout.stop(); + + const auto code = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + const auto doc = QJsonDocument::fromJson(m_reply->readAll()); + const auto headers = m_reply->rawHeaderPairs(); + const auto error = m_reply->errorString(); + + close(); + + const QJsonObject data = doc.object(); + + if (code != 200) { + handleError(code, data.value("message").toString(), error); + return; + } + + if (doc.isNull()) { + setState(InvalidJson); + return; + } + + if (!data.contains("user_id")) { + setState(BadResponse); + return; + } + + const int id = data.value("user_id").toInt(); + const QString key = data.value("key").toString(); + const QString name = data.value("name").toString(); + const bool premium = data.value("is_premium").toBool(); + + const auto user = APIUserAccount() + .apiKey(key) + .id(QString("%1").arg(id)) + .name(name) + .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) + .limits(NexusInterface::parseLimits(headers)); + + if (finished) { + setState(Finished); + finished(user); + } +} + +void NexusKeyValidator::onSslErrors(const QList& errors) +{ + if (m_active) { + for (const auto& e : errors) { + setState(Error, e.errorString()); + } + } +} + +void NexusKeyValidator::onTimeout() +{ + abort(); + setState(Timeout); +} + +void NexusKeyValidator::handleError( + int code, const QString& nexusMessage, const QString& httpError) +{ + QString s = httpError; + + if (!nexusMessage.isEmpty()) { + if (!s.isEmpty()) { + s += ", "; + } + + s += nexusMessage; + } + + if (code != 0) { + if (s.isEmpty()) { + s = QString("HTTP code %1").arg(code); + } else { + s += QString(" (%1)").arg(code); + } + } + + setState(Error, s); +} + + + NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) - , m_ValidateReply(nullptr) , m_ProgressDialog(new ValidationProgressDialog(ValidationTimeout)) , m_MOVersion(moVersion) + , m_validator(*this) + , m_validationState(NotChecked) { - m_ValidateTimeout.setSingleShot(true); - m_ValidateTimeout.setInterval(ValidationTimeout); + m_validator.stateChanged = [&](auto&& s, auto&& e){ onValidatorState(s, e); }; + m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; - connect(&m_ValidateTimeout, SIGNAL(timeout()), this, SLOT(validateTimeout())); - setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); + setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( + Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { // why is this necessary all of a sudden? @@ -290,14 +500,6 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) } } -NXMAccessManager::~NXMAccessManager() -{ - if (m_ValidateReply != nullptr) { - m_ValidateReply->deleteLater(); - m_ValidateReply = nullptr; - } -} - void NXMAccessManager::setTopLevelWidget(QWidget* w) { m_ProgressDialog->setParentWidget(w); @@ -323,7 +525,6 @@ QNetworkReply *NXMAccessManager::createRequest( } } - void NXMAccessManager::showCookies() const { QUrl url(NexusBaseUrl + "/"); @@ -344,80 +545,122 @@ void NXMAccessManager::clearCookies() } } -void NXMAccessManager::startValidationCheck(bool showProgress) +void NXMAccessManager::startValidationCheck(const QString& key, bool showProgress) { - qDebug("Checking Nexus API Key..."); - QString requestString = NexusBaseUrl + "/users/validate"; - - QNetworkRequest request(requestString); - request.setRawHeader("APIKEY", m_ApiKey.toUtf8()); - request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, userAgent().toUtf8()); - request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); - request.setRawHeader("Protocol-Version", "1.0.0"); - request.setRawHeader("Application-Name", "MO2"); - request.setRawHeader("Application-Version", m_MOVersion.toUtf8()); + m_validationState = NotChecked; + m_validator.start(key); if (showProgress) { m_ProgressDialog->start(); } +} + +void NXMAccessManager::onValidatorState( + NexusKeyValidator::States s, const QString& e) +{ + switch (s) + { + case NexusKeyValidator::Connecting: // fall-through + case NexusKeyValidator::Finished: + { + // no-op, success is handled in onValidatorFinished() + break; + } + + case NexusKeyValidator::InvalidJson: + { + onValidatorError(tr("Invalid JSON")); + break; + } + + case NexusKeyValidator::BadResponse: + { + onValidatorError(tr("Bad response")); + break; + } + + case NexusKeyValidator::Timeout: + { + onValidatorError(tr("There was a timeout during the request")); + break; + } + + case NexusKeyValidator::Cancelled: + { + onValidatorError(tr("Cancelled")); + break; + } + + case NexusKeyValidator::Error: + { + onValidatorError(e); + break; + } + } +} - QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback +void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) +{ + m_ProgressDialog->stop(); - m_ValidateReply = get(request); - m_ValidateTimeout.start(); - m_ValidateState = VALIDATE_CHECKING; - connect(m_ValidateReply, SIGNAL(finished()), this, SLOT(validateFinished())); - connect(m_ValidateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(validateError(QNetworkReply::NetworkError))); + m_validationState = Valid; + emit credentialsReceived(user); + emit validateSuccessful(true); } +void NXMAccessManager::onValidatorError(const QString& e) +{ + m_ProgressDialog->stop(); + m_validationState = Invalid; + emit validateFailed(e); +} bool NXMAccessManager::validated() const { - if (m_ValidateState == VALIDATE_CHECKING) { + if (m_validator.isActive()) { m_ProgressDialog->show(); } - return m_ValidateState == VALIDATE_VALID; + return (m_validationState == Valid); } - void NXMAccessManager::refuseValidation() { - m_ValidateState = VALIDATE_REFUSED; + m_validationState = Invalid; } - bool NXMAccessManager::validateAttempted() const { - return m_ValidateState != VALIDATE_NOT_CHECKED; + return (m_validationState != NotChecked); } - bool NXMAccessManager::validateWaiting() const { - return m_ValidateReply != nullptr; + return m_validator.isActive(); } - void NXMAccessManager::apiCheck(const QString &apiKey, ApiCheckFlags flags) { - if (m_ValidateReply != nullptr) { + if (m_validator.isActive()) { return; } if (flags & Force) { - m_ValidateState = VALIDATE_NOT_CHECKED; + m_validationState = NotChecked; } - if (m_ValidateState == VALIDATE_VALID) { + if (m_validationState == Valid) { emit validateSuccessful(false); return; } - m_ApiKey = apiKey; - startValidationCheck((flags & HideProgress) == 0); + startValidationCheck(apiKey, (flags & HideProgress) == 0); } +const QString& NXMAccessManager::MOVersion() const +{ + return m_MOVersion; +} QString NXMAccessManager::userAgent(const QString &subModule) const { @@ -436,100 +679,8 @@ QString NXMAccessManager::userAgent(const QString &subModule) const return QString("Mod Organizer/%1 (%2) Qt/%3").arg(m_MOVersion, comments.join("; "), qVersion()); } - -QString NXMAccessManager::apiKey() const -{ - return m_ApiKey; -} - void NXMAccessManager::clearApiKey() { - m_ApiKey = ""; - m_ValidateState = VALIDATE_NOT_VALID; - + m_validator.cancel(); emit credentialsReceived(APIUserAccount()); } - -void NXMAccessManager::validateTimeout() -{ - m_ValidateTimeout.stop(); - m_ProgressDialog->stop(); - - m_ApiKey.clear(); - m_ValidateState = VALIDATE_NOT_VALID; - - if (m_ValidateReply != nullptr) { - m_ValidateReply->deleteLater(); - m_ValidateReply = nullptr; - } - - emit validateFailed(tr("There was a timeout during the request")); -} - - -void NXMAccessManager::validateError(QNetworkReply::NetworkError) -{ - m_ValidateTimeout.stop(); - m_ProgressDialog->stop(); - - m_ApiKey.clear(); - m_ValidateState = VALIDATE_NOT_VALID; - - if (m_ValidateReply != nullptr) { - m_ValidateReply->disconnect(); - QString error = m_ValidateReply->errorString(); - m_ValidateReply->deleteLater(); - m_ValidateReply = nullptr; - emit validateFailed(error); - } else { - emit validateFailed(tr("Unknown error")); - } -} - - -void NXMAccessManager::validateFinished() -{ - m_ValidateTimeout.stop(); - m_ProgressDialog->stop(); - - if (m_ValidateReply != nullptr) { - QJsonDocument jdoc = QJsonDocument::fromJson(m_ValidateReply->readAll()); - if (!jdoc.isNull()) { - QJsonObject credentialsData = jdoc.object(); - if (credentialsData.contains("user_id")) { - int id = credentialsData.value("user_id").toInt(); - QString name = credentialsData.value("name").toString(); - bool premium = credentialsData.value("is_premium").toBool(); - - const auto user = APIUserAccount() - .id(QString("%1").arg(id)) - .name(name) - .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) - .limits(NexusInterface::parseLimits(m_ValidateReply)); - - - emit credentialsReceived(user); - - m_ValidateReply->deleteLater(); - m_ValidateReply = nullptr; - - m_ValidateState = VALIDATE_VALID; - emit validateSuccessful(true); - - } else { - m_ApiKey.clear(); - m_ValidateState = VALIDATE_NOT_VALID; - emit validateFailed(tr("Validation failed, please reauthenticate in the Settings -> Nexus tab: %1").arg(credentialsData.value("message").toString())); - } - } else { - m_ApiKey.clear(); - m_ValidateState = VALIDATE_NOT_CHECKED; - emit validateFailed(tr("Could not parse response. Invalid JSON.")); - } - } - else { - m_ApiKey.clear(); - m_ValidateState = VALIDATE_NOT_CHECKED; - emit validateFailed(tr("Unknown error.")); - } -} diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 08a799f9..11370d65 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -31,6 +31,7 @@ along with Mod Organizer. If not, see . #include namespace MOBase { class IPluginGame; } +class NXMAccessManager; class ValidationProgressDialog : private QDialog { @@ -103,10 +104,56 @@ private: void onMessage(const QString& s); void onDisconnected(); void onError(QAbstractSocket::SocketError e); + void onSslErrors(const QList& errors); void onTimeout(); }; +class NexusKeyValidator +{ +public: + enum States + { + Connecting, + Finished, + InvalidJson, + BadResponse, + Timeout, + Cancelled, + Error + }; + + std::function finished; + std::function stateChanged; + + NexusKeyValidator(NXMAccessManager& am); + ~NexusKeyValidator(); + + void start(const QString& key); + void cancel(); + + bool isActive() const; + +private: + NXMAccessManager& m_manager; + QNetworkReply* m_reply; + QTimer m_timeout; + bool m_active; + + void setState(States s, const QString& error={}); + + void close(); + void abort(); + + void onFinished(); + void onSslErrors(const QList& errors); + void onTimeout(); + + void handleError( + int code, const QString& nexusMessage, const QString& httpError); +}; + + /** * @brief access manager extended to handle nxm links **/ @@ -127,7 +174,6 @@ public: explicit NXMAccessManager(QObject *parent, const QString &moVersion); - ~NXMAccessManager(); void setTopLevelWidget(QWidget* w); @@ -143,8 +189,8 @@ public: void clearCookies(); QString userAgent(const QString &subModule = QString()) const; + const QString& MOVersion() const; - QString apiKey() const; void clearApiKey(); void refuseValidation(); @@ -164,17 +210,9 @@ signals: * @param necessary true if a login was necessary and succeeded, false if the user is still logged in **/ void validateSuccessful(bool necessary); - void validateFailed(const QString &message); - void credentialsReceived(const APIUserAccount& user); -private slots: - - void validateFinished(); - void validateError(QNetworkReply::NetworkError errorCode); - void validateTimeout(); - protected: virtual QNetworkReply *createRequest( @@ -182,25 +220,23 @@ protected: QIODevice *device); private: + enum States + { + NotChecked, + Valid, + Invalid + }; + QWidget* m_TopLevel; - QTimer m_ValidateTimeout; - QNetworkReply *m_ValidateReply; mutable ValidationProgressDialog* m_ProgressDialog; - QString m_MOVersion; + NexusKeyValidator m_validator; + States m_validationState; - QString m_ApiKey; - - enum { - VALIDATE_NOT_CHECKED, - VALIDATE_CHECKING, - VALIDATE_NOT_VALID, - VALIDATE_ATTEMPT_FAILED, - VALIDATE_REFUSED, - VALIDATE_VALID - } m_ValidateState = VALIDATE_NOT_CHECKED; - - void startValidationCheck(bool showProgress); + void startValidationCheck(const QString& key, bool showProgress); + void onValidatorState(NexusKeyValidator::States s, const QString& e); + void onValidatorFinished(const APIUserAccount& user); + void onValidatorError(const QString& e); }; Q_DECLARE_OPERATORS_FOR_FLAGS(NXMAccessManager::ApiCheckFlags); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 65c8eb81..87668f4b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2484,7 +2484,8 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) void OrganizerCore::loginFailed(const QString &message) { - qDebug("Nexus API validation failed: %s", qUtf8Printable(message)); + qDebug().nospace().noquote() + << "Nexus API validation failed: " << message; if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), tr("Login failed, try again?")) -- cgit v1.3.1 From 226372dba2f8a06ef2349a331da91010d97e72bf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 11 Jul 2019 05:33:35 -0400 Subject: api key validator in settings --- src/nxmaccessmanager.cpp | 4 +- src/settingsdialog.cpp | 161 +++++++++++++++++++++++++++++++++++------------ src/settingsdialog.h | 12 ++-- 3 files changed, 129 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c0a6c227..40ab19b9 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -313,7 +313,7 @@ void NexusKeyValidator::start(const QString& key) return; } - qDebug("Checking Nexus API Key..."); + m_active = true; setState(Connecting); const QString requestUrl(NexusBaseUrl + "/users/validate"); @@ -328,11 +328,11 @@ void NexusKeyValidator::start(const QString& key) m_reply = m_manager.get(request); if (!m_reply) { + close(); setState(Error, QObject::tr("Failed to request %1").arg(requestUrl)); return; } - m_active = true; m_timeout.start(NXMAccessManager::ValidationTimeout); QObject::connect( diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 0131d20b..12dbc482 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -104,20 +104,6 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti , m_keyChanged(false) , m_GeometriesReset(false) { - m_nexusLogin.keyChanged = [&](auto&& s){ onKeyChanged(s); }; - m_nexusLogin.stateChanged = [&](auto&& s, auto&& e){ onStateChanged(s, e); }; - - connect( - NexusInterface::instance(m_PluginContainer)->getAccessManager(), - &NXMAccessManager::validateSuccessful, - [&]{ onManualKeyValidation(true, ""); }); - - connect( - NexusInterface::instance(m_PluginContainer)->getAccessManager(), - &NXMAccessManager::validateFailed, - [&](auto&& e){ onManualKeyValidation(false, e); }); - - ui->setupUi(this); ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); @@ -394,44 +380,66 @@ void SettingsDialog::on_resetDialogsButton_clicked() void SettingsDialog::on_nexusConnect_clicked() { - if (m_nexusLogin.isActive()) { - m_nexusLogin.cancel(); - } else { - fetchNexusApiKey(); + if (m_nexusLogin && m_nexusLogin->isActive()) { + m_nexusLogin->cancel(); + return; } + + if (!m_nexusLogin) { + m_nexusLogin.reset(new NexusSSOLogin); + + m_nexusLogin->keyChanged = [&](auto&& s){ + onSSOKeyChanged(s); + }; + + m_nexusLogin->stateChanged = [&](auto&& s, auto&& e){ + onSSOStateChanged(s, e); + }; + } + + ui->nexusLog->clear(); + m_nexusLogin->start(); + updateNexusButtons(); } void SettingsDialog::on_nexusManualKey_clicked() { - NexusManualKeyDialog dialog(this); + if (m_nexusValidator && m_nexusValidator->isActive()) { + m_nexusValidator->cancel(); + return; + } + NexusManualKeyDialog dialog(this); if (dialog.exec() != QDialog::Accepted) { return; } const auto key = dialog.key(); - if (key.isEmpty()) { clearKey(); - } else { - if (setKey(key)) { - ui->nexusLog->clear(); - ui->nexusLog->addItem(tr("Checking API key...")); - - NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck( - key, NXMAccessManager::Force | NXMAccessManager::HideProgress); - } + return; } -} -void SettingsDialog::fetchNexusApiKey() -{ ui->nexusLog->clear(); - m_nexusLogin.start(); - updateNexusButtons(); + ui->nexusLog->addItem(tr("Checking API key...")); + + if (!m_nexusValidator) { + m_nexusValidator.reset(new NexusKeyValidator( + *NexusInterface::instance(m_PluginContainer)->getAccessManager())); + + m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ + onValidatorStateChanged(s, e); + }; + + m_nexusValidator->finished = [&](auto&& user) { + onValidatorFinished(user); + }; + } + + m_nexusValidator->start(key); } -void SettingsDialog::onKeyChanged(const QString& key) +void SettingsDialog::onSSOKeyChanged(const QString& key) { if (key.isEmpty()) { clearKey(); @@ -440,7 +448,7 @@ void SettingsDialog::onKeyChanged(const QString& key) } } -void SettingsDialog::onStateChanged(NexusSSOLogin::States s, const QString& e) +void SettingsDialog::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) { QString log; @@ -507,12 +515,72 @@ void SettingsDialog::onStateChanged(NexusSSOLogin::States s, const QString& e) updateNexusButtons(); } -void SettingsDialog::onManualKeyValidation(bool success, const QString& e) +void SettingsDialog::onValidatorStateChanged( + NexusKeyValidator::States s, const QString& e) { - if (success) { - ui->nexusLog->addItem("Connected."); - } else { - ui->nexusLog->addItem("Error: " + e); + QString log; + + switch (s) + { + case NexusKeyValidator::Connecting: + { + log = tr("Connecting to Nexus..."); + break; + } + + case NexusKeyValidator::Finished: + { + log = tr("Connected."); + break; + } + + case NexusKeyValidator::InvalidJson: + { + log = tr("Invalid JSON"); + break; + } + + case NexusKeyValidator::BadResponse: + { + log = tr("Bad response"); + break; + } + + case NexusKeyValidator::Timeout: + { + log = QObject::tr( + "No answer from Nexus.\n" + "A firewall might be blocking Mod Organizer."); + + break; + } + + case NexusKeyValidator::Cancelled: + { + log = QObject::tr("Cancelled."); + break; + } + + case NexusKeyValidator::Error: + { + log = tr("Error: %1.").arg(e); + break; + } + } + + if (!log.isEmpty()) { + for (auto&& line : log.split("\n")) { + ui->nexusLog->addItem(line); + } + } + + updateNexusButtons(); +} + +void SettingsDialog::onValidatorFinished(const APIUserAccount& user) +{ + if (!user.apiKey().isEmpty()) { + setKey(user.apiKey()); } } @@ -537,24 +605,35 @@ bool SettingsDialog::clearKey() void SettingsDialog::updateNexusButtons() { - if (m_nexusLogin.isActive()) { + if (m_nexusLogin && m_nexusLogin->isActive()) { // api key is in the process of being retrieved ui->nexusConnect->setText(tr("Cancel")); ui->nexusConnect->setEnabled(true); ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setText(tr("Enter API Key Manually")); ui->nexusManualKey->setEnabled(false); } + else if (m_nexusValidator && m_nexusValidator->isActive()) { + // api key is in the process of being tested + ui->nexusConnect->setText(tr("Connect to Nexus")); + ui->nexusConnect->setEnabled(false); + ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setText(tr("Cancel")); + ui->nexusManualKey->setEnabled(true); + } else if (m_settings->hasNexusApiKey()) { // api key is present ui->nexusConnect->setText(tr("Connect to Nexus")); ui->nexusConnect->setEnabled(false); ui->nexusDisconnect->setEnabled(true); + ui->nexusManualKey->setText(tr("Enter API Key Manually")); ui->nexusManualKey->setEnabled(false); } else { // api key not present ui->nexusConnect->setText(tr("Connect to Nexus")); ui->nexusConnect->setEnabled(true); ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setText(tr("Enter API Key Manually")); ui->nexusManualKey->setEnabled(true); } } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 507214dd..1741fc13 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -142,16 +142,18 @@ private: bool m_keyChanged; QString m_ExecutableBlacklist; - NexusSSOLogin m_nexusLogin; + std::unique_ptr m_nexusLogin; + std::unique_ptr m_nexusValidator; bool setKey(const QString& key); bool clearKey(); void updateNexusButtons(); - void fetchNexusApiKey(); - void onKeyChanged(const QString& key); - void onStateChanged(NexusSSOLogin::States s, const QString& e); - void onManualKeyValidation(bool success, const QString& e); + void onSSOKeyChanged(const QString& key); + void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); + + void onValidatorStateChanged(NexusKeyValidator::States s, const QString& e); + void onValidatorFinished(const APIUserAccount& user); }; #endif // SETTINGSDIALOG_H -- cgit v1.3.1 From be1ee2a97c867a80c07c4b865c306977d96249dc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 11 Jul 2019 05:49:12 -0400 Subject: refactored state messages --- src/nxmaccessmanager.cpp | 125 ++++++++++++++++++++++++++++++----------------- src/nxmaccessmanager.h | 5 +- src/settingsdialog.cpp | 118 +++----------------------------------------- 3 files changed, 90 insertions(+), 158 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 40ab19b9..a331b2e8 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -153,6 +153,45 @@ NexusSSOLogin::NexusSSOLogin() QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); } +QString NexusSSOLogin::stateToString(States s, const QString& e) +{ + switch (s) + { + case ConnectingToSSO: + return QObject::tr("Connecting to Nexus..."); + + case WaitingForToken: + return QObject::tr("Waiting for Nexus..."); + + case WaitingForBrowser: + return QObject::tr("Opened browser, waiting for user..."); + + case Finished: + return QObject::tr("Connected."); + + case Timeout: + return QObject::tr( + "No answer from Nexus.\n" + "A firewall might be blocking Mod Organizer."); + + case ClosedByRemote: + return QObject::tr("Nexus closed the connection."); + + case Cancelled: + return QObject::tr("Cancelled."); + + case Error: // fall-through + default: + { + if (e.isEmpty()) { + return QString("%1").arg(s); + } else { + return e; + } + } + } +} + void NexusSSOLogin::start() { m_active = true; @@ -306,6 +345,40 @@ NexusKeyValidator::~NexusKeyValidator() abort(); } +QString NexusKeyValidator::stateToString(States s, const QString& e) +{ + switch (s) + { + case NexusKeyValidator::Connecting: + return QObject::tr("Connecting to Nexus..."); + + case NexusKeyValidator::Finished: + return QObject::tr("Finished."); + + case NexusKeyValidator::InvalidJson: + return QObject::tr("Invalid JSON"); + + case NexusKeyValidator::BadResponse: + return QObject::tr("Bad response"); + + case NexusKeyValidator::Timeout: + return QObject::tr("There was a timeout during the request"); + + case NexusKeyValidator::Cancelled: + return QObject::tr("Cancelled"); + + case NexusKeyValidator::Error: // fall-through + default: + { + if (e.isEmpty()) { + return QString("%1").arg(s); + } else { + return e; + } + } + } +} + void NexusKeyValidator::start(const QString& key) { if (m_reply) { @@ -558,45 +631,14 @@ void NXMAccessManager::startValidationCheck(const QString& key, bool showProgres void NXMAccessManager::onValidatorState( NexusKeyValidator::States s, const QString& e) { - switch (s) - { - case NexusKeyValidator::Connecting: // fall-through - case NexusKeyValidator::Finished: - { - // no-op, success is handled in onValidatorFinished() - break; - } - - case NexusKeyValidator::InvalidJson: - { - onValidatorError(tr("Invalid JSON")); - break; - } - - case NexusKeyValidator::BadResponse: - { - onValidatorError(tr("Bad response")); - break; - } - - case NexusKeyValidator::Timeout: - { - onValidatorError(tr("There was a timeout during the request")); - break; - } - - case NexusKeyValidator::Cancelled: - { - onValidatorError(tr("Cancelled")); - break; - } - - case NexusKeyValidator::Error: - { - onValidatorError(e); - break; - } + if (s == NexusKeyValidator::Connecting || s == NexusKeyValidator::Finished) { + // no-op, success is handled in onValidatorFinished() + return; } + + m_ProgressDialog->stop(); + m_validationState = Invalid; + emit validateFailed(NexusKeyValidator::stateToString(s, e)); } void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) @@ -608,13 +650,6 @@ void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) emit validateSuccessful(true); } -void NXMAccessManager::onValidatorError(const QString& e) -{ - m_ProgressDialog->stop(); - m_validationState = Invalid; - emit validateFailed(e); -} - bool NXMAccessManager::validated() const { if (m_validator.isActive()) { diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 11370d65..c633ae3b 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -80,6 +80,8 @@ public: std::function keyChanged; std::function stateChanged; + static QString stateToString(States s, const QString& e); + NexusSSOLogin(); void start(); @@ -126,6 +128,8 @@ public: std::function finished; std::function stateChanged; + static QString stateToString(States s, const QString& e); + NexusKeyValidator(NXMAccessManager& am); ~NexusKeyValidator(); @@ -236,7 +240,6 @@ private: void startValidationCheck(const QString& key, bool showProgress); void onValidatorState(NexusKeyValidator::States s, const QString& e); void onValidatorFinished(const APIUserAccount& user); - void onValidatorError(const QString& e); }; Q_DECLARE_OPERATORS_FOR_FLAGS(NXMAccessManager::ApiCheckFlags); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 12dbc482..df957f87 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -450,66 +450,10 @@ void SettingsDialog::onSSOKeyChanged(const QString& key) void SettingsDialog::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) { - QString log; + const auto log = NexusSSOLogin::stateToString(s, e); - switch (s) - { - case NexusSSOLogin::ConnectingToSSO: - { - log = tr("Connecting to Nexus..."); - break; - } - - case NexusSSOLogin::WaitingForToken: - { - log = tr("Waiting for Nexus..."); - break; - } - - case NexusSSOLogin::WaitingForBrowser: - { - log = tr("Opened browser, waiting for user..."); - break; - } - - case NexusSSOLogin::Finished: - { - log = tr("Connected."); - break; - } - - case NexusSSOLogin::Timeout: - { - log = QObject::tr( - "No answer from Nexus.\n" - "A firewall might be blocking Mod Organizer."); - - break; - } - - case NexusSSOLogin::ClosedByRemote: - { - log = QObject::tr("Nexus closed the connection."); - break; - } - - case NexusSSOLogin::Cancelled: - { - log = QObject::tr("Cancelled."); - break; - } - - case NexusSSOLogin::Error: - { - log = tr("Error: %1.").arg(e); - break; - } - } - - if (!log.isEmpty()) { - for (auto&& line : log.split("\n")) { - ui->nexusLog->addItem(line); - } + for (auto&& line : log.split("\n")) { + ui->nexusLog->addItem(line); } updateNexusButtons(); @@ -518,60 +462,10 @@ void SettingsDialog::onSSOStateChanged(NexusSSOLogin::States s, const QString& e void SettingsDialog::onValidatorStateChanged( NexusKeyValidator::States s, const QString& e) { - QString log; - - switch (s) - { - case NexusKeyValidator::Connecting: - { - log = tr("Connecting to Nexus..."); - break; - } - - case NexusKeyValidator::Finished: - { - log = tr("Connected."); - break; - } - - case NexusKeyValidator::InvalidJson: - { - log = tr("Invalid JSON"); - break; - } - - case NexusKeyValidator::BadResponse: - { - log = tr("Bad response"); - break; - } - - case NexusKeyValidator::Timeout: - { - log = QObject::tr( - "No answer from Nexus.\n" - "A firewall might be blocking Mod Organizer."); + const auto log = NexusKeyValidator::stateToString(s, e); - break; - } - - case NexusKeyValidator::Cancelled: - { - log = QObject::tr("Cancelled."); - break; - } - - case NexusKeyValidator::Error: - { - log = tr("Error: %1.").arg(e); - break; - } - } - - if (!log.isEmpty()) { - for (auto&& line : log.split("\n")) { - ui->nexusLog->addItem(line); - } + for (auto&& line : log.split("\n")) { + ui->nexusLog->addItem(line); } updateNexusButtons(); -- cgit v1.3.1 From 45f0a9e78ac876a2a956bc538c6d34358703e338 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 12 Jul 2019 02:46:31 -0400 Subject: nexus info and stats in settings cleaned up double logging for github reset validation progress dialog parent just before the main window dies removed unused APIStats from APIUserAccount, added isValid() --- src/apiuseraccount.cpp | 22 +++++ src/apiuseraccount.h | 8 +- src/main.cpp | 8 +- src/nxmaccessmanager.cpp | 2 +- src/organizercore.cpp | 2 +- src/selfupdater.cpp | 2 +- src/settingsdialog.cpp | 98 ++++++++++++++++----- src/settingsdialog.h | 6 ++ src/settingsdialog.ui | 218 +++++++++++++++++++++++------------------------ 9 files changed, 226 insertions(+), 140 deletions(-) (limited to 'src') diff --git a/src/apiuseraccount.cpp b/src/apiuseraccount.cpp index 596f8aa7..35a868d5 100644 --- a/src/apiuseraccount.cpp +++ b/src/apiuseraccount.cpp @@ -1,10 +1,32 @@ #include "apiuseraccount.h" +QString localizedUserAccountType(APIUserAccountTypes t) +{ + switch (t) + { + case APIUserAccountTypes::Regular: + return QObject::tr("Regular"); + + case APIUserAccountTypes::Premium: + return QObject::tr("Premium"); + + case APIUserAccountTypes::None: // fall-through + default: + return QObject::tr("None"); + } +} + + APIUserAccount::APIUserAccount() : m_type(APIUserAccountTypes::None) { } +bool APIUserAccount::isValid() const +{ + return !m_key.isEmpty(); +} + const QString& APIUserAccount::apiKey() const { return m_key; diff --git a/src/apiuseraccount.h b/src/apiuseraccount.h index 7dd16128..ea4e8685 100644 --- a/src/apiuseraccount.h +++ b/src/apiuseraccount.h @@ -18,6 +18,8 @@ enum class APIUserAccountTypes Premium }; +QString localizedUserAccountType(APIUserAccountTypes t); + /** * current limits imposed on the user account @@ -61,6 +63,11 @@ public: APIUserAccount(); + /** + * whether the user is logged in + */ + bool isValid() const; + /** * api key */ @@ -134,7 +141,6 @@ private: QString m_key, m_id, m_name; APIUserAccountTypes m_type; APILimits m_limits; - APIStats m_stats; }; #endif // APIUSERACCOUNT_H diff --git a/src/main.cpp b/src/main.cpp index 0b078f03..4359c645 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -713,7 +713,13 @@ int runApplication(MOApplication &application, SingleInstance &instance, mainWindow.activateWindow(); splash.finish(&mainWindow); - return application.exec(); + + const auto ret = application.exec(); + + NexusInterface::instance(&pluginContainer) + ->getAccessManager()->setTopLevelWidget(nullptr); + + return ret; } } catch (const std::exception &e) { reportError(e.what()); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index a331b2e8..196368fd 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -167,7 +167,7 @@ QString NexusSSOLogin::stateToString(States s, const QString& e) return QObject::tr("Opened browser, waiting for user..."); case Finished: - return QObject::tr("Connected."); + return QObject::tr("Finished."); case Timeout: return QObject::tr( diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 87668f4b..eeb69e61 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2484,7 +2484,7 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) void OrganizerCore::loginFailed(const QString &message) { - qDebug().nospace().noquote() + qCritical().nospace().noquote() << "Nexus API validation failed: " << message; if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 271c621b..e967b27c 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -130,7 +130,7 @@ void SelfUpdater::testForUpdate() m_GitHub.releases(GitHub::Repository("Modorganizer2", "modorganizer"), [this](const QJsonArray &releases) { if (releases.isEmpty()) { - qDebug("Unable to connect to github.com to check version"); + // error message already logged return; } diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index df957f87..0dae31ac 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -55,6 +55,7 @@ public: : QDialog(parent), ui(new Ui::NexusManualKeyDialog) { ui->setupUi(this); + ui->key->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); connect(ui->openBrowser, &QPushButton::clicked, [&]{ openBrowser(); }); connect(ui->paste, &QPushButton::clicked, [&]{ paste(); }); @@ -111,7 +112,7 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); - updateNexusButtons(); + updateNexusState(); } SettingsDialog::~SettingsDialog() @@ -399,7 +400,7 @@ void SettingsDialog::on_nexusConnect_clicked() ui->nexusLog->clear(); m_nexusLogin->start(); - updateNexusButtons(); + updateNexusState(); } void SettingsDialog::on_nexusManualKey_clicked() @@ -421,8 +422,18 @@ void SettingsDialog::on_nexusManualKey_clicked() } ui->nexusLog->clear(); - ui->nexusLog->addItem(tr("Checking API key...")); + validateKey(key); +} + +void SettingsDialog::on_nexusDisconnect_clicked() +{ + clearKey(); + ui->nexusLog->clear(); + addNexusLog(tr("Disconnected.")); +} +void SettingsDialog::validateKey(const QString& key) +{ if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( *NexusInterface::instance(m_PluginContainer)->getAccessManager())); @@ -436,6 +447,7 @@ void SettingsDialog::on_nexusManualKey_clicked() }; } + addNexusLog(tr("Checking API key...")); m_nexusValidator->start(key); } @@ -444,45 +456,62 @@ void SettingsDialog::onSSOKeyChanged(const QString& key) if (key.isEmpty()) { clearKey(); } else { - setKey(key); + addNexusLog(tr("Received API key.")); + validateKey(key); } } void SettingsDialog::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) { - const auto log = NexusSSOLogin::stateToString(s, e); + if (s != NexusSSOLogin::Finished) { + // finished state is handled in onSSOKeyChanged() + const auto log = NexusSSOLogin::stateToString(s, e); - for (auto&& line : log.split("\n")) { - ui->nexusLog->addItem(line); + for (auto&& line : log.split("\n")) { + addNexusLog(line); + } } - updateNexusButtons(); + updateNexusState(); } void SettingsDialog::onValidatorStateChanged( NexusKeyValidator::States s, const QString& e) { - const auto log = NexusKeyValidator::stateToString(s, e); + if (s != NexusKeyValidator::Finished) { + // finished state is handled in onValidatorFinished() + const auto log = NexusKeyValidator::stateToString(s, e); - for (auto&& line : log.split("\n")) { - ui->nexusLog->addItem(line); + for (auto&& line : log.split("\n")) { + addNexusLog(line); + } } - updateNexusButtons(); + updateNexusState(); } void SettingsDialog::onValidatorFinished(const APIUserAccount& user) { + NexusInterface::instance(m_PluginContainer)->setUserAccount(user); + if (!user.apiKey().isEmpty()) { - setKey(user.apiKey()); + if (setKey(user.apiKey())) { + addNexusLog(tr("Linked with Nexus successfully.")); + } } } +void SettingsDialog::addNexusLog(const QString& s) +{ + ui->nexusLog->addItem(s); + ui->nexusLog->scrollToBottom(); +} + bool SettingsDialog::setKey(const QString& key) { m_keyChanged = true; const bool ret = m_settings->setNexusApiKey(key); - updateNexusButtons(); + updateNexusState(); return ret; } @@ -490,13 +519,19 @@ bool SettingsDialog::clearKey() { m_keyChanged = true; const auto ret = m_settings->clearNexusApiKey(); - updateNexusButtons(); NexusInterface::instance(m_PluginContainer)->getAccessManager()->clearApiKey(); + updateNexusState(); return ret; } +void SettingsDialog::updateNexusState() +{ + updateNexusButtons(); + updateNexusData(); +} + void SettingsDialog::updateNexusButtons() { if (m_nexusLogin && m_nexusLogin->isActive()) { @@ -532,6 +567,32 @@ void SettingsDialog::updateNexusButtons() } } +void SettingsDialog::updateNexusData() +{ + const auto user = NexusInterface::instance(m_PluginContainer) + ->getAPIUserAccount(); + + if (user.isValid()) { + ui->nexusUserID->setText(user.id()); + ui->nexusName->setText(user.name()); + ui->nexusAccount->setText(localizedUserAccountType(user.type())); + + ui->nexusDailyRequests->setText(QString("%1/%2") + .arg(user.limits().remainingDailyRequests) + .arg(user.limits().maxDailyRequests)); + + ui->nexusHourlyRequests->setText(QString("%1/%2") + .arg(user.limits().remainingHourlyRequests) + .arg(user.limits().maxHourlyRequests)); + } else { + ui->nexusUserID->setText(tr("N/A")); + ui->nexusName->setText(tr("N/A")); + ui->nexusAccount->setText(tr("N/A")); + ui->nexusDailyRequests->setText(tr("N/A")); + ui->nexusHourlyRequests->setText(tr("N/A")); + } +} + void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) { if (pluginItem != nullptr) { @@ -599,13 +660,6 @@ void SettingsDialog::on_clearCacheButton_clicked() NexusInterface::instance(m_PluginContainer)->clearCache(); } -void SettingsDialog::on_nexusDisconnect_clicked() -{ - clearKey(); - ui->nexusLog->clear(); - ui->nexusLog->addItem(tr("Disconnected.")); -} - void SettingsDialog::normalizePath(QLineEdit *lineEdit) { QString text = lineEdit->text(); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 1741fc13..c5f487fd 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -145,15 +145,21 @@ private: std::unique_ptr m_nexusLogin; std::unique_ptr m_nexusValidator; + void validateKey(const QString& key); bool setKey(const QString& key); bool clearKey(); + + void updateNexusState(); void updateNexusButtons(); + void updateNexusData(); void onSSOKeyChanged(const QString& key); void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); void onValidatorStateChanged(NexusKeyValidator::States s, const QString& e); void onValidatorFinished(const APIUserAccount& user); + + void addNexusLog(const QString& s); }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index dfbde943..fccc8be0 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -451,103 +451,7 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Nexus - - - - - Nexus Connection - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Connect to Nexus - - - - - - - Manually enter the API key and try to login - - - Enter API Key Manually - - - - - - - Clear the stored Nexus API key and force reauthorization. - - - Disconnect from Nexus - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - - - - Qt::Vertical - - - - 0 - 0 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QAbstractScrollArea::AdjustToContents - - - - - - - - - + @@ -569,10 +473,13 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Nexus Account + + 10 + - User ID + User ID: Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse @@ -589,21 +496,21 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - Username + Name: - + - username + name - Account + Account: @@ -623,17 +530,27 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Statistics + + 10 + - Daily requests + Daily requests: + + + + + + + daily requests - Hourly requests + Hourly requests: @@ -644,24 +561,99 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - - + + + + + + + + + + Nexus Connection + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + - Requests queued + Connect to Nexus - - + + + + Manually enter the API key and try to login + - queued + Enter API Key Manually - - + + + + Clear the stored Nexus API key and force reauthorization. + - daily requests + Disconnect from Nexus + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractScrollArea::AdjustToContents -- cgit v1.3.1 From 45daaec7aada840692db4bbcb9fd4ebfdb9dae1b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 12 Jul 2019 03:09:03 -0400 Subject: removed useless flags, they were only used by settings --- src/nxmaccessmanager.cpp | 13 +++++-------- src/nxmaccessmanager.h | 15 ++------------- 2 files changed, 7 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 196368fd..ce33af43 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -618,14 +618,11 @@ void NXMAccessManager::clearCookies() } } -void NXMAccessManager::startValidationCheck(const QString& key, bool showProgress) +void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; m_validator.start(key); - - if (showProgress) { - m_ProgressDialog->start(); - } + m_ProgressDialog->start(); } void NXMAccessManager::onValidatorState( @@ -674,13 +671,13 @@ bool NXMAccessManager::validateWaiting() const return m_validator.isActive(); } -void NXMAccessManager::apiCheck(const QString &apiKey, ApiCheckFlags flags) +void NXMAccessManager::apiCheck(const QString &apiKey, bool force) { if (m_validator.isActive()) { return; } - if (flags & Force) { + if (force) { m_validationState = NotChecked; } @@ -689,7 +686,7 @@ void NXMAccessManager::apiCheck(const QString &apiKey, ApiCheckFlags flags) return; } - startValidationCheck(apiKey, (flags & HideProgress) == 0); + startValidationCheck(apiKey); } const QString& NXMAccessManager::MOVersion() const diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index c633ae3b..0cfaffb5 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -165,15 +165,6 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: - enum ApiCheckFlagsEnum - { - NoFlags = 0, - Force, - HideProgress - }; - - Q_DECLARE_FLAGS(ApiCheckFlags, ApiCheckFlagsEnum) - static const std::chrono::seconds ValidationTimeout; explicit NXMAccessManager(QObject *parent, const QString &moVersion); @@ -186,7 +177,7 @@ public: bool validateAttempted() const; bool validateWaiting() const; - void apiCheck(const QString &apiKey, ApiCheckFlags flags=NoFlags); + void apiCheck(const QString &apiKey, bool force=false); void showCookies() const; @@ -237,11 +228,9 @@ private: NexusKeyValidator m_validator; States m_validationState; - void startValidationCheck(const QString& key, bool showProgress); + void startValidationCheck(const QString& key); void onValidatorState(NexusKeyValidator::States s, const QString& e); void onValidatorFinished(const APIUserAccount& user); }; -Q_DECLARE_OPERATORS_FOR_FLAGS(NXMAccessManager::ApiCheckFlags); - #endif // NXMACCESSMANAGER_H -- cgit v1.3.1 From b69b3e5e471dfd16f1861bd1e4bed10cf46709b9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 12 Jul 2019 03:24:05 -0400 Subject: removed already commented out code: don't resend the guid and token, nexus never answers with the api key, just create a new request every time --- src/nxmaccessmanager.cpp | 4 ---- src/nxmaccessmanager.h | 1 - 2 files changed, 5 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index ce33af43..8b70f09b 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -242,15 +242,12 @@ void NexusSSOLogin::onConnected() m_keyReceived = false; - //if (m_guid.isEmpty()) { boost::uuids::random_generator generator; boost::uuids::uuid sessionId = generator(); m_guid = boost::uuids::to_string(sessionId).c_str(); - //} QJsonObject data; data.insert(QString("id"), QJsonValue(m_guid)); - //data.insert(QString("token"), QJsonValue(m_token)); data.insert(QString("protocol"), 2); const QString message = QJsonDocument(data).toJson(); @@ -275,7 +272,6 @@ void NexusSSOLogin::onMessage(const QString& s) if (data.contains("connection_token")) { // first answer - m_token = data["connection_token"].toString(); // open browser const auto url = NexusSSOPage.arg(m_guid); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 0cfaffb5..eed7c1c9 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -93,7 +93,6 @@ private: QWebSocket m_socket; QString m_guid; bool m_keyReceived; - QString m_token; bool m_active; QTimer m_timeout; -- cgit v1.3.1 From 2d78957c3d4c33de5813d7ee5b86dec8cb01478c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 14 Jul 2019 17:20:53 -0400 Subject: changed message when opening browser --- src/nxmaccessmanager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 8b70f09b..c413e156 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -164,7 +164,9 @@ QString NexusSSOLogin::stateToString(States s, const QString& e) return QObject::tr("Waiting for Nexus..."); case WaitingForBrowser: - return QObject::tr("Opened browser, waiting for user..."); + return QObject::tr( + "Opened Nexus in browser.\n" + "Switch to your browser and accept the request."); case Finished: return QObject::tr("Finished."); -- cgit v1.3.1 From bc9f286bce224743d244e540d55f26b55affbd4a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 12 Jul 2019 08:30:40 -0400 Subject: moved the log to a dock widget added a menu item in the view menu for it --- src/main.cpp | 4 ++ src/mainwindow.cpp | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/mainwindow.h | 5 ++- src/mainwindow.ui | 69 ++++++++++++++++++++++-------- 4 files changed, 176 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 4359c645..db0c8f93 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -706,6 +706,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, SLOT(externalMessage(QString))); mainWindow.processUpdates(); + + // this must be before readSettings(), see DockFixer in mainwindow.cpp + splash.finish(&mainWindow); + mainWindow.readSettings(); qDebug("displaying main window"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9586cb93..4b5ef9ed 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -195,6 +195,102 @@ const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock +// sizes are not restored when the main window is maximized; it is used in +// MainWindow::readSettings() and MainWindow::storeSettings() +// +// there's also https://stackoverflow.com/questions/44005852, which has what +// seems to be a popular fix, but it breaks the restored size of the window +// by setting it to the desktop's resolution, so that doesn't work +// +// the only fix I could find is to remember the sizes of the docks and manually +// setting them back; saving is straightforward, but restoring is messy +// +// this also depends on the window being visible before the timer in restore() +// is fired and the timer must be processed by application.exec(); therefore, +// the splash screen _must_ be closed before readSettings() is called, because +// it has its own event loop, which seems to interfere with this +// +// all of this should become unnecessary when QTBUG-46620 is fixed +// +class DockFixer +{ +public: + static void save(MainWindow* mw, QSettings& settings) + { + const auto docks = mw->findChildren(); + + // saves the size of each dock + for (int i=0; isize().width(); + } else { + size = docks[i]->size().height(); + } + + settings.setValue(settingName(docks[i]), size); + } + } + + static void restore(MainWindow* mw, const QSettings& settings) + { + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; + + std::vector dockInfos; + + const auto docks = mw->findChildren(); + + // for each dock + for (int i=0; iresizeDocks({info.d}, {info.size}, info.ori); + } + }); + } + + static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d) + { + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(d) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } + } + + static QString settingName(QDockWidget* d) + { + return "geometry/" + d->objectName() + "_size"; + } +}; + + MainWindow::MainWindow(QSettings &initSettings , OrganizerCore &organizerCore , PluginContainer &pluginContainer @@ -422,7 +518,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); - connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ toolbarMenu_aboutToShow(); }); + connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); + connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -764,7 +861,7 @@ void MainWindow::updatePinnedExecutables() ui->menuRun->menuAction()->setVisible(hasLinks); } -void MainWindow::toolbarMenu_aboutToShow() +void MainWindow::updateToolbarMenu() { // well, this is a bit of a hack to allow the same toolbar menu to be shown // in both the main menu and the context menu @@ -788,6 +885,11 @@ void MainWindow::toolbarMenu_aboutToShow() ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); } +void MainWindow::updateViewMenu() +{ + ui->actionViewLog->setChecked(ui->logDock->isVisible()); +} + QMenu* MainWindow::createPopupMenu() { return ui->menuToolbars; @@ -838,6 +940,11 @@ void MainWindow::on_actionToolBarIconsAndText_triggered() setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon); } +void MainWindow::on_actionViewLog_triggered() +{ + ui->logDock->setVisible(!ui->logDock->isVisible()); +} + void MainWindow::setToolbarSize(const QSize& s) { for (auto* tb : findChildren()) { @@ -1257,7 +1364,7 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().registerAsNXMHandler(false); m_WasVisible = true; - updateProblemsButton(); + updateProblemsButton(); } } @@ -2204,6 +2311,8 @@ void MainWindow::readSettings() if (settings.value("Settings/use_proxy", false).toBool()) { activateProxy(true); } + + DockFixer::restore(this, settings); } void MainWindow::processUpdates() { @@ -2285,10 +2394,13 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + for (const std::pair kv : m_PersistedGeometry) { QString key = QString("geometry/") + kv.first; settings.setValue(key, kv.second->saveState()); } + + DockFixer::save(this, settings); } } @@ -2569,7 +2681,7 @@ void MainWindow::directory_refreshed() if (ui->tabWidget->currentIndex() == 2) { refreshDataTreeKeepExpandedNodes(); } - + } void MainWindow::esplist_changed() diff --git a/src/mainwindow.h b/src/mainwindow.h index 80508787..d7dbfd90 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -219,7 +219,9 @@ private: void updatePinnedExecutables(); void setToolbarSize(const QSize& s); void setToolbarButtonStyle(Qt::ToolButtonStyle s); - void toolbarMenu_aboutToShow(); + + void updateToolbarMenu(); + void updateViewMenu(); QMenu* createPopupMenu() override; void activateSelectedProfile(); @@ -654,6 +656,7 @@ private slots: // ui slots void on_actionToolBarIconsOnly_triggered(); void on_actionToolBarTextOnly_triggered(); void on_actionToolBarIconsAndText_triggered(); + void on_actionViewLog_triggered(); void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 70d1cf39..d83c68ca 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1286,23 +1286,6 @@ p, li { white-space: pre-wrap; } - - - Qt::ActionsContextMenu - - - QAbstractItemView::NoSelection - - - true - - - false - - - true - - @@ -1403,6 +1386,7 @@ p, li { white-space: pre-wrap; } + @@ -1417,6 +1401,49 @@ p, li { white-space: pre-wrap; } + + + Log + + + 8 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ActionsContextMenu + + + QAbstractItemView::NoSelection + + + true + + + false + + + true + + + + + + @@ -1736,6 +1763,14 @@ p, li { white-space: pre-wrap; } Status &bar + + + true + + + Log + + -- cgit v1.3.1 From 54d98e291701f2187174a67c186f1ea762c6b959 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:38:32 -0400 Subject: removed unused or redundant stuff in error_report.h renamed log() to vlog() for now extracted console creation to Console class rewrote LogBuffer to work with logging from uibase, renamed to LogModel added fmt dependency --- CMakeLists.txt | 8 +- src/CMakeLists.txt | 6 +- src/logbuffer.cpp | 263 +++++++++++++----------------------------- src/logbuffer.h | 70 +++-------- src/main.cpp | 116 ++++++++++++++++--- src/mainwindow.cpp | 35 ++++-- src/mainwindow.h | 1 + src/mainwindow.ui | 15 ++- src/organizercore.cpp | 1 - src/profile.cpp | 5 +- src/shared/directoryentry.cpp | 12 +- src/shared/error_report.cpp | 45 -------- src/shared/error_report.h | 21 +--- 13 files changed, 254 insertions(+), 344 deletions(-) (limited to 'src') diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c76373..ac9d8fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) -ADD_COMPILE_OPTIONS($<$:/MP> $<$:$<$:/O2>> $<$:$<$:/O2>>) +ADD_COMPILE_OPTIONS( + $<$:/MP> + $<$:/D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING> + $<$:$<$:/O2>> + $<$:$<$:/O2>>) PROJECT(organizer) @@ -11,9 +15,11 @@ set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) set(CMAKE_INSTALL_MESSAGE NEVER) SET(DEPENDENCIES_DIR CACHE PATH "") + # hint to find qt in dependencies path LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll) +LIST(APPEND CMAKE_PREFIX_PATH ${FMT_ROOT}/build) ADD_SUBDIRECTORY(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f197211a..a359b8a9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -520,6 +520,9 @@ LINK_DIRECTORIES(${Boost_LIBRARY_DIRS}) FIND_PACKAGE(zlib REQUIRED) # TODO FindZlib doesn't find the static zlib library +# fmt +find_package(fmt REQUIRED) + INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/bsatk/src ${project_path}/esptk/src @@ -551,10 +554,11 @@ ELSE() ENDIF() ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm}) + TARGET_LINK_LIBRARIES(ModOrganizer Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets - ${Boost_LIBRARIES} + ${Boost_LIBRARIES} fmt::fmt zlibstatic uibase esptk bsatk githubpp ${usvfs_name} diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index dfe8f943..9e3cd712 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -20,239 +20,140 @@ along with Mod Organizer. If not, see . #include "logbuffer.h" #include #include +#include #include #include #include #include #include -using MOBase::reportError; +using namespace MOBase; -QScopedPointer LogBuffer::s_Instance; -QMutex LogBuffer::s_Mutex; +static LogModel* g_instance = nullptr; +const std::size_t MaxLines = 1000; -LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, - const QString &outputFileName) - : QAbstractItemModel(nullptr) - , m_OutFileName(outputFileName) - , m_ShutDown(false) - , m_MinMsgType(minMsgType) - , m_NumMessages(0) +LogModel::LogModel() { - m_Messages.resize(messageCount); + connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); } -LogBuffer::~LogBuffer() +void LogModel::create() { - qInstallMessageHandler(0); - write(); + g_instance = new LogModel; } -void LogBuffer::logMessage(QtMsgType type, const QString &message) +LogModel& LogModel::instance() { - if (type >= m_MinMsgType) { - QStringList messagelist = message.split("\n"); - for (auto split_message : messagelist) { - Message msg = {type, QTime::currentTime(), split_message}; - if (m_NumMessages < m_Messages.size()) { - beginInsertRows(QModelIndex(), static_cast(m_NumMessages), - static_cast(m_NumMessages) + 1); - } - m_Messages.at(m_NumMessages % m_Messages.size()) = msg; - if (m_NumMessages < m_Messages.size()) { - endInsertRows(); - } else { - emit dataChanged(createIndex(0, 0), - createIndex(static_cast(m_Messages.size()), 0)); - } - ++m_NumMessages; - if (type >= QtCriticalMsg) { - write(); - } - } - } -} - -void LogBuffer::write() const -{ - if (m_NumMessages == 0) { - return; - } - - DWORD lastError = ::GetLastError(); - - QFile file(m_OutFileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write log to %1: %2") - .arg(m_OutFileName) - .arg(file.errorString())); - return; - } - - unsigned int i - = (m_NumMessages > m_Messages.size()) - ? static_cast(m_NumMessages - m_Messages.size()) - : 0U; - for (; i < m_NumMessages; ++i) { - file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); - file.write("\r\n"); - } - ::SetLastError(lastError); + return *g_instance; } -void LogBuffer::init(int messageCount, QtMsgType minMsgType, - const QString &outputFileName) +void LogModel::add(MOBase::log::Entry e) { - QMutexLocker guard(&s_Mutex); - - s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); - qInstallMessageHandler(LogBuffer::log); + emit entryAdded(std::move(e)); } -char LogBuffer::msgTypeID(QtMsgType type) +void LogModel::onEntryAdded(MOBase::log::Entry e) { - switch (type) { - case QtDebugMsg: - return 'D'; - case QtInfoMsg: - return 'I'; - case QtWarningMsg: - return 'W'; - case QtCriticalMsg: - return 'C'; - case QtFatalMsg: - return 'F'; - default: - return '?'; + bool full = false; + if (m_messages.size() > MaxLines) { + m_messages.pop_front(); + full = true; } -} -void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, - const QString &message) -{ - // QMutexLocker doesn't support timeout... - if (!s_Mutex.tryLock(100)) { - fprintf(stderr, "failed to log: %s", qUtf8Printable(message)); - return; - } - ON_BLOCK_EXIT([]() { s_Mutex.unlock(); }); + const int row = static_cast(m_messages.size()); - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); + if (!full) { + beginInsertRows(QModelIndex(), row, row + 1); } - if (type == QtDebugMsg) { - fprintf(stdout, "%s [%c] %s\n", qUtf8Printable(QTime::currentTime().toString()), - msgTypeID(type), qUtf8Printable(message)); + m_messages.emplace_back(std::move(e)); + + if (!full) { + endInsertRows(); } else { - if (context.line != 0) { - fprintf(stdout, "%s [%c] (%s:%u) %s\n", - qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type), - context.file, context.line, qUtf8Printable(message)); - } else { - fprintf(stdout, "%s [%c] %s\n", - qUtf8Printable(QTime::currentTime().toString()), msgTypeID(type), - qUtf8Printable(message)); - } + emit dataChanged( + createIndex(row, 0), + createIndex(row + 1, columnCount({}))); } - fflush(stdout); } -QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const +QModelIndex LogModel::index(int row, int column, const QModelIndex&) const { return createIndex(row, column, row); } -QModelIndex LogBuffer::parent(const QModelIndex &) const +QModelIndex LogModel::parent(const QModelIndex&) const { return QModelIndex(); } -int LogBuffer::rowCount(const QModelIndex &parent) const +int LogModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; else - return static_cast(std::min(m_NumMessages, m_Messages.size())); + return static_cast(m_messages.size()); } -int LogBuffer::columnCount(const QModelIndex &) const +int LogModel::columnCount(const QModelIndex&) const { - return 2; + return 3; } -QVariant LogBuffer::data(const QModelIndex &index, int role) const +QVariant LogModel::data(const QModelIndex& index, int role) const { - unsigned int offset - = m_NumMessages < m_Messages.size() - ? 0 - : static_cast(m_NumMessages - m_Messages.size()); - unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); - switch (role) { - case Qt::DisplayRole: { - if (index.column() == 0) { - return m_Messages[msgIndex].time.toString("H: mm: ss"); - } else if (index.column() == 1) { - const QString &msg = m_Messages[msgIndex].message; - if (msg.length() < 200) { - return msg; - } else { - return msg.mid(0, 200) + "..."; - } - } - } break; - case Qt::DecorationRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - case QtInfoMsg: - return QIcon(":/MO/gui/information"); - case QtWarningMsg: - return QIcon(":/MO/gui/warning"); - case QtCriticalMsg: - return QIcon(":/MO/gui/important"); - case QtFatalMsg: - return QIcon(":/MO/gui/problem"); - } - } - } break; - case Qt::UserRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - return "D"; - case QtInfoMsg: - return "I"; - case QtWarningMsg: - return "W"; - case QtCriticalMsg: - return "C"; - case QtFatalMsg: - return "F"; - } - } - } break; + using namespace std::chrono; + + const auto row = static_cast(index.row()); + if (row >= m_messages.size()) { + return {}; } - return QVariant(); -} -void LogBuffer::writeNow() -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->write(); + const auto& e = m_messages[row]; + + if (role == Qt::DisplayRole) { + if (index.column() == 1) { + const auto ms = duration_cast(e.time.time_since_epoch()); + const auto s = duration_cast(ms); + + const std::time_t t = s.count(); + const std::size_t frac = ms.count() % 1000; + + auto time = QDateTime::fromTime_t(t).time(); + time = time.addMSecs(frac); + + return time.toString("hh:mm:ss.zzz"); + } else if (index.column() == 2) { + return QString::fromStdString(e.message); + } + } + + if (role == Qt::DecorationRole) { + if (index.column() == 0) { + switch (e.level) { + case log::Warning: + return QIcon(":/MO/gui/warning"); + + case log::Error: + return QIcon(":/MO/gui/problem"); + + case log::Debug: // fall-through + case log::Info: + default: + return {}; + } + } } + + return QVariant(); } -void LogBuffer::cleanQuit() +QVariant LogModel::headerData(int, Qt::Orientation, int) const { - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->m_ShutDown = true; - } + return {}; } -void log(const char *format, ...) +void vlog(const char *format, ...) { va_list argList; va_start(argList, format); @@ -268,11 +169,3 @@ void log(const char *format, ...) va_end(argList); } - -QString LogBuffer::Message::toString() const -{ - return QString("%1 [%2] %3") - .arg(time.toString()) - .arg(msgTypeID(type)) - .arg(message); -} diff --git a/src/logbuffer.h b/src/logbuffer.h index 0cfecfa2..1bf8901b 100644 --- a/src/logbuffer.h +++ b/src/logbuffer.h @@ -26,70 +26,36 @@ along with Mod Organizer. If not, see . #include #include #include +#include - -class LogBuffer : public QAbstractItemModel +class LogModel : public QAbstractItemModel { Q_OBJECT public: + static void create(); + static LogModel& instance(); - static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName); - static void log(QtMsgType type, const QMessageLogContext &context, const QString &message); - - static void writeNow(); - static void cleanQuit(); - - static LogBuffer *instance() { return s_Instance.data(); } - -public: - - virtual ~LogBuffer(); - - void logMessage(QtMsgType type, const QString &message); + void add(MOBase::log::Entry e); - // QAbstractItemModel interface -public: - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &child) const; - int rowCount(const QModelIndex &parent) const; - int columnCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; +protected: + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QVariant headerData( + int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; signals: - -public slots: + void entryAdded(MOBase::log::Entry e); private: + std::deque m_messages; - explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName); - LogBuffer(const LogBuffer &reference); // not implemented - LogBuffer &operator=(const LogBuffer &reference); // not implemented - - void write() const; - - static char msgTypeID(QtMsgType type); - -private: - - struct Message { - QtMsgType type; - QTime time; - QString message; - QString toString() const; - }; - -private: - - static QScopedPointer s_Instance; - static QMutex s_Mutex; - - QString m_OutFileName; - bool m_ShutDown; - QtMsgType m_MinMsgType; - size_t m_NumMessages; - std::vector m_Messages; - + LogModel(); + void onEntryAdded(MOBase::log::Entry e); }; #endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index db0c8f93..23ea234a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,6 +51,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -731,18 +732,46 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } -int doCoreDump(env::CoreDumpTypes type) +class Console { - // open a console - AllocConsole(); +public: + Console() + { + // open a console + AllocConsole(); - // redirect stdin, stdout and stderr to it - FILE* in=nullptr; - FILE* out=nullptr; - FILE* err=nullptr; - freopen_s(&in, "CONIN$", "r", stdin); - freopen_s(&out, "CONOUT$", "w", stdout); - freopen_s(&err, "CONOUT$", "w", stderr); + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); + } + + ~Console() + { + // close redirected handles + std::fclose(m_err); + std::fclose(m_out); + std::fclose(m_in); + + // close console + FreeConsole(); + + // redirect stdin, stdout and stderr to NUL, don't bother closing the + // handles + freopen_s(&m_in, "NUL", "r", stdin); + freopen_s(&m_out, "NUL", "w", stdout); + freopen_s(&m_err, "NUL", "w", stderr); + } + +private: + FILE* m_in = nullptr; + FILE* m_out = nullptr; + FILE* m_err = nullptr; +}; + +int doCoreDump(env::CoreDumpTypes type) +{ + Console c; // dump const auto b = env::coredumpOther(type); @@ -753,15 +782,66 @@ int doCoreDump(env::CoreDumpTypes type) std::wcerr << L"Press enter to continue..."; std::wcin.get(); - // close redirected handles - std::fclose(err); - std::fclose(out); - std::fclose(in); + return (b ? 0 : 1); +} + +log::Levels convertQtLevel(QtMsgType t) +{ + switch (t) + { + case QtDebugMsg: + return log::Debug; - // close console - FreeConsole(); + case QtWarningMsg: + return log::Warning; - return (b ? 0 : 1); + case QtCriticalMsg: // fall-through + case QtFatalMsg: + return log::Error; + + case QtInfoMsg: // fall-through + default: + return log::Info; + } +} + +void qtLogCallback( + QtMsgType type, const QMessageLogContext& context, const QString& message) +{ + std::string_view file = ""; + + if (type != QtDebugMsg) { + if (context.file) { + file = context.file; + + const auto lastSep = file.find_last_of("/\\"); + if (lastSep != std::string_view::npos) { + file = {context.file + lastSep + 1}; + } + } + } + + if (file.empty()) { + log::log( + convertQtLevel(type), "{}", + message.toStdString()); + } else { + log::log( + convertQtLevel(type), "[{}:{}] {}", + file, context.line, message.toStdString()); + } +} + +void initLogging(const QString& logFile) +{ + LogModel::create(); + + log::init( + true, MOBase::log::File::rotating(logFile.toStdWString(), 5*1024*1024, 5), + MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$", + [](log::Entry e){ LogModel::instance().add(e); }); + + qInstallMessageHandler(qtLogCallback); } int main(int argc, char *argv[]) @@ -839,7 +919,7 @@ int main(int argc, char *argv[]) // initialize dump collection only after "dataPath" since the crashes are stored under it prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - LogBuffer::init(1000000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + initLogging(qApp->property("dataPath").toString() + "/logs/mo_interface.log"); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4b5ef9ed..cd224414 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -357,17 +357,10 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - ui->logList->setModel(LogBuffer::instance()); - ui->logList->setColumnWidth(0, 100); - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + setupLogList(); + int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); - connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), - ui->logList, SLOT(scrollToBottom())); - connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - ui->logList, SLOT(scrollToBottom())); updateProblemsButton(); @@ -593,6 +586,30 @@ MainWindow::MainWindow(QSettings &initSettings updateModCount(); } +void MainWindow::setupLogList() +{ + ui->logList->setModel(&LogModel::instance()); + + const int timestampWidth = + QFontMetrics(ui->logList->font()).width("00:00:00.000"); + + ui->logList->header()->setMinimumSectionSize(0); + ui->logList->header()->resizeSection(0, 20); + ui->logList->header()->resizeSection(1, timestampWidth + 8); + + ui->logList->setAutoScroll(true); + ui->logList->scrollToBottom(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + + connect( + ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + ui->logList, SLOT(scrollToBottom())); + + connect( + ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + ui->logList, SLOT(scrollToBottom())); +} + void MainWindow::resetActionIcons() { // this is a bit of a hack diff --git a/src/mainwindow.h b/src/mainwindow.h index d7dbfd90..9ca3e5c3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,6 +636,7 @@ private slots: void resetActionIcons(); void updateModCount(); void updatePluginCount(); + void setupLogList(); private slots: // ui slots // actions diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d83c68ca..5a45b3b4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1402,6 +1402,9 @@ p, li { white-space: pre-wrap; } + + QDockWidget::AllDockWidgetFeatures + Log @@ -1428,17 +1431,17 @@ p, li { white-space: pre-wrap; } Qt::ActionsContextMenu - QAbstractItemView::NoSelection - - - true + QAbstractItemView::ExtendedSelection - + false - + true + + false + diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eeb69e61..5f5c3afe 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -342,7 +342,6 @@ OrganizerCore::~OrganizerCore() m_CurrentProfile = nullptr; ModInfo::clear(); - LogBuffer::cleanQuit(); m_ModList.setProfile(nullptr); // NexusInterface::instance()->cleanup(); diff --git a/src/profile.cpp b/src/profile.cpp index ef387027..01906903 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -287,8 +287,11 @@ void Profile::createTweakedIniFile() } if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorString().c_str())); + const auto e = ::GetLastError(); + reportError(tr("failed to create tweaked ini: %1") + .arg(formatSystemMessageQ(e))); } + qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni))); } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index bde515a9..9d9edd85 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -103,7 +103,7 @@ public: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); + vlog("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); } } @@ -714,12 +714,12 @@ void DirectoryEntry::removeFile(FileEntry::Index index) if (iter != m_Files.end()) { m_Files.erase(iter); } else { - log("file \"%ls\" not in directory \"%ls\"", + vlog("file \"%ls\" not in directory \"%ls\"", m_FileRegister->getFile(index)->getName().c_str(), this->getName().c_str()); } } else { - log("file \"%ls\" not in directory \"%ls\", directory empty", + vlog("file \"%ls\" not in directory \"%ls\", directory empty", m_FileRegister->getFile(index)->getName().c_str(), this->getName().c_str()); } @@ -844,7 +844,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry *temp = findSubDirectory(pathComponent); if (temp != nullptr) { if (len >= path.size()) { - log("unexpected end of path"); + vlog("unexpected end of path"); return FileEntry::Ptr(); } return temp->searchFile(path.substr(len + 1), directory); @@ -988,7 +988,7 @@ bool FileRegister::removeFile(FileEntry::Index index) m_Files.erase(index); return true; } else { - log("invalid file index for remove: %lu", index); + vlog("invalid file index for remove: %lu", index); return false; } } @@ -1002,7 +1002,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) m_Files.erase(iter); } } else { - log("invalid file index for remove (for origin): %lu", index); + vlog("invalid file index for remove (for origin): %lu", index); } } diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp index 6d091630..4185b544 100644 --- a/src/shared/error_report.cpp +++ b/src/shared/error_report.cpp @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . namespace MOShared { - void reportError(LPCSTR format, ...) { char buffer[1025]; @@ -52,48 +51,4 @@ void reportError(LPCWSTR format, ...) MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR); } - -std::string getCurrentErrorStringA() -{ - LPSTR buffer = nullptr; - - DWORD errorCode = ::GetLastError(); - - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::string(); - } else { - LPSTR lastChar = buffer + strlen(buffer) - 2; - *lastChar = '\0'; - - std::string result(buffer); - - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } -} - -std::wstring getCurrentErrorStringW() -{ - LPWSTR buffer = nullptr; - - DWORD errorCode = ::GetLastError(); - - if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::wstring(); - } else { - LPWSTR lastChar = buffer + wcslen(buffer) - 2; - *lastChar = '\0'; - - std::wstring result(buffer); - - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } -} } // namespace MOShared diff --git a/src/shared/error_report.h b/src/shared/error_report.h index c09ad75b..a003ee09 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -24,28 +24,11 @@ along with Mod Organizer. If not, see . #include #include -namespace std { -#ifdef UNICODE -typedef wstring tstring; -#else -typedef string tstring; -#endif -} - -extern void log(const char* format, ...); - namespace MOShared { void reportError(LPCSTR format, ...); void reportError(LPCWSTR format, ...); -std::string getCurrentErrorStringA(); -std::wstring getCurrentErrorStringW(); - -#ifdef UNICODE -#define getCurrentErrorString getCurrentErrorStringW -#else -#define getCurrentErrorString getCurrentErrorStringA -#endif - } // namespace MOShared + +void vlog(const char* format, ...); -- cgit v1.3.1 From 6217f910f095adea4b06f370726636b09efea4ed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:42:22 -0400 Subject: renamed logbuffer files to loglist --- src/CMakeLists.txt | 6 +- src/logbuffer.cpp | 171 -------------------------------------------------- src/logbuffer.h | 61 ------------------ src/loglist.cpp | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/loglist.h | 61 ++++++++++++++++++ src/main.cpp | 2 +- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 1 - 8 files changed, 237 insertions(+), 238 deletions(-) delete mode 100644 src/logbuffer.cpp delete mode 100644 src/logbuffer.h create mode 100644 src/loglist.cpp create mode 100644 src/loglist.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a359b8a9..9dbab132 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,7 +73,7 @@ SET(organizer_SRCS mainwindow.cpp main.cpp loghighlighter.cpp - logbuffer.cpp + loglist.cpp lockeddialogbase.cpp lockeddialog.cpp waitingonclosedialog.cpp @@ -181,7 +181,7 @@ SET(organizer_HDRS messagedialog.h mainwindow.h loghighlighter.h - logbuffer.h + loglist.h lockeddialogbase.h lockeddialog.h waitingonclosedialog.h @@ -436,7 +436,7 @@ set(widgets filterwidget icondelegate lcdnumber - logbuffer + loglist loghighlighter modflagicondelegate modidlineedit diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp deleted file mode 100644 index 9e3cd712..00000000 --- a/src/logbuffer.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "logbuffer.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace MOBase; - -static LogModel* g_instance = nullptr; -const std::size_t MaxLines = 1000; - -LogModel::LogModel() -{ - connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); -} - -void LogModel::create() -{ - g_instance = new LogModel; -} - -LogModel& LogModel::instance() -{ - return *g_instance; -} - -void LogModel::add(MOBase::log::Entry e) -{ - emit entryAdded(std::move(e)); -} - -void LogModel::onEntryAdded(MOBase::log::Entry e) -{ - bool full = false; - if (m_messages.size() > MaxLines) { - m_messages.pop_front(); - full = true; - } - - const int row = static_cast(m_messages.size()); - - if (!full) { - beginInsertRows(QModelIndex(), row, row + 1); - } - - m_messages.emplace_back(std::move(e)); - - if (!full) { - endInsertRows(); - } else { - emit dataChanged( - createIndex(row, 0), - createIndex(row + 1, columnCount({}))); - } -} - -QModelIndex LogModel::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - -QModelIndex LogModel::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -int LogModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - return 0; - else - return static_cast(m_messages.size()); -} - -int LogModel::columnCount(const QModelIndex&) const -{ - return 3; -} - -QVariant LogModel::data(const QModelIndex& index, int role) const -{ - using namespace std::chrono; - - const auto row = static_cast(index.row()); - if (row >= m_messages.size()) { - return {}; - } - - const auto& e = m_messages[row]; - - if (role == Qt::DisplayRole) { - if (index.column() == 1) { - const auto ms = duration_cast(e.time.time_since_epoch()); - const auto s = duration_cast(ms); - - const std::time_t t = s.count(); - const std::size_t frac = ms.count() % 1000; - - auto time = QDateTime::fromTime_t(t).time(); - time = time.addMSecs(frac); - - return time.toString("hh:mm:ss.zzz"); - } else if (index.column() == 2) { - return QString::fromStdString(e.message); - } - } - - if (role == Qt::DecorationRole) { - if (index.column() == 0) { - switch (e.level) { - case log::Warning: - return QIcon(":/MO/gui/warning"); - - case log::Error: - return QIcon(":/MO/gui/problem"); - - case log::Debug: // fall-through - case log::Info: - default: - return {}; - } - } - } - - return QVariant(); -} - -QVariant LogModel::headerData(int, Qt::Orientation, int) const -{ - return {}; -} - -void vlog(const char *format, ...) -{ - va_list argList; - va_start(argList, format); - - static const int BUFFERSIZE = 1000; - - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; - - vsnprintf(buffer, BUFFERSIZE, format, argList); - - qCritical("%s", buffer); - - va_end(argList); -} diff --git a/src/logbuffer.h b/src/logbuffer.h deleted file mode 100644 index 1bf8901b..00000000 --- a/src/logbuffer.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef LOGBUFFER_H -#define LOGBUFFER_H - -#include -#include -#include -#include -#include -#include -#include - -class LogModel : public QAbstractItemModel -{ - Q_OBJECT - -public: - static void create(); - static LogModel& instance(); - - void add(MOBase::log::Entry e); - -protected: - QModelIndex index(int row, int column, const QModelIndex& parent) const override; - QModelIndex parent(const QModelIndex &child) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - - QVariant headerData( - int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; - -signals: - void entryAdded(MOBase::log::Entry e); - -private: - std::deque m_messages; - - LogModel(); - void onEntryAdded(MOBase::log::Entry e); -}; - -#endif // LOGBUFFER_H diff --git a/src/loglist.cpp b/src/loglist.cpp new file mode 100644 index 00000000..cb927272 --- /dev/null +++ b/src/loglist.cpp @@ -0,0 +1,171 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "loglist.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; + +static LogModel* g_instance = nullptr; +const std::size_t MaxLines = 1000; + +LogModel::LogModel() +{ + connect(this, &LogModel::entryAdded, [&](auto&& e){ onEntryAdded(e); }); +} + +void LogModel::create() +{ + g_instance = new LogModel; +} + +LogModel& LogModel::instance() +{ + return *g_instance; +} + +void LogModel::add(MOBase::log::Entry e) +{ + emit entryAdded(std::move(e)); +} + +void LogModel::onEntryAdded(MOBase::log::Entry e) +{ + bool full = false; + if (m_messages.size() > MaxLines) { + m_messages.pop_front(); + full = true; + } + + const int row = static_cast(m_messages.size()); + + if (!full) { + beginInsertRows(QModelIndex(), row, row + 1); + } + + m_messages.emplace_back(std::move(e)); + + if (!full) { + endInsertRows(); + } else { + emit dataChanged( + createIndex(row, 0), + createIndex(row + 1, columnCount({}))); + } +} + +QModelIndex LogModel::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + +QModelIndex LogModel::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +int LogModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + else + return static_cast(m_messages.size()); +} + +int LogModel::columnCount(const QModelIndex&) const +{ + return 3; +} + +QVariant LogModel::data(const QModelIndex& index, int role) const +{ + using namespace std::chrono; + + const auto row = static_cast(index.row()); + if (row >= m_messages.size()) { + return {}; + } + + const auto& e = m_messages[row]; + + if (role == Qt::DisplayRole) { + if (index.column() == 1) { + const auto ms = duration_cast(e.time.time_since_epoch()); + const auto s = duration_cast(ms); + + const std::time_t t = s.count(); + const std::size_t frac = ms.count() % 1000; + + auto time = QDateTime::fromTime_t(t).time(); + time = time.addMSecs(frac); + + return time.toString("hh:mm:ss.zzz"); + } else if (index.column() == 2) { + return QString::fromStdString(e.message); + } + } + + if (role == Qt::DecorationRole) { + if (index.column() == 0) { + switch (e.level) { + case log::Warning: + return QIcon(":/MO/gui/warning"); + + case log::Error: + return QIcon(":/MO/gui/problem"); + + case log::Debug: // fall-through + case log::Info: + default: + return {}; + } + } + } + + return QVariant(); +} + +QVariant LogModel::headerData(int, Qt::Orientation, int) const +{ + return {}; +} + +void vlog(const char *format, ...) +{ + va_list argList; + va_start(argList, format); + + static const int BUFFERSIZE = 1000; + + char buffer[BUFFERSIZE + 1]; + buffer[BUFFERSIZE] = '\0'; + + vsnprintf(buffer, BUFFERSIZE, format, argList); + + qCritical("%s", buffer); + + va_end(argList); +} diff --git a/src/loglist.h b/src/loglist.h new file mode 100644 index 00000000..1bf8901b --- /dev/null +++ b/src/loglist.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef LOGBUFFER_H +#define LOGBUFFER_H + +#include +#include +#include +#include +#include +#include +#include + +class LogModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + static void create(); + static LogModel& instance(); + + void add(MOBase::log::Entry e); + +protected: + QModelIndex index(int row, int column, const QModelIndex& parent) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QVariant headerData( + int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; + +signals: + void entryAdded(MOBase::log::Entry e); + +private: + std::deque m_messages; + + LogModel(); + void onEntryAdded(MOBase::log::Entry e); +}; + +#endif // LOGBUFFER_H diff --git a/src/main.cpp b/src/main.cpp index 23ea234a..8b5648c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,7 +39,7 @@ along with Mod Organizer. If not, see . #include "singleinstance.h" #include "utility.h" #include "helper.h" -#include "logbuffer.h" +#include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" #include "tutorialmanager.h" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cd224414..107f3e09 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,7 +59,7 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" #include "lockeddialog.h" #include "waitingonclosedialog.h" -#include "logbuffer.h" +#include "loglist.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5f5c3afe..d3cd54ee 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -14,7 +14,6 @@ #include "plugincontainer.h" #include "pluginlistsortproxy.h" #include "profile.h" -#include "logbuffer.h" #include "credentialsdialog.h" #include "filedialogmemory.h" #include "modinfodialog.h" -- cgit v1.3.1 From d1b4dec8ad1635738ada3dfbde5907e7f0df3448 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 16 Jul 2019 05:58:51 -0400 Subject: moved setup to new LogList class --- src/loglist.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++++++------- src/loglist.h | 14 +++++++++++-- src/mainwindow.cpp | 35 ++----------------------------- src/mainwindow.h | 1 - src/mainwindow.ui | 10 +++++---- 5 files changed, 74 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index cb927272..9e876d37 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -52,21 +52,26 @@ void LogModel::add(MOBase::log::Entry e) emit entryAdded(std::move(e)); } +const std::deque& LogModel::entries() const +{ + return m_entries; +} + void LogModel::onEntryAdded(MOBase::log::Entry e) { bool full = false; - if (m_messages.size() > MaxLines) { - m_messages.pop_front(); + if (m_entries.size() > MaxLines) { + m_entries.pop_front(); full = true; } - const int row = static_cast(m_messages.size()); + const int row = static_cast(m_entries.size()); if (!full) { beginInsertRows(QModelIndex(), row, row + 1); } - m_messages.emplace_back(std::move(e)); + m_entries.emplace_back(std::move(e)); if (!full) { endInsertRows(); @@ -92,7 +97,7 @@ int LogModel::rowCount(const QModelIndex& parent) const if (parent.isValid()) return 0; else - return static_cast(m_messages.size()); + return static_cast(m_entries.size()); } int LogModel::columnCount(const QModelIndex&) const @@ -105,11 +110,11 @@ QVariant LogModel::data(const QModelIndex& index, int role) const using namespace std::chrono; const auto row = static_cast(index.row()); - if (row >= m_messages.size()) { + if (row >= m_entries.size()) { return {}; } - const auto& e = m_messages[row]; + const auto& e = m_entries[row]; if (role == Qt::DisplayRole) { if (index.column() == 1) { @@ -153,6 +158,48 @@ QVariant LogModel::headerData(int, Qt::Orientation, int) const return {}; } + +LogList::LogList(QWidget* parent) + : QTreeView(parent) +{ + setModel(&LogModel::instance()); + + const int timestampWidth = QFontMetrics(font()).width("00:00:00.000"); + + header()->setMinimumSectionSize(0); + header()->resizeSection(0, 20); + header()->resizeSection(1, timestampWidth + 8); + + setAutoScroll(true); + scrollToBottom(); + + connect( + model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(scrollToBottom())); + + connect( + model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + this, SLOT(scrollToBottom())); +} + +void LogList::copyToClipboard() +{ + std::string s; + + auto* m = static_cast(model()); + for (const auto& e : m->entries()) { + s += e.formattedMessage + "\n"; + } + + if (!s.empty()) { + // last newline + s.pop_back(); + } + + QApplication::clipboard()->setText(QString::fromStdString(s)); +} + + void vlog(const char *format, ...) { va_list argList; diff --git a/src/loglist.h b/src/loglist.h index 1bf8901b..d1f7a2ad 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -37,6 +37,7 @@ public: static LogModel& instance(); void add(MOBase::log::Entry e); + const std::deque& entries() const; protected: QModelIndex index(int row, int column, const QModelIndex& parent) const override; @@ -44,7 +45,7 @@ protected: int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; - + QVariant headerData( int section, Qt::Orientation ori, int role=Qt::DisplayRole) const override; @@ -52,10 +53,19 @@ signals: void entryAdded(MOBase::log::Entry e); private: - std::deque m_messages; + std::deque m_entries; LogModel(); void onEntryAdded(MOBase::log::Entry e); }; + +class LogList : public QTreeView +{ +public: + LogList(QWidget* parent=nullptr); + + void copyToClipboard(); +}; + #endif // LOGBUFFER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 107f3e09..4e91ef9f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -357,7 +357,7 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - setupLogList(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); @@ -586,30 +586,6 @@ MainWindow::MainWindow(QSettings &initSettings updateModCount(); } -void MainWindow::setupLogList() -{ - ui->logList->setModel(&LogModel::instance()); - - const int timestampWidth = - QFontMetrics(ui->logList->font()).width("00:00:00.000"); - - ui->logList->header()->setMinimumSectionSize(0); - ui->logList->header()->resizeSection(0, 20); - ui->logList->header()->resizeSection(1, timestampWidth + 8); - - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); - - connect( - ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), - ui->logList, SLOT(scrollToBottom())); - - connect( - ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), - ui->logList, SLOT(scrollToBottom())); -} - void MainWindow::resetActionIcons() { // this is a bit of a hack @@ -6837,14 +6813,7 @@ void MainWindow::on_restoreModsButton_clicked() void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() { - QStringList lines; - QAbstractItemModel *model = ui->logList->model(); - for (int i = 0; i < model->rowCount(); ++i) { - lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString()) - .arg(model->index(i, 1).data(Qt::UserRole).toString()) - .arg(model->index(i, 1).data().toString())); - } - QApplication::clipboard()->setText(lines.join("\n")); + ui->logList->copyToClipboard(); } void MainWindow::on_categoriesAndBtn_toggled(bool checked) diff --git a/src/mainwindow.h b/src/mainwindow.h index 9ca3e5c3..d7dbfd90 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,7 +636,6 @@ private slots: void resetActionIcons(); void updateModCount(); void updatePluginCount(); - void setupLogList(); private slots: // ui slots // actions diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5a45b3b4..c694abd4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1426,13 +1426,10 @@ p, li { white-space: pre-wrap; } 0 - + Qt::ActionsContextMenu - - QAbstractItemView::ExtendedSelection - false @@ -1807,6 +1804,11 @@ p, li { white-space: pre-wrap; } QWidget
sortabletreewidget.h
+ + LogList + QTreeView +
loglist.h
+
-- cgit v1.3.1 From bca6283311cf1dea4c96f8ee5bf192bdb1640cb3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 08:56:16 -0400 Subject: use log::Levels instead of ints create log level combobox in code, set selected index based on value instead added log level to context menu in log list --- src/mainwindow.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++--- src/mainwindow.h | 12 +++++++++--- src/mainwindow.ui | 30 +++++++++++++++--------------- src/organizercore.cpp | 4 +++- src/organizercore.h | 5 ++++- src/settings.cpp | 30 ++++++++++++++++++++++++++---- src/settings.h | 10 +++++++++- src/settingsdialog.ui | 20 -------------------- src/usvfsconnector.cpp | 20 +++++++++++--------- src/usvfsconnector.h | 7 ++++++- 10 files changed, 127 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4e91ef9f..f65bf4e1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -357,7 +357,7 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + setupLogMenu(); int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); @@ -812,6 +812,36 @@ void MainWindow::setupActionMenu(QAction* a) tb->setPopupMode(QToolButton::InstantPopup); } +void MainWindow::setupLogMenu() +{ + connect(ui->logList, &QWidget::customContextMenuRequested, [&](auto&& pos){ + auto* menu = new QMenu(ui->logList); + + menu->addAction(tr("Copy& Log"), [&]{ ui->logList->copyToClipboard(); }); + menu->addSeparator(); + + auto* levels = new QMenu(tr("&Level")); + menu->addMenu(levels); + + auto* ag = new QActionGroup(menu); + + auto addAction = [&](auto&& text, auto&& level) { + auto* a = new QAction(text, ag); + a->setCheckable(true); + a->setChecked(log::getDefault().level() == level); + connect(a, &QAction::triggered, [this, level]{ setLogLevel(level); }); + levels->addAction(a); + }; + + addAction(tr("&Errors"), log::Error); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Info"), log::Info); + addAction(tr("&Debug"), log::Debug); + + menu->popup(ui->logList->viewport()->mapToGlobal(pos)); + }); +} + void MainWindow::updatePinnedExecutables() { for (auto* a : ui->toolBar->actions()) { @@ -5287,12 +5317,23 @@ void MainWindow::on_actionSettings_triggered() m_statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType(), settings.executablesBlacklist()); + setLogLevel(settings.logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); } +void MainWindow::setLogLevel(log::Levels level) +{ + auto& s = m_OrganizerCore.settings(); + + s.setLogLevel(level); + + m_OrganizerCore.updateVFSParams( + s.logLevel(), s.crashDumpsType(), s.executablesBlacklist()); + + log::getDefault().setLevel(s.logLevel()); +} void MainWindow::on_actionNexus_triggered() { @@ -6811,7 +6852,7 @@ void MainWindow::on_restoreModsButton_clicked() } } -void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() +void MainWindow::on_actionLogCopy_triggered() { ui->logList->copyToClipboard(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index d7dbfd90..74993667 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -30,6 +30,9 @@ along with Mod Organizer. If not, see . #include "modlistsortproxy.h" #include "savegameinfo.h" #include "tutorialcontrol.h" +#include "plugincontainer.h" //class PluginContainer; +#include "iplugingame.h" //namespace MOBase { class IPluginGame; } +#include //Note the commented headers here can be replaced with forward references, //when I get round to cleaning up main.cpp @@ -38,10 +41,10 @@ class CategoryFactory; class LockedDialogBase; class OrganizerCore; class StatusBar; -#include "plugincontainer.h" //class PluginContainer; + class PluginListSortProxy; namespace BSA { class Archive; } -#include "iplugingame.h" //namespace MOBase { class IPluginGame; } + namespace MOBase { class IPluginModPage; } namespace MOBase { class IPluginTool; } namespace MOBase { class ISaveGame; } @@ -633,10 +636,13 @@ private slots: void search_activated(); void searchClear_activated(); + void setupLogMenu(); void resetActionIcons(); void updateModCount(); void updatePluginCount(); + void setLogLevel(MOBase::log::Levels level); + private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); @@ -690,7 +696,7 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_actionCopy_Log_to_Clipboard_triggered(); + void on_actionLogCopy_triggered(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c694abd4..fc2bcdd3 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1428,7 +1428,7 @@ p, li { white-space: pre-wrap; } - Qt::ActionsContextMenu + Qt::CustomContextMenu false @@ -1645,20 +1645,6 @@ p, li { white-space: pre-wrap; } Endorse Mod Organizer - - - Copy &Log - - - Copy &Log - - - Copy log to clipboard - - - Copy log to clipboard - - @@ -1771,6 +1757,20 @@ p, li { white-space: pre-wrap; } Log + + + Copy &Log + + + Copy &Log + + + Copy log to clipboard + + + Copy log to clipboard + + diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d3cd54ee..25fbc7cd 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -726,7 +726,9 @@ void OrganizerCore::prepareVFS() m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString())); } -void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist) { +void OrganizerCore::updateVFSParams( + log::Levels logLevel, int crashDumpsType, QString executableBlacklist) +{ setGlobalCrashDumpsType(crashDumpsType); m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); } diff --git a/src/organizercore.h b/src/organizercore.h index 99b1c5f2..ef1a4133 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -21,6 +21,7 @@ #include #include #include "executableinfo.h" +#include class ModListSortProxy; class PluginListSortProxy; @@ -191,7 +192,9 @@ public: void prepareVFS(); - void updateVFSParams(int logLevel, int crashDumpsType, QString executableBlacklist); + void updateVFSParams( + MOBase::log::Levels logLevel, int crashDumpsType, + QString executableBlacklist); bool cycleDiagnostics(); diff --git a/src/settings.cpp b/src/settings.cpp index 5cb2524f..e622d632 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -415,9 +415,14 @@ bool Settings::offlineMode() const return m_Settings.value("Settings/offline_mode", false).toBool(); } -int Settings::logLevel() const +log::Levels Settings::logLevel() const { - return m_Settings.value("Settings/log_level", static_cast(LogLevel::Info)).toInt(); + return static_cast(m_Settings.value("Settings/log_level").toInt()); +} + +void Settings::setLogLevel(log::Levels level) +{ + m_Settings.setValue("Settings/log_level", static_cast(level)); } int Settings::crashDumpsType() const @@ -1000,7 +1005,7 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d , m_dumpsMaxEdit(m_dialog.findChild("dumpsMaxEdit")) , m_diagnosticsExplainedLabel(m_dialog.findChild("diagnosticsExplainedLabel")) { - m_logLevelBox->setCurrentIndex(m_parent->logLevel()); + setLevelsBox(); m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() @@ -1016,11 +1021,28 @@ Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_d void Settings::DiagnosticsTab::update() { - m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); + m_Settings.setValue("Settings/log_level", m_logLevelBox->currentData().toInt()); m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); } +void Settings::DiagnosticsTab::setLevelsBox() +{ + m_logLevelBox->clear(); + + m_logLevelBox->addItem(tr("Debug"), log::Debug); + m_logLevelBox->addItem(tr("Info (recommended)"), log::Info); + m_logLevelBox->addItem(tr("Warning"), log::Warning); + m_logLevelBox->addItem(tr("Error"), log::Error); + + for (int i=0; icount(); ++i) { + if (m_logLevelBox->itemData(i) == m_parent->logLevel()) { + m_logLevelBox->setCurrentIndex(i); + break; + } + } +} + Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) : Settings::SettingsTab(parent, dialog) , m_offlineBox(dialog.findChild("offlineBox")) diff --git a/src/settings.h b/src/settings.h index bccd1e81..c66eb94c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGS_H #include "loadmechanism.h" +#include #include #include @@ -232,7 +233,12 @@ public: /** * @return the configured log level */ - int logLevel() const; + MOBase::log::Levels logLevel() const; + + /** + * sets the log level setting + */ + void setLogLevel(MOBase::log::Levels level); /** * @return the configured crash dumps type @@ -481,6 +487,8 @@ private: QComboBox *m_dumpsTypeBox; QSpinBox *m_dumpsMaxEdit; QLabel *m_diagnosticsExplainedLabel; + + void setLevelsBox(); }; /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index fccc8be0..1e94bcde 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1426,26 +1426,6 @@ programs you are intentionally running. "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. - - - Debug - - - - - Info (recommended) - - - - - Warning - - - - - Error - - diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index b752667d..197955b8 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -32,7 +32,7 @@ along with Mod Organizer. If not, see . #include static const char SHMID[] = "mod_organizer_instance"; - +using namespace MOBase; std::string to_hex(void *bufferIn, size_t bufferSize) { @@ -90,15 +90,16 @@ void LogWorker::exit() m_QuitRequested = true; } -LogLevel logLevel(int level) +LogLevel toUsvfsLogLevel(log::Levels level) { - switch (static_cast(level)) { - case LogLevel::Info: + switch (level) { + case log::Info: return LogLevel::Info; - case LogLevel::Warning: + case log::Warning: return LogLevel::Warning; - case LogLevel::Error: + case log::Error: return LogLevel::Error; + case log::Debug: // fall-through default: return LogLevel::Debug; } @@ -121,7 +122,7 @@ CrashDumpsType crashDumpsType(int type) UsvfsConnector::UsvfsConnector() { USVFSParameters params; - LogLevel level = logLevel(Settings::instance().logLevel()); + LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); @@ -205,9 +206,10 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) */ } -void UsvfsConnector::updateParams(int logLevel, int crashDumpsType, QString executableBlacklist) +void UsvfsConnector::updateParams( + MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist) { - USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType)); + USVFSUpdateParams(toUsvfsLogLevel(logLevel), ::crashDumpsType(crashDumpsType)); ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index 8a88bde5..b0bd320c 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include "executableinfo.h" @@ -84,7 +85,11 @@ public: ~UsvfsConnector(); void updateMapping(const MappingType &mapping); - void updateParams(int logLevel, int crashDumpsType, QString executableBlacklist); + + void updateParams( + MOBase::log::Levels logLevel, int crashDumpsType, + QString executableBlacklist); + void updateForcedLibraries(const QList &forcedLibraries); private: -- cgit v1.3.1 From ad77e315f5c53994d75056608df0f9ff0a390530 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 09:10:14 -0400 Subject: moved Console to util --- src/main.cpp | 39 +-------------------------------------- src/shared/util.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 14 ++++++++++++++ 3 files changed, 58 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 8b5648c9..65f4bd05 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -732,46 +732,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } -class Console -{ -public: - Console() - { - // open a console - AllocConsole(); - - // redirect stdin, stdout and stderr to it - freopen_s(&m_in, "CONIN$", "r", stdin); - freopen_s(&m_out, "CONOUT$", "w", stdout); - freopen_s(&m_err, "CONOUT$", "w", stderr); - } - - ~Console() - { - // close redirected handles - std::fclose(m_err); - std::fclose(m_out); - std::fclose(m_in); - - // close console - FreeConsole(); - - // redirect stdin, stdout and stderr to NUL, don't bother closing the - // handles - freopen_s(&m_in, "NUL", "r", stdin); - freopen_s(&m_out, "NUL", "w", stdout); - freopen_s(&m_err, "NUL", "w", stderr); - } - -private: - FILE* m_in = nullptr; - FILE* m_out = nullptr; - FILE* m_err = nullptr; -}; - int doCoreDump(env::CoreDumpTypes type) { - Console c; + env::Console c; // dump const auto b = env::coredumpOther(type); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 17df3b92..29e52f40 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -441,6 +441,49 @@ private: }; +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + Shortcut::Shortcut() : m_iconIndex(0) { diff --git a/src/shared/util.h b/src/shared/util.h index c4a2ed7d..a5d096ac 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -54,6 +54,20 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); namespace env { +class Console +{ +public: + Console(); + ~Console(); + +private: + bool m_hasConsole; + FILE* m_in; + FILE* m_out; + FILE* m_err; +}; + + // an application shortcut that can be either on the desktop or the start menu // class Shortcut -- cgit v1.3.1 From 4dfaa363c05eb7691e2c7ea755c35758b62e0fc9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:00:47 -0400 Subject: logging initialized early, log file set later replaced a few qDebug() --- src/loglist.cpp | 8 +++--- src/main.cpp | 80 +++++++++++++++++++++++++++++++-------------------------- 2 files changed, 47 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index 9e876d37..207f412b 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -121,12 +121,10 @@ QVariant LogModel::data(const QModelIndex& index, int role) const const auto ms = duration_cast(e.time.time_since_epoch()); const auto s = duration_cast(ms); - const std::time_t t = s.count(); - const std::size_t frac = ms.count() % 1000; - - auto time = QDateTime::fromTime_t(t).time(); - time = time.addMSecs(frac); + const std::time_t tt = s.count(); + const int frac = static_cast(ms.count() % 1000); + const auto time = QDateTime::fromTime_t(tt).time().addMSecs(frac); return time.toString("hh:mm:ss.zzz"); } else if (index.column() == 2) { return QString::fromStdString(e.message); diff --git a/src/main.cpp b/src/main.cpp index 65f4bd05..f55c32f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -253,17 +253,17 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - qDebug("profile overwritten on command line"); + log::debug("profile overwritten on command line"); selectedProfileName = arguments.at(profileIndex + 1); } arguments.removeAt(profileIndex); arguments.removeAt(profileIndex); } if (selectedProfileName.isEmpty()) { - qDebug("no configured profile"); + log::debug("no configured profile"); selectedProfileName = "Default"; } else { - qDebug("configured profile: %s", qUtf8Printable(selectedProfileName)); + log::debug("configured profile: {}", selectedProfileName); } return selectedProfileName; @@ -425,8 +425,7 @@ void setupPath() { static const int BUFSIZE = 4096; - qDebug("MO at: %s", qUtf8Printable(QDir::toNativeSeparators( - QCoreApplication::applicationDirPath()))); + log::debug("MO at {}", QCoreApplication::applicationDirPath()); QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); @@ -447,7 +446,7 @@ void setupPath() void preloadDll(const QString& filename) { - qDebug().nospace() << "preloading " << filename; + log::debug("preloading {}", filename); if (GetModuleHandleW(filename.toStdWString().c_str())) { // already loaded, this can happen when "restarting" MO by switching @@ -490,41 +489,43 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } +void dumpEnvironment() +{ + env::Environment env; + + log::debug("windows: {}", env.windowsInfo().toString()); + + if (env.windowsInfo().compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : env.securityProducts()) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : env.loadedModules()) { + log::debug(" . {}", m.toString()); + } +} + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { - qDebug().nospace() - << "Starting Mod Organizer version " - << getVersionDisplayString() << " revision " << GITID; + log::info( + "Starting Mod Organizer version {} revision {}", + getVersionDisplayString(), GITID); #if !defined(QT_NO_SSL) preloadSsl(); - qDebug("ssl support: %d", QSslSocket::supportsSsl()); + log::info("ssl support: {}", QSslSocket::supportsSsl()); #else - qDebug("non-ssl build"); + log::info("non-ssl build"); #endif - { - env::Environment env; - - qDebug().nospace().noquote() - << "windows: " << env.windowsInfo().toString(); - - if (env.windowsInfo().compatibilityMode()) { - qWarning() << "MO seems to be running in compatibility mode"; - } - - qDebug().nospace().noquote() << "security products:"; - for (const auto& sp : env.securityProducts()) { - qDebug().nospace().noquote() << " . " << sp.toString(); - } - - qDebug() << "modules loaded in process:"; - for (const auto& m : env.loadedModules()) { - qDebug().nospace().noquote() << " . " << m.toString(); - } - } + dumpEnvironment(); QString dataPath = application.property("dataPath").toString(); qDebug("data path: %s", qUtf8Printable(dataPath)); @@ -795,18 +796,19 @@ void qtLogCallback( } } -void initLogging(const QString& logFile) +void initLogging() { LogModel::create(); - log::init( - true, MOBase::log::File::rotating(logFile.toStdWString(), 5*1024*1024, 5), - MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$", + log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$"); + + log::getDefault().setCallback( [](log::Entry e){ LogModel::instance().add(e); }); qInstallMessageHandler(qtLogCallback); } + int main(int argc, char *argv[]) { // handle --crashdump first @@ -820,6 +822,8 @@ int main(int argc, char *argv[]) } } + initLogging(); + //Make sure the configured temp folder exists QDir tempDir = QDir::temp(); if (!tempDir.exists()) @@ -882,7 +886,11 @@ int main(int argc, char *argv[]) // initialize dump collection only after "dataPath" since the crashes are stored under it prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - initLogging(qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + const auto logFile = + qApp->property("dataPath").toString() + "/logs/mo_interface.log"; + + log::getDefault().setFile(MOBase::log::File::rotating( + logFile.toStdWString(), 5*1024*1024, 5)); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { -- cgit v1.3.1 From 3bf7717c0a8507c9befce6b74c84c4dbdcac99de Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:41:09 -0400 Subject: moved Settings out of OrganizerCore so it can be created by itself to access settings early set log level on startup replaced more qDebug() --- src/main.cpp | 100 +++++++++++++++++++++++++------------------------- src/organizercore.cpp | 6 +-- src/organizercore.h | 4 +- src/shared/util.cpp | 4 +- 4 files changed, 57 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index f55c32f2..aa842ead 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -425,8 +425,6 @@ void setupPath() { static const int BUFSIZE = 4096; - log::debug("MO at {}", QCoreApplication::applicationDirPath()); - QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); boost::scoped_array oldPath(new TCHAR[BUFSIZE]); @@ -446,8 +444,6 @@ void setupPath() void preloadDll(const QString& filename) { - log::debug("preloading {}", filename); - if (GetModuleHandleW(filename.toStdWString().c_str())) { // already loaded, this can happen when "restarting" MO by switching // instances, for example @@ -513,62 +509,68 @@ void dumpEnvironment() int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { - log::info( - "Starting Mod Organizer version {} revision {}", - getVersionDisplayString(), GITID); + "Starting Mod Organizer version {} revision {} in {}", + getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); -#if !defined(QT_NO_SSL) preloadSsl(); - log::info("ssl support: {}", QSslSocket::supportsSsl()); -#else - log::info("non-ssl build"); -#endif - - dumpEnvironment(); + if (!QSslSocket::supportsSsl()) { + log::warn("no ssl support"); + } QString dataPath = application.property("dataPath").toString(); - qDebug("data path: %s", qUtf8Printable(dataPath)); + log::info("data path: {}", dataPath); if (!bootstrap()) { reportError("failed to set up data paths"); return 1; } - QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); + QWindowsWindowFunctions::setWindowActivationBehavior( + QWindowsWindowFunctions::AlwaysActivateWindow); QStringList arguments = application.arguments(); try { - qDebug("Working directory: %s", qUtf8Printable(QDir::toNativeSeparators(QDir::currentPath()))); + log::info("working directory: {}", QDir::currentPath()); - QSettings settings(dataPath + "/" - + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); + QSettings initSettings( + dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), + QSettings::IniFormat); - // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); + Settings settings(initSettings); + log::getDefault().setLevel(settings.logLevel()); - qDebug("Loaded settings:"); - settings.beginGroup("Settings"); - for (auto k : settings.allKeys()) - if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) - qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); - settings.endGroup(); + dumpEnvironment(); + // global crashDumpType sits in OrganizerCore to make a bit less ugly to + // update it when the settings are changed during runtime + OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - qDebug("initializing core"); + log::debug("Loaded settings:"); + + initSettings.beginGroup("Settings"); + for (auto k : initSettings.allKeys()) { + if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) { + log::debug(" {}={}", k, initSettings.value(k).toString()); + } + } + initSettings.endGroup(); + + + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { reportError("failed to set up data paths"); return 1; } - qDebug("initialize plugins"); + + log::debug("initializing plugins"); PluginContainer pluginContainer(&organizer); pluginContainer.loadPlugins(); MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), settings, pluginContainer); + application.applicationDirPath(), initSettings, pluginContainer); if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); @@ -586,14 +588,14 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (!image.isNull()) { image.save(dataPath + "/splash.png"); } else { - qDebug("no plugin splash"); + log::debug("no plugin splash"); } } organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!settings.contains("game_edition")) { + if (!initSettings.contains("game_edition")) { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -609,18 +611,17 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - settings.setValue("game_edition", selection.getChoiceString()); + initSettings.setValue("game_edition", selection.getChoiceString()); } } } - game->setGameVariant(settings.value("game_edition").toString()); + game->setGameVariant(initSettings.value("game_edition").toString()); - qDebug("managing game at %s", qUtf8Printable(QDir::toNativeSeparators( - game->gameDirectory().absolutePath()))); + log::info("managing game at {}", game->gameDirectory().absolutePath()); - organizer.updateExecutablesList(settings); + organizer.updateExecutablesList(initSettings); - QString selectedProfileName = determineProfile(arguments, settings); + QString selectedProfileName = determineProfile(arguments, initSettings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or @@ -640,13 +641,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } else if (OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", - qUtf8Printable(arguments.at(1))); + log::debug("starting download from command line: {}", arguments.at(1)); organizer.externalMessage(arguments.at(1)); } else { QString exeName = arguments.at(1); - qDebug("starting %s from command line", qUtf8Printable(exeName)); + log::debug("starting {} from command line", exeName); arguments.removeFirst(); // remove application name (ModOrganizer.exe) arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary @@ -665,8 +665,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - if (settings.contains("window_monitor")) { - const int monitor = settings.value("window_monitor").toInt(); + if (initSettings.contains("window_monitor")) { + const int monitor = initSettings.value("window_monitor").toInt(); if (monitor != -1) { QDesktopWidget* desktop = QApplication::desktop(); @@ -683,21 +683,21 @@ int runApplication(MOApplication &application, SingleInstance &instance, NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } - qDebug("initializing tutorials"); + log::debug("initializing tutorials"); TutorialManager::init( qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { + if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) { // disable invalid stylesheet - settings.setValue("Settings/style", ""); + initSettings.setValue("Settings/style", ""); } int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(settings, organizer, pluginContainer); + MainWindow mainWindow(initSettings, organizer, pluginContainer); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(&mainWindow); @@ -714,7 +714,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, mainWindow.readSettings(); - qDebug("displaying main window"); + log::debug("displaying main window"); mainWindow.show(); mainWindow.activateWindow(); @@ -857,7 +857,7 @@ int main(int argc, char *argv[]) if (moshortcut || arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending shortcut/download message"); + log::debug("not primary instance, sending shortcut/download message"); instance.sendMessage(arguments.at(1)); return 0; } else if (arguments.size() == 1) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 25fbc7cd..400f5391 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -268,12 +268,12 @@ bool checkService() } -OrganizerCore::OrganizerCore(const QSettings &initSettings) +OrganizerCore::OrganizerCore(Settings &settings) : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) - , m_Settings(initSettings) + , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() , m_FinishedRun() @@ -294,7 +294,7 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); - MOBase::QuestionBoxMemory::init(initSettings.fileName()); + MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName()); m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); diff --git a/src/organizercore.h b/src/organizercore.h index ef1a4133..c368d101 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -97,7 +97,7 @@ public: static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } - OrganizerCore(const QSettings &initSettings); + OrganizerCore(Settings &settings); ~OrganizerCore(); @@ -336,7 +336,7 @@ private: Profile *m_CurrentProfile; - Settings m_Settings; + Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 29e52f40..8d8c2000 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1492,11 +1492,11 @@ QString WindowsInfo::toString() const const QString real = m_real.toString(); // version - sl.push_back("version: " + reported); + sl.push_back("version " + reported); // real version if different if (compatibilityMode()) { - sl.push_back("real version: " + real); + sl.push_back("real version " + real); } // build.UBR, such as 17763.557 -- cgit v1.3.1 From 28c46eed919cf3044147f642ea0a8d9909fea2ea Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 11:53:25 -0400 Subject: reversed log level menu actions to fit settings combobox replaced qWarnings() and qCritical() --- src/main.cpp | 11 ++++------- src/mainwindow.cpp | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index aa842ead..b89e4a80 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -133,9 +133,9 @@ static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *except int dumpRes = CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); if (!dumpRes) - qCritical("ModOrganizer has crashed, crash dump created."); + log::error("ModOrganizer has crashed, crash dump created."); else - qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); + log::error("ModOrganizer has crashed, CreateMiniDump failed ({}, error {}).", dumpRes, GetLastError()); if (prevUnhandledExceptionFilter) return prevUnhandledExceptionFilter(exceptionPtrs); @@ -456,16 +456,13 @@ void preloadDll(const QString& filename) const auto dllPath = appPath + "\\" + filename; if (!QFile::exists(dllPath)) { - qWarning().nospace() << dllPath << "not found"; + log::warn("{} not found", dllPath); return; } if (!LoadLibraryW(dllPath.toStdWString().c_str())) { const auto e = GetLastError(); - - qWarning().nospace() - << "failed to load " << dllPath << ": " - << formatSystemMessage(e); + log::warn("failed to load {}: {}", dllPath, formatSystemMessageQ(e)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f65bf4e1..6c2ab389 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -833,10 +833,10 @@ void MainWindow::setupLogMenu() levels->addAction(a); }; - addAction(tr("&Errors"), log::Error); - addAction(tr("&Warnings"), log::Warning); - addAction(tr("&Info"), log::Info); addAction(tr("&Debug"), log::Debug); + addAction(tr("&Info"), log::Info); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Errors"), log::Error); menu->popup(ui->logList->viewport()->mapToGlobal(pos)); }); -- cgit v1.3.1 From eb190380e3044900a30ba41c81a23d813fd708e9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:15:08 -0400 Subject: dump executables on startup --- src/executableslist.cpp | 30 +++++++++++++++++++++++++++++- src/executableslist.h | 4 ++++ src/main.cpp | 35 +++++++++++++++++++++++------------ 3 files changed, 56 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 077d2a93..0ca880cd 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" #include "utility.h" +#include #include #include @@ -65,7 +66,7 @@ bool ExecutablesList::empty() const void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) { - qDebug("setting up configured executables"); + log::debug("loading executables"); m_Executables.clear(); @@ -103,6 +104,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) if (needsUpgrade) upgradeFromCustom(game); + + dump(); } void ExecutablesList::store(QSettings& settings) @@ -332,6 +335,31 @@ void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game) } } +void ExecutablesList::dump() const +{ + for (const auto& e : m_Executables) { + QStringList flags; + + if (e.flags() & Executable::ShowInToolbar) { + flags.push_back("toolbar"); + } + + if (e.flags() & Executable::UseApplicationIcon) { + flags.push_back("icon"); + } + + log::debug( + " . executable '{}'\n" + " binary: {}\n" + " arguments: {}\n" + " steam ID: {}\n" + " directory: {}\n" + " flags: {} ({})", + e.title(), e.binaryInfo().absoluteFilePath(), e.arguments(), + e.steamAppID(), e.workingDirectory(), flags.join("|"), e.flags()); + } +} + Executable::Executable(QString title) : m_title(title) diff --git a/src/executableslist.h b/src/executableslist.h index 2d1dd28e..eda2034e 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -214,6 +214,10 @@ private: * called when MO is still using the old custom executables from 2.2.0 **/ void upgradeFromCustom(const MOBase::IPluginGame* game); + + // logs all executables + // + void dump() const; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) diff --git a/src/main.cpp b/src/main.cpp index b89e4a80..6b4280b4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -503,6 +503,27 @@ void dumpEnvironment() } } +void dumpSettings(QSettings& settings) +{ + static QStringList ignore({ + "username", "password", "nexus_api_key" + }); + + log::debug("settings:"); + + settings.beginGroup("Settings"); + + for (auto k : settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } + + log::debug(" . {}={}", k, settings.value(k).toString()); + } + + settings.endGroup(); +} + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { @@ -538,22 +559,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, Settings settings(initSettings); log::getDefault().setLevel(settings.logLevel()); - dumpEnvironment(); - // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - log::debug("Loaded settings:"); - - initSettings.beginGroup("Settings"); - for (auto k : initSettings.allKeys()) { - if (!k.contains("username") && !k.contains("password") && !k.contains("nexus_api_key")) { - log::debug(" {}={}", k, initSettings.value(k).toString()); - } - } - initSettings.endGroup(); - + dumpEnvironment(); + dumpSettings(initSettings); log::debug("initializing core"); OrganizerCore organizer(settings); -- cgit v1.3.1 From 35ae099d3fcc6c5b42fbd8d10e5efc2427bcf2dc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:23:26 -0400 Subject: check for files likely to be eaten by an AV on startup --- src/main.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 6b4280b4..15d36428 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -505,7 +505,7 @@ void dumpEnvironment() void dumpSettings(QSettings& settings) { - static QStringList ignore({ + static const QStringList ignore({ "username", "password", "nexus_api_key" }); @@ -524,11 +524,33 @@ void dumpSettings(QSettings& settings) settings.endGroup(); } +void sanityChecks() +{ + // files that are likely to be eaten + static const QStringList files({ + "helper.exe", "nxmhandler.exe", + "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", + "usvfs_x64.dll", "usvfs_x86.dll" + }); + + const auto dir = QCoreApplication::applicationDirPath(); + + for (const auto& name : files) { + const QFileInfo file(dir + QDir::separator() + name); + if (!file.exists()) { + log::warn( + "'{}' seems to be missing, an antivirus may have deleted it", + file.absoluteFilePath()); + } + } +} + + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { log::info( - "Starting Mod Organizer version {} revision {} in {}", + "starting Mod Organizer version {} revision {} in {}", getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); preloadSsl(); @@ -565,6 +587,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, dumpEnvironment(); dumpSettings(initSettings); + sanityChecks(); log::debug("initializing core"); OrganizerCore organizer(settings); -- cgit v1.3.1 From 2ef32d12306ac21c0c900ff8f353ff0b573e67ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 12:39:01 -0400 Subject: moved environment dump to member function added check for nahimic --- src/main.cpp | 51 +++++++++++++++++++++++++++------------------------ src/shared/util.cpp | 25 ++++++++++++++++++++++--- src/shared/util.h | 6 +++++- 3 files changed, 54 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 15d36428..ef698dd4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -482,27 +482,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void dumpEnvironment() -{ - env::Environment env; - - log::debug("windows: {}", env.windowsInfo().toString()); - - if (env.windowsInfo().compatibilityMode()) { - log::warn("MO seems to be running in compatibility mode"); - } - - log::debug("security products:"); - for (const auto& sp : env.securityProducts()) { - log::debug(" . {}", sp.toString()); - } - - log::debug("modules loaded in process:"); - for (const auto& m : env.loadedModules()) { - log::debug(" . {}", m.toString()); - } -} - void dumpSettings(QSettings& settings) { static const QStringList ignore({ @@ -524,7 +503,7 @@ void dumpSettings(QSettings& settings) settings.endGroup(); } -void sanityChecks() +void checkMissingFiles() { // files that are likely to be eaten static const QStringList files({ @@ -545,6 +524,28 @@ void sanityChecks() } } +void checkNahimic(const env::Environment& e) +{ + for (auto&& m : e.loadedModules()) { + const QFileInfo file(m.path()); + + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive)) { + log::warn( + "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "Mod Organizer, such as freezing or blank windows. Consider " + "uninstalling it."); + + break; + } + } +} + +void sanityChecks(const env::Environment& e) +{ + checkMissingFiles(); + checkNahimic(e); +} + int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) @@ -585,9 +586,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); - dumpEnvironment(); + env::Environment env; + + env.dump(); dumpSettings(initSettings); - sanityChecks(); + sanityChecks(env); log::debug("initializing core"); OrganizerCore organizer(settings); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 8d8c2000..eacd1f88 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "executableslist.h" #include "instancemanager.h" #include +#include #include #include @@ -41,8 +42,7 @@ along with Mod Organizer. If not, see . #pragma comment(lib, "Wbemuuid.lib") -using MOBase::formatSystemMessage; -using MOBase::formatSystemMessageQ; +using namespace MOBase; namespace fs = std::filesystem; namespace MOShared { @@ -870,7 +870,7 @@ Environment::Environment() m_security = getSecurityProducts(); } -const std::vector& Environment::loadedModules() +const std::vector& Environment::loadedModules() const { return m_modules; } @@ -885,6 +885,25 @@ const std::vector& Environment::securityProducts() const return m_security; } +void Environment::dump() const +{ + log::debug("windows: {}", windowsInfo().toString()); + + if (windowsInfo().compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : securityProducts()) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : loadedModules()) { + log::debug(" . {}", m.toString()); + } +} + std::vector Environment::getLoadedModules() const { HandlePtr snapshot(CreateToolhelp32Snapshot( diff --git a/src/shared/util.h b/src/shared/util.h index a5d096ac..267bb780 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -410,7 +410,7 @@ public: // list of loaded modules in the current process // - const std::vector& loadedModules(); + const std::vector& loadedModules() const; // information about the operating system // @@ -420,6 +420,10 @@ public: // const std::vector& securityProducts() const; + // logs the environment + // + void dump() const; + private: std::vector m_modules; WindowsInfo m_windows; -- cgit v1.3.1 From 20ac714bf880ab7e3762428c7154d1c81b5188ba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 19:00:33 -0400 Subject: fixed bad compare for nahimic log displays on startup --- src/main.cpp | 2 +- src/shared/util.cpp | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 27 +++++++ 3 files changed, 251 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index ef698dd4..09da9408 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -529,7 +529,7 @@ void checkNahimic(const env::Environment& e) for (auto&& m : e.loadedModules()) { const QFileInfo file(m.path()); - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive)) { + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { log::warn( "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " "Mod Organizer, such as freezing or blank windows. Consider " diff --git a/src/shared/util.cpp b/src/shared/util.cpp index eacd1f88..8c4a3f17 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #pragma comment(lib, "Wbemuuid.lib") @@ -864,6 +865,196 @@ private: }; +class DisplayEnumerator +{ +public: + DisplayEnumerator() + : m_GetDpiForMonitor(nullptr) + { + m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (m_shcore) { + // windows 8.1+ only + m_GetDpiForMonitor = reinterpret_cast( + GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + } + + // gets all monitors and the device they're running on + getDisplayDevices(); + } + + std::vector&& displays() && + { + return std::move(m_displays); + } + + const std::vector& displays() const & + { + return m_displays; + } + +private: + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + std::unique_ptr m_shcore; + GetDpiForMonitorFunction* m_GetDpiForMonitor; + std::vector m_displays; + + void getDisplayDevices() + { + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.push_back(createDisplay(device)); + } + } + + Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) + { + Metrics::Display d; + + d.adapter = QString::fromWCharArray(device.DeviceString); + d.monitor = QString::fromWCharArray(device.DeviceName); + d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); + + getDisplaySettings(device.DeviceName, d); + getDpi(d); + + return d; + } + + void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", d.monitor); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + d.refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + d.resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + d.resY = dm.dmPelsHeight; + } + } + + void getDpi(Metrics::Display& d) + { + if (!m_GetDpiForMonitor) { + // this happens on windows 7, get the desktop dpi instead + getDesktopDpi(d); + return; + } + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(d.monitor); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", d.monitor); + return; + } + + UINT dpiX=0, dpiY=0; + const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + d.monitor, formatSystemMessageQ(r)); + + return; + } + + // dpiX and dpiY are always identical, as per the documentation + d.dpi = dpiX; + } + + void getDesktopDpi(Metrics::Display& d) + { + // desktop dc + HDC dc = GetDC(0); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return; + } + + d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + + ReleaseDC(0, dc); + } + + HMONITOR findMonitor(const QString& name) + { + // passed to the enumeration callback + struct Data + { + DisplayEnumerator* self; + QString name; + HMONITOR hm; + }; + + Data data = {this, name, 0}; + + // for each monitor + EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }, reinterpret_cast(&data)); + + return data.hm; + } +}; + + Environment::Environment() { m_modules = getLoadedModules(); @@ -885,6 +1076,11 @@ const std::vector& Environment::securityProducts() const return m_security; } +const Metrics& Environment::metrics() const +{ + return m_metrics; +} + void Environment::dump() const { log::debug("windows: {}", windowsInfo().toString()); @@ -902,6 +1098,11 @@ void Environment::dump() const for (const auto& m : loadedModules()) { log::debug(" . {}", m.toString()); } + + log::debug("displays:"); + for (const auto& d : m_metrics.displays()) { + log::debug(" . {}", d.toString()); + } } std::vector Environment::getLoadedModules() const @@ -1137,6 +1338,28 @@ std::optional Environment::getWindowsFirewall() const } +Metrics::Metrics() +{ + m_displays = DisplayEnumerator().displays(); +} + +const std::vector& Metrics::displays() const +{ + return m_displays; +} + +QString Metrics::Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(resX) + .arg(resY) + .arg(refreshRate) + .arg(dpi) + .arg(adapter) + .arg(primary ? " (primary)" : ""); +} + + Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) { diff --git a/src/shared/util.h b/src/shared/util.h index 267bb780..fc2028db 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -401,6 +401,28 @@ private: }; +class Metrics +{ +public: + struct Display + { + int resX=0, resY=0, dpi=0; + bool primary=false; + int refreshRate = 0; + QString monitor, adapter; + + QString toString() const; + }; + + Metrics(); + + const std::vector& displays() const; + +private: + std::vector m_displays; +}; + + // represents the process's environment // class Environment @@ -420,6 +442,10 @@ public: // const std::vector& securityProducts() const; + // information about displays + // + const Metrics& metrics() const; + // logs the environment // void dump() const; @@ -428,6 +454,7 @@ private: std::vector m_modules; WindowsInfo m_windows; std::vector m_security; + Metrics m_metrics; std::vector getLoadedModules() const; std::vector getSecurityProducts() const; -- cgit v1.3.1 From c84240b75906fdc1f2ef8b41f4f3c00421dc61fa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 17 Jul 2019 19:01:58 -0400 Subject: only display "inactive" for security products --- src/shared/util.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 8c4a3f17..441b64bd 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1976,9 +1976,7 @@ QString SecurityProduct::toString() const s += "(" + ps.join("|") + ")"; } - if (m_active) { - s += ", active"; - } else { + if (!m_active) { s += ", inactive"; } -- cgit v1.3.1 From f95479b981b41f51a3ecf055c73f42440766e5d7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 22:35:12 -0400 Subject: log guid for security products --- src/shared/util.cpp | 41 ++++++++++++++++++++++++----------------- src/shared/util.h | 6 +++++- 2 files changed, 29 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 441b64bd..4ee4b766 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1264,7 +1264,7 @@ std::vector Environment::getSecurityProductsFromWMI() const map.insert({ guid, - {QString::fromStdWString(name), provider, active, upToDate}}); + {guid, QString::fromStdWString(name), provider, active, upToDate}}); }; { @@ -1334,7 +1334,7 @@ std::optional Environment::getWindowsFirewall() const } return SecurityProduct( - "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); + {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); } @@ -1907,9 +1907,9 @@ std::optional WindowsInfo::getElevated() const SecurityProduct::SecurityProduct( - QString name, int provider, + QUuid guid, QString name, int provider, bool active, bool upToDate) : - m_name(std::move(name)), m_provider(provider), + m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), m_active(active), m_upToDate(upToDate) { } @@ -1938,10 +1938,27 @@ QString SecurityProduct::toString() const { QString s; - s += m_name + " "; + s += m_name + " (" + providerToString() + ")"; + if (!m_active) { + s += ", inactive"; + } + + if (!m_upToDate) { + s += ", definitions outdated"; + } + + if (!m_guid.isNull()) { + s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); + } + + return s; +} +QString SecurityProduct::providerToString() const +{ QStringList ps; + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { ps.push_back("firewall"); } @@ -1971,20 +1988,10 @@ QString SecurityProduct::toString() const } if (ps.empty()) { - s += "(doesn't provide anything)"; - } else { - s += "(" + ps.join("|") + ")"; - } - - if (!m_active) { - s += ", inactive"; - } - - if (!m_upToDate) { - s += ", definitions outdated"; + return "doesn't provider anything"; } - return s; + return ps.join("|"); } diff --git a/src/shared/util.h b/src/shared/util.h index fc2028db..46764367 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include #include +#include class Executable; @@ -370,7 +371,7 @@ class SecurityProduct { public: SecurityProduct( - QString name, int provider, + QUuid guid, QString name, int provider, bool active, bool upToDate); // display name of the product @@ -394,10 +395,13 @@ public: QString toString() const; private: + QUuid m_guid; QString m_name; int m_provider; bool m_active; bool m_upToDate; + + QString providerToString() const; }; -- cgit v1.3.1 From b2a1e1391fdd6bdee1c5e8d337b273447c70a506 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 23:13:57 -0400 Subject: split env --- src/CMakeLists.txt | 25 +- src/env.cpp | 479 ++++++++++++ src/env.h | 117 +++ src/envmetrics.cpp | 224 ++++++ src/envmetrics.h | 28 + src/envmodule.cpp | 390 ++++++++++ src/envmodule.h | 98 +++ src/envsecurity.cpp | 418 ++++++++++ src/envsecurity.h | 49 ++ src/envshortcut.cpp | 376 +++++++++ src/envshortcut.h | 114 +++ src/envwindows.cpp | 236 ++++++ src/envwindows.h | 106 +++ src/main.cpp | 2 + src/mainwindow.cpp | 1 + src/shared/util.cpp | 2123 +-------------------------------------------------- src/shared/util.h | 443 ----------- 17 files changed, 2663 insertions(+), 2566 deletions(-) create mode 100644 src/env.cpp create mode 100644 src/env.h create mode 100644 src/envmetrics.cpp create mode 100644 src/envmetrics.h create mode 100644 src/envmodule.cpp create mode 100644 src/envmodule.h create mode 100644 src/envsecurity.cpp create mode 100644 src/envsecurity.h create mode 100644 src/envshortcut.cpp create mode 100644 src/envshortcut.h create mode 100644 src/envwindows.cpp create mode 100644 src/envwindows.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9dbab132..9785dc3d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,12 @@ SET(organizer_SRCS filerenamer.cpp texteditor.cpp expanderwidget.cpp + env.cpp + envmetrics.cpp + envmodule.cpp + envsecurity.cpp + envshortcut.cpp + envwindows.cpp shared/windows_error.cpp shared/error_report.cpp @@ -239,6 +245,12 @@ SET(organizer_HDRS filerenamer.h texteditor.h expanderwidget.h + env.h + envmetrics.h + envmodule.h + envsecurity.h + envshortcut.h + envwindows.h shared/windows_error.h shared/error_report.h @@ -350,6 +362,15 @@ set(downloads downloadmanager ) +set(env + env + envmetrics + envmodule + envsecurity + envshortcut + envwindows +) + set(executables executableslist editexecutablesdialog @@ -447,8 +468,8 @@ set(widgets ) set(src_filters - application core browser dialogs downloads executables locking modinfo modinfo\\dialog - modlist plugins previews profiles settings utilities widgets + application core browser dialogs downloads env executables locking modinfo + modinfo\\dialog modlist plugins previews profiles settings utilities widgets ) foreach(filter in list ${src_filters}) diff --git a/src/env.cpp b/src/env.cpp new file mode 100644 index 00000000..641eb4a7 --- /dev/null +++ b/src/env.cpp @@ -0,0 +1,479 @@ +#include "env.h" +#include "envmetrics.h" +#include "envmodule.h" +#include "envsecurity.h" +#include "envshortcut.h" +#include "envwindows.h" +#include +#include + +namespace env +{ + +using namespace MOBase; + +Console::Console() + : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) +{ + // open a console + if (!AllocConsole()) { + // failed, ignore + } + + m_hasConsole = true; + + // redirect stdin, stdout and stderr to it + freopen_s(&m_in, "CONIN$", "r", stdin); + freopen_s(&m_out, "CONOUT$", "w", stdout); + freopen_s(&m_err, "CONOUT$", "w", stderr); +} + +Console::~Console() +{ + // close redirected handles and redirect standard stream to NUL in case + // they're used after this + + if (m_err) { + std::fclose(m_err); + freopen_s(&m_err, "NUL", "w", stderr); + } + + if (m_out) { + std::fclose(m_out); + freopen_s(&m_out, "NUL", "w", stdout); + } + + if (m_in) { + std::fclose(m_in); + freopen_s(&m_in, "NUL", "r", stdin); + } + + // close console + if (m_hasConsole) { + FreeConsole(); + } +} + + +Environment::Environment() + : m_windows(new WindowsInfo), m_metrics(new Metrics) +{ + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); +} + +// anchor +Environment::~Environment() = default; + +const std::vector& Environment::loadedModules() const +{ + return m_modules; +} + +const WindowsInfo& Environment::windowsInfo() const +{ + return *m_windows; +} + +const std::vector& Environment::securityProducts() const +{ + return m_security; +} + +const Metrics& Environment::metrics() const +{ + return *m_metrics; +} + +void Environment::dump() const +{ + log::debug("windows: {}", m_windows->toString()); + + if (m_windows->compatibilityMode()) { + log::warn("MO seems to be running in compatibility mode"); + } + + log::debug("security products:"); + for (const auto& sp : m_security) { + log::debug(" . {}", sp.toString()); + } + + log::debug("modules loaded in process:"); + for (const auto& m : m_modules) { + log::debug(" . {}", m.toString()); + } + + log::debug("displays:"); + for (const auto& d : m_metrics->displays()) { + log::debug(" . {}", d.toString()); + } +} + + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + + +// returns the filename of the given process or the current one +// +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + // double the buffer size 10 times + const int MaxTries = 10; + + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + DWORD writtenSize = 0; + + if (process == INVALID_HANDLE_VALUE) { + // query this process + writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); + } else { + // query another process + writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); + } + + if (writtenSize == 0) { + // hard failure + const auto e = GetLastError(); + std::wcerr << formatSystemMessage(e) << L"\n"; + break; + } else if (writtenSize >= bufferSize) { + // buffer is too small, try again + bufferSize *= 2; + } else { + // if GetModuleFileName() works, `writtenSize` does not include the null + // terminator + const std::wstring s(buffer.get(), writtenSize); + const std::filesystem::path path(s); + + return path.filename().native(); + } + } + + // something failed or the path is way too long to make sense + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector runningProcessesIds() +{ + // double the buffer size 10 times + const int MaxTries = 10; + + // initial size of 300 processes, unlikely to be more than that + std::size_t size = 300; + + for (int tries=0; tries(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast(size * sizeof(ids[0])); + DWORD bytesWritten = 0; + + if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) + { + const auto e = GetLastError(); + + std::wcerr + << L"failed to enumerate processes, " + << formatSystemMessage(e) << L"\n"; + + return {}; + } + + if (bytesWritten == bytesGiven) { + // no way to distinguish between an exact fit and not enough space, + // just try again + size *= 2; + continue; + } + + const auto count = bytesWritten / sizeof(ids[0]); + return std::vector(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector v; + + for (const auto& pid : pids) { + if (pid == 0) { + // the idle process has pid 0 and seems to be picked up by EnumProcesses() + continue; + } + + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + + if (e != ERROR_ACCESS_DENIED) { + // don't log access denied, will happen a lot for system processes, even + // when elevated + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + } + + continue; + } + + auto filename = processFilename(h.get()); + if (!filename.empty()) { + v.emplace_back(std::move(filename), pid); + } + } + + return v; +} + +DWORD findOtherPid() +{ + const std::wstring defaultName = L"ModOrganizer.exe"; + + std::wclog << L"looking for the other process...\n"; + + // used to skip the current process below + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + // getting the filename for this process, assumes the other process has the + // smae one + auto filename = processFilename(); + if (filename.empty()) { + std::wcerr + << L"can't get current process filename, defaulting to " + << defaultName << L"\n"; + + filename = defaultName; + } else { + std::wclog << L"this process filename is " << filename << L"\n"; + } + + // getting all running processes + const auto processes = runningProcesses(); + std::wclog << L"there are " << processes.size() << L" processes running\n"; + + // going through processes, trying to find one with the same name and a + // different pid than this process has + for (const auto& p : processes) { + if (p.filename == filename) { + if (p.pid != thisPid) { + return p.pid; + } + } + } + + std::wclog + << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; + + return 0; +} + +std::wstring tempDir() +{ + const DWORD bufferSize = MAX_PATH + 1; + wchar_t buffer[bufferSize + 1] = {}; + + const auto written = GetTempPathW(bufferSize, buffer); + if (written == 0) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // `written` does not include the null terminator + return std::wstring(buffer, buffer + written); +} + +HandlePtr tempFile(const std::wstring dir) +{ + // maximum tries of incrementing the counter + const int MaxTries = 100; + + // UTC time and date will be in the filename + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where + // i can go until MaxTries + std::wostringstream oss; + oss + << L"ModOrganizer-" + << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) + << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" + << std::setw(2) << std::setfill(L'0') << tm->tm_hour + << std::setw(2) << std::setfill(L'0') << tm->tm_min + << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + + const std::wstring prefix = oss.str(); + const std::wstring ext = L".dmp"; + + // first path to try, without counter in it + std::wstring path = dir + L"\\" + prefix + ext; + + for (int i=0; i; + + +struct LibraryFreer +{ + using pointer = HINSTANCE; + + void operator()(HINSTANCE h) + { + if (h != 0) { + ::FreeLibrary(h); + } + } +}; + +struct COMReleaser +{ + void operator()(IUnknown* p) + { + if (p) { + p->Release(); + } + } +}; + + +template +using COMPtr = std::unique_ptr; + + +class Console +{ +public: + Console(); + ~Console(); + +private: + bool m_hasConsole; + FILE* m_in; + FILE* m_out; + FILE* m_err; +}; + + +// represents the process's environment +// +class Environment +{ +public: + Environment(); + ~Environment(); + + // list of loaded modules in the current process + // + const std::vector& loadedModules() const; + + // information about the operating system + // + const WindowsInfo& windowsInfo() const; + + // information about the installed security products + // + const std::vector& securityProducts() const; + + // information about displays + // + const Metrics& metrics() const; + + // logs the environment + // + void dump() const; + +private: + std::vector m_modules; + std::unique_ptr m_windows; + std::vector m_security; + std::unique_ptr m_metrics; +}; + + +enum class CoreDumpTypes +{ + Mini = 1, + Data, + Full +}; + +// creates a minidump file for the given process +// +bool coredump(CoreDumpTypes type); + +// finds another process with the same name as this one and creates a minidump +// file for it +// +bool coredumpOther(CoreDumpTypes type); + +} // namespace env diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp new file mode 100644 index 00000000..a6988909 --- /dev/null +++ b/src/envmetrics.cpp @@ -0,0 +1,224 @@ +#include "envmetrics.h" +#include "env.h" +#include +#include +#include +#include + +namespace env +{ + +using namespace MOBase; + +class DisplayEnumerator +{ +public: + DisplayEnumerator() + : m_GetDpiForMonitor(nullptr) + { + m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (m_shcore) { + // windows 8.1+ only + m_GetDpiForMonitor = reinterpret_cast( + GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + } + + // gets all monitors and the device they're running on + getDisplayDevices(); + } + + std::vector&& displays() && + { + return std::move(m_displays); + } + + const std::vector& displays() const & + { + return m_displays; + } + +private: + using GetDpiForMonitorFunction = + HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + std::unique_ptr m_shcore; + GetDpiForMonitorFunction* m_GetDpiForMonitor; + std::vector m_displays; + + void getDisplayDevices() + { + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.push_back(createDisplay(device)); + } + } + + Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) + { + Metrics::Display d; + + d.adapter = QString::fromWCharArray(device.DeviceString); + d.monitor = QString::fromWCharArray(device.DeviceName); + d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); + + getDisplaySettings(device.DeviceName, d); + getDpi(d); + + return d; + } + + void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) + { + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); + + if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", d.monitor); + return; + } + + // all these fields should be available + + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + d.refreshRate = dm.dmDisplayFrequency; + } + + if (dm.dmFields & DM_PELSWIDTH) { + d.resX = dm.dmPelsWidth; + } + + if (dm.dmFields & DM_PELSHEIGHT) { + d.resY = dm.dmPelsHeight; + } + } + + void getDpi(Metrics::Display& d) + { + if (!m_GetDpiForMonitor) { + // this happens on windows 7, get the desktop dpi instead + getDesktopDpi(d); + return; + } + + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(d.monitor); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", d.monitor); + return; + } + + UINT dpiX=0, dpiY=0; + const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + d.monitor, formatSystemMessageQ(r)); + + return; + } + + // dpiX and dpiY are always identical, as per the documentation + d.dpi = dpiX; + } + + void getDesktopDpi(Metrics::Display& d) + { + // desktop dc + HDC dc = GetDC(0); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return; + } + + d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + + ReleaseDC(0, dc); + } + + HMONITOR findMonitor(const QString& name) + { + // passed to the enumeration callback + struct Data + { + DisplayEnumerator* self; + QString name; + HMONITOR hm; + }; + + Data data = {this, name, 0}; + + // for each monitor + EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; + } + + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } + + // not found, continue to the next monitor + return TRUE; + }, reinterpret_cast(&data)); + + return data.hm; + } +}; + + +Metrics::Metrics() +{ + m_displays = DisplayEnumerator().displays(); +} + +const std::vector& Metrics::displays() const +{ + return m_displays; +} + +QString Metrics::Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(resX) + .arg(resY) + .arg(refreshRate) + .arg(dpi) + .arg(adapter) + .arg(primary ? " (primary)" : ""); +} + +} // namespace diff --git a/src/envmetrics.h b/src/envmetrics.h new file mode 100644 index 00000000..62fc8c49 --- /dev/null +++ b/src/envmetrics.h @@ -0,0 +1,28 @@ +#include +#include + +namespace env +{ + +class Metrics +{ +public: + struct Display + { + int resX=0, resY=0, dpi=0; + bool primary=false; + int refreshRate = 0; + QString monitor, adapter; + + QString toString() const; + }; + + Metrics(); + + const std::vector& displays() const; + +private: + std::vector m_displays; +}; + +} // namespace diff --git a/src/envmodule.cpp b/src/envmodule.cpp new file mode 100644 index 00000000..1717da15 --- /dev/null +++ b/src/envmodule.cpp @@ -0,0 +1,390 @@ +#include "envmodule.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +Module::Module(QString path, std::size_t fileSize) + : m_path(std::move(path)), m_fileSize(fileSize) +{ + const auto fi = getFileInfo(); + + m_version = getVersion(fi.ffi); + m_timestamp = getTimestamp(fi.ffi); + m_versionString = fi.fileDescription; + m_md5 = getMD5(); +} + +const QString& Module::path() const +{ + return m_path; +} + +QString Module::displayPath() const +{ + return QDir::fromNativeSeparators(m_path.toLower()); +} + +std::size_t Module::fileSize() const +{ + return m_fileSize; +} + +const QString& Module::version() const +{ + return m_version; +} + +const QString& Module::versionString() const +{ + return m_versionString; +} + +const QDateTime& Module::timestamp() const +{ + return m_timestamp; +} + +const QString& Module::md5() const +{ + return m_md5; +} + +QString Module::timestampString() const +{ + if (!m_timestamp.isValid()) { + return "(no timestamp)"; + } + + return m_timestamp.toString(Qt::DateFormat::ISODate); +} + +QString Module::toString() const +{ + QStringList sl; + + // file size + sl.push_back(displayPath()); + sl.push_back(QString("%1 B").arg(m_fileSize)); + + // version + if (m_version.isEmpty() && m_versionString.isEmpty()) { + sl.push_back("(no version)"); + } else { + if (!m_version.isEmpty()) { + sl.push_back(m_version); + } + + if (!m_versionString.isEmpty() && m_versionString != m_version) { + sl.push_back(versionString()); + } + } + + // timestamp + if (m_timestamp.isValid()) { + sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); + } else { + sl.push_back("(no timestamp)"); + } + + // md5 + if (!m_md5.isEmpty()) { + sl.push_back(m_md5); + } + + return sl.join(", "); +} + +Module::FileInfo Module::getFileInfo() const +{ + const auto wspath = m_path.toStdWString(); + + // getting version info size + DWORD dummy = 0; + const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); + + if (size == 0) { + const auto e = GetLastError(); + + if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { + // not an error, no version information built into that module + return {}; + } + + qCritical().nospace().noquote() + << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // getting version info + auto buffer = std::make_unique(size); + + if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "GetFileVersionInfoW() failed on '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + + // the version info has two major parts: a fixed version and a localizable + // set of strings + + FileInfo fi; + fi.ffi = getFixedFileInfo(buffer.get()); + fi.fileDescription = getFileDescription(buffer.get()); + + return fi; +} + +VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const +{ + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // the fixed version info is in the root + const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no fixed file info + return {}; + } + + const auto* fi = static_cast(valuePointer); + + // signature is always 0xfeef04bd + if (fi->dwSignature != 0xfeef04bd) { + qCritical().nospace().noquote() + << "bad file info signature 0x" << hex << fi->dwSignature << " for " + << "'" << m_path << "'"; + + return {}; + } + + return *fi; +} + +QString Module::getFileDescription(std::byte* buffer) const +{ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + }; + + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + // getting list of available languages + auto ret = VerQueryValueW( + buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + qCritical().nospace().noquote() + << "VerQueryValueW() for translations failed on '" << m_path << "'"; + + return {}; + } + + // number of languages + const auto count = valueSize / sizeof(LANGANDCODEPAGE); + if (count == 0) { + return {}; + } + + // using the first language in the list to get FileVersion + const auto* lcp = static_cast(valuePointer); + + const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); + + ret = VerQueryValueW( + buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no file version + return {}; + } + + // valueSize includes the null terminator + return QString::fromWCharArray( + static_cast(valuePointer), valueSize - 1); +} + +QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const +{ + if (fi.dwSignature == 0) { + return {}; + } + + const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; + const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; + const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; + const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; + + if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { + return {}; + } + + return QString("%1.%2.%3.%4") + .arg(major).arg(minor).arg(maintenance).arg(build); +} + +QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +{ + FILETIME ft = {}; + + if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + // if the file info is invalid or doesn't have a date, use the creation + // time on the file + + // opening the file + HandlePtr h(CreateFileW( + m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + + if (h.get() == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "can't open file '" << m_path << "' for timestamp, " + << formatSystemMessageQ(e); + + return {}; + } + + // getting the file time + if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { + const auto e = GetLastError(); + qCritical().nospace().noquote() + << "can't get file time for '" << m_path << "', " + << formatSystemMessageQ(e); + + return {}; + } + } else { + // use the time from the file info + ft.dwHighDateTime = fi.dwFileDateMS; + ft.dwLowDateTime = fi.dwFileDateLS; + } + + + // converting to SYSTEMTIME + SYSTEMTIME utc = {}; + + if (!FileTimeToSystemTime(&ft, &utc)) { + qCritical().nospace().noquote() + << "FileTimeToSystemTime() failed on timestamp " + << "high=0x" << hex << ft.dwHighDateTime << " " + << "low=0x" << hex << ft.dwLowDateTime << " for " + << "'" << m_path << "'"; + + return {}; + } + + return QDateTime( + QDate(utc.wYear, utc.wMonth, utc.wDay), + QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); +} + +QString Module::getMD5() const +{ + if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { + // don't calculate md5 for system files, it's not really relevant and + // it takes a while + return {}; + } + + // opening the file + QFile f(m_path); + + if (!f.open(QFile::ReadOnly)) { + qCritical().nospace().noquote() + << "failed to open file '" << m_path << "' for md5"; + + return {}; + } + + // hashing + QCryptographicHash hash(QCryptographicHash::Md5); + if (!hash.addData(&f)) { + qCritical().nospace().noquote() + << "failed to calculate md5 for '" << m_path << "'"; + + return {}; + } + + return hash.result().toHex(); +} + + +std::vector getLoadedModules() +{ + HandlePtr snapshot(CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "CreateToolhelp32Snapshot() failed, " + << formatSystemMessageQ(e); + + return {}; + } + + MODULEENTRY32 me = {}; + me.dwSize = sizeof(me); + + // first module, this shouldn't fail because there's at least the executable + if (!Module32First(snapshot.get(), &me)) + { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "Module32First() failed, " << formatSystemMessageQ(e); + + return {}; + } + + std::vector v; + + for (;;) + { + const auto path = QString::fromWCharArray(me.szExePath); + if (!path.isEmpty()) { + v.push_back(Module(path, me.modBaseSize)); + } + + // next module + if (!Module32Next(snapshot.get(), &me)) { + const auto e = GetLastError(); + + // no more modules is not an error + if (e != ERROR_NO_MORE_FILES) { + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessageQ(e); + } + + break; + } + } + + // sorting by display name + std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { + return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); + }); + + return v; +} + +} // namespace diff --git a/src/envmodule.h b/src/envmodule.h new file mode 100644 index 00000000..ea1156bd --- /dev/null +++ b/src/envmodule.h @@ -0,0 +1,98 @@ +#include +#include + +namespace env +{ + +// represents one module +// +class Module +{ +public: + explicit Module(QString path, std::size_t fileSize); + + // returns the module's path + // + const QString& path() const; + + // returns the module's path in lowercase and using forward slashes + // + QString displayPath() const; + + // returns the size in bytes, may be 0 + // + std::size_t fileSize() const; + + // returns the x.x.x.x version embedded from the version info, may be empty + // + const QString& version() const; + + // returns the FileVersion entry from the resource file, returns + // "(no version)" if not available + // + const QString& versionString() const; + + // returns the build date from the version info, or the creation time of the + // file on the filesystem, may be empty + // + const QDateTime& timestamp() const; + + // returns the md5 of the file, may be empty for system files + // + const QString& md5() const; + + // converts timestamp() to a string for display, returns "(no timestamp)" if + // not available + // + QString timestampString() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + // contains the information from the version resource + // + struct FileInfo + { + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; + + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + QString m_md5; + + // returns information from the version resource + // + FileInfo getFileInfo() const; + + // uses VS_FIXEDFILEINFO to build the version string + // + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + + // uses the file date from VS_FIXEDFILEINFO if available, or gets the + // creation date on the file + // + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + // returns the md5 hash unless the path contains "\windows\" + // + QString getMD5() const; + + // gets VS_FIXEDFILEINFO from the file version info buffer + // + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + + // gets FileVersion from the file version info buffer + // + QString getFileDescription(std::byte* buffer) const; +}; + + +std::vector getLoadedModules(); + +} // namespace env diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp new file mode 100644 index 00000000..559ce4ad --- /dev/null +++ b/src/envsecurity.cpp @@ -0,0 +1,418 @@ +#include "envsecurity.h" +#include "env.h" +#include + +#include +#include +#include +#include +#pragma comment(lib, "Wbemuuid.lib") + +namespace env +{ + +using namespace MOBase; + +class WMI +{ +public: + class failed {}; + + WMI(const std::string& ns) + { + try + { + createLocator(); + createService(ns); + setSecurity(); + } + catch(failed&) + { + } + } + + template + void query(const std::string& q, F&& f) + { + if (!m_locator || !m_service) { + return; + } + + auto enumerator = getEnumerator(q); + if (!enumerator) { + return; + } + + for (;;) + { + COMPtr object; + + { + IWbemClassObject* rawObject = nullptr; + ULONG count = 0; + auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); + + if (count == 0 || !rawObject) { + break; + } + + if (FAILED(ret)) { + qCritical() + << "enumerator->next() failed, " << formatSystemMessageQ(ret); + break; + } + + object.reset(rawObject); + } + + f(object.get()); + } + } + +private: + COMPtr m_locator; + COMPtr m_service; + + void createLocator() + { + void* rawLocator = nullptr; + + const auto ret = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); + + if (FAILED(ret) || !rawLocator) { + qCritical() + << "CoCreateInstance for WbemLocator failed, " + << formatSystemMessageQ(ret); + + throw failed(); + } + + m_locator.reset(static_cast(rawLocator)); + } + + void createService(const std::string& ns) + { + IWbemServices* rawService = nullptr; + + const auto res = m_locator->ConnectServer( + _bstr_t(ns.c_str()), + nullptr, nullptr, nullptr, 0, nullptr, nullptr, + &rawService); + + if (FAILED(res) || !rawService) { + qCritical() + << "locator->ConnectServer() failed for namespace " + << "'" << QString::fromStdString(ns) << "', " + << formatSystemMessageQ(res); + + throw failed(); + } + + m_service.reset(rawService); + } + + void setSecurity() + { + auto ret = CoSetProxyBlanket( + m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); + + if (FAILED(ret)) + { + qCritical() + << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); + + throw failed(); + } + } + + COMPtr getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessageQ(ret); + + return {}; + } + + return COMPtr(rawEnumerator); + } +}; + + +SecurityProduct::SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate) : + m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) +{ +} + +const QString& SecurityProduct::name() const +{ + return m_name; +} + +int SecurityProduct::provider() const +{ + return m_provider; +} + +bool SecurityProduct::active() const +{ + return m_active; +} + +bool SecurityProduct::upToDate() const +{ + return m_upToDate; +} + +QString SecurityProduct::toString() const +{ + QString s; + + s += m_name + " (" + providerToString() + ")"; + + if (!m_active) { + s += ", inactive"; + } + + if (!m_upToDate) { + s += ", definitions outdated"; + } + + if (!m_guid.isNull()) { + s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); + } + + return s; +} + +QString SecurityProduct::providerToString() const +{ + QStringList ps; + + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { + ps.push_back("firewall"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + ps.push_back("autoupdate"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + ps.push_back("antivirus"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + ps.push_back("antispyware"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + ps.push_back("settings"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + ps.push_back("uac"); + } + + if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { + ps.push_back("service"); + } + + if (ps.empty()) { + return "doesn't provider anything"; + } + + return ps.join("|"); +} + + +std::vector getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map map; + + auto handleProduct = [&](auto* o) { + VARIANT prop; + + // display name + auto ret = o->Get(L"displayName", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get displayName, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "displayName is a " << prop.vt << ", not a bstr"; + return; + } + + const std::wstring name = prop.bstrVal; + VariantClear(&prop); + + // product state + ret = o->Get(L"productState", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get productState, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_UI4 && prop.vt != VT_I4) { + qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; + return; + } + + DWORD state = 0; + if (prop.vt == VT_I4) { + state = prop.lVal; + } else { + state = prop.ulVal; + } + + VariantClear(&prop); + + // guid + ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() + << "failed to get instanceGuid, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + return; + } + + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); + + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; + + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); + + map.insert({ + guid, + {guid, QString::fromStdWString(name), provider, active, upToDate}}); + }; + + { + WMI wmi("root\\SecurityCenter2"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + { + WMI wmi("root\\SecurityCenter"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + std::vector v; + + for (auto&& p : map) { + v.push_back(p.second); + } + + return v; +} + +std::optional getWindowsFirewall() +{ + HRESULT hr = 0; + + COMPtr policy; + + { + void* rawPolicy = nullptr; + + hr = CoCreateInstance( + __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); + + if (FAILED(hr) || !rawPolicy) { + qCritical() + << "CoCreateInstance for NetFwPolicy2 failed, " + << formatSystemMessageQ(hr); + + return {}; + } + + policy.reset(static_cast(rawPolicy)); + } + + VARIANT_BOOL enabledVariant; + + if (policy) { + hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); + if (FAILED(hr)) + { + qCritical() + << "get_FirewallEnabled failed, " + << formatSystemMessageQ(hr); + + return {}; + } + } + + const auto enabled = (enabledVariant != VARIANT_FALSE); + if (!enabled) { + return {}; + } + + return SecurityProduct( + {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); +} + + +std::vector getSecurityProducts() +{ + std::vector v; + + { + auto fromWMI = getSecurityProductsFromWMI(); + v.insert( + v.end(), + std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); + } + + if (auto p=getWindowsFirewall()) { + v.push_back(std::move(*p)); + } + + return v; +} + +} // namespace diff --git a/src/envsecurity.h b/src/envsecurity.h new file mode 100644 index 00000000..200cb531 --- /dev/null +++ b/src/envsecurity.h @@ -0,0 +1,49 @@ +#include +#include + +namespace env +{ + +// represents a security product, such as an antivirus or a firewall +// +class SecurityProduct +{ +public: + SecurityProduct( + QUuid guid, QString name, int provider, + bool active, bool upToDate); + + // display name of the product + // + const QString& name() const; + + // a bunch of _WSC_SECURITY_PROVIDER flags + // + int provider() const; + + // whether the product is active + // + bool active() const; + + // whether its definitions are up-to-date + // + bool upToDate() const; + + // string representation of the above + // + QString toString() const; + +private: + QUuid m_guid; + QString m_name; + int m_provider; + bool m_active; + bool m_upToDate; + + QString providerToString() const; +}; + + +std::vector getSecurityProducts(); + +} // namespace env diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp new file mode 100644 index 00000000..30ef4633 --- /dev/null +++ b/src/envshortcut.cpp @@ -0,0 +1,376 @@ +#include "envshortcut.h" +#include "env.h" +#include "executableslist.h" +#include "instancemanager.h" +#include + +namespace env +{ + +using namespace MOBase; + +class ShellLinkException +{ +public: + ShellLinkException(QString s) + : m_what(std::move(s)) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; + +// just a wrapper around IShellLink operations that throws ShellLinkException +// on errors +// +class ShellLinkWrapper +{ +public: + ShellLinkWrapper() + { + m_link = createShellLink(); + m_file = createPersistFile(); + } + + void setPath(const QString& s) + { + if (s.isEmpty()) { + throw ShellLinkException("path cannot be empty"); + } + + const auto r = m_link->SetPath(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set target path '%1'").arg(s)); + } + + void setArguments(const QString& s) + { + const auto r = m_link->SetArguments(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); + } + + void setDescription(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetDescription(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set description '%1'").arg(s)); + } + + void setIcon(const QString& file, int i) + { + if (file.isEmpty()) { + return; + } + + const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); + throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); + } + + void setWorkingDirectory(const QString& s) + { + if (s.isEmpty()) { + return; + } + + const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); + throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); + } + + void save(const QString& path) + { + const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); + throwOnFail(r, QString("failed to save link '%1'").arg(path)); + } + +private: + COMPtr m_link; + COMPtr m_file; + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessageQ(r))); + } + } + + COMPtr createShellLink() + { + void* link = nullptr; + + const auto r = CoCreateInstance( + CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, &link); + + throwOnFail(r, "failed to create IShellLink instance"); + + if (!link) { + throw ShellLinkException("creating IShellLink worked, pointer is null"); + } + + return COMPtr(static_cast(link)); + } + + COMPtr createPersistFile() + { + void* file = nullptr; + + const auto r = m_link->QueryInterface(IID_IPersistFile, &file); + throwOnFail(r, "failed to get IPersistFile interface"); + + if (!file) { + throw ShellLinkException("querying IPersistFile worked, pointer is null"); + } + + return COMPtr(static_cast(file)); + } +}; + + +Shortcut::Shortcut() + : m_iconIndex(0) +{ +} + +Shortcut::Shortcut(const Executable& exe) + : Shortcut() +{ + m_name = exe.title(); + m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); + + m_arguments = QString("\"moshortcut://%1:%2\"") + .arg(InstanceManager::instance().currentInstance()) + .arg(exe.title()); + + m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); + + if (exe.usesOwnIcon()) { + m_icon = exe.binaryInfo().absoluteFilePath(); + } + + m_workingDirectory = qApp->applicationDirPath(); +} + +Shortcut& Shortcut::name(const QString& s) +{ + m_name = s; + return *this; +} + +Shortcut& Shortcut::target(const QString& s) +{ + m_target = s; + return *this; +} + +Shortcut& Shortcut::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Shortcut& Shortcut::description(const QString& s) +{ + m_description = s; + return *this; +} + +Shortcut& Shortcut::icon(const QString& s, int index) +{ + m_icon = s; + m_iconIndex = index; + return *this; +} + +Shortcut& Shortcut::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +bool Shortcut::exists(Locations loc) const +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + return QFileInfo(path).exists(); +} + +bool Shortcut::toggle(Locations loc) +{ + if (exists(loc)) { + return remove(loc); + } else { + return add(loc); + } +} + +bool Shortcut::add(Locations loc) +{ + debug() + << "adding shortcut to " << toString(loc) << ":\n" + << " . name: '" << m_name << "'\n" + << " . target: '" << m_target << "'\n" + << " . arguments: '" << m_arguments << "'\n" + << " . description: '" << m_description << "'\n" + << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" + << " . working directory: '" << m_workingDirectory << "'"; + + if (m_target.isEmpty()) { + critical() << "target is empty"; + return false; + } + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "shorcut file will be saved at '" << path << "'"; + + try + { + ShellLinkWrapper link; + + link.setPath(m_target); + link.setArguments(m_arguments); + link.setDescription(m_description); + link.setIcon(m_icon, m_iconIndex); + link.setWorkingDirectory(m_workingDirectory); + + link.save(path); + + return true; + } + catch(ShellLinkException& e) + { + critical() << e.what() << "\nshortcut file was not saved"; + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + debug() << "path to shortcut file is '" << path << "'"; + + if (!QFile::exists(path)) { + critical() << "can't remove '" << path << "', file not found"; + return false; + } + + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + critical() + << "failed to remove '" << path << "', " + << formatSystemMessageQ(e); + + return false; + } + + return true; +} + +QString Shortcut::shortcutPath(Locations loc) const +{ + const auto dir = shortcutDirectory(loc); + if (dir.isEmpty()) { + return {}; + } + + const auto file = shortcutFilename(); + if (file.isEmpty()) { + return {}; + } + + return dir + QDir::separator() + file; +} + +QString Shortcut::shortcutDirectory(Locations loc) const +{ + QString dir; + + try + { + switch (loc) + { + case Desktop: + dir = MOBase::getDesktopDirectory(); + break; + + case StartMenu: + dir = MOBase::getStartMenuDirectory(); + break; + + case None: + default: + critical() << "bad location " << loc; + break; + } + } + catch(std::exception&) + { + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + critical() << "name is empty"; + return {}; + } + + return m_name + ".lnk"; +} + +QDebug Shortcut::debug() const +{ + return qDebug().noquote().nospace() << "system shortcut: "; +} + +QDebug Shortcut::critical() const +{ + return qCritical().noquote().nospace() << "system shortcut: "; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast(loc)); + } +} + +} // namespace diff --git a/src/envshortcut.h b/src/envshortcut.h new file mode 100644 index 00000000..904b3ab7 --- /dev/null +++ b/src/envshortcut.h @@ -0,0 +1,114 @@ +#include + +class Executable; + +namespace env +{ + +// an application shortcut that can be either on the desktop or the start menu +// +class Shortcut +{ +public: + // location of a shortcut + // + enum Locations + { + None = 0, + + // on the desktop + Desktop, + + // in the start menu + StartMenu + }; + + + // empty shortcut + // + Shortcut(); + + // shortcut from an executable + // + explicit Shortcut(const Executable& exe); + + // sets the name of the shortcut, shown on icons and start menu entries + // + Shortcut& name(const QString& s); + + // the program to start + // + Shortcut& target(const QString& s); + + // arguments to pass + // + Shortcut& arguments(const QString& s); + + // shows in the status bar of explorer, for example + // + Shortcut& description(const QString& s); + + // path to a binary that contains the icon and its index + // + Shortcut& icon(const QString& s, int index=0); + + // "start in" option for this shortcut + // + Shortcut& workingDirectory(const QString& s); + + + // returns whether this shortcut already exists at the given location; this + // does not check whether the shortcut parameters are different, it merely if + // the .lnk file exists + // + bool exists(Locations loc) const; + + // calls remove() if exists(), or add() + // + bool toggle(Locations loc); + + // adds the shortcut to the given location + // + bool add(Locations loc); + + // removes the shortcut from the given location + // + bool remove(Locations loc); + +private: + QString m_name; + QString m_target; + QString m_arguments; + QString m_description; + QString m_icon; + int m_iconIndex; + QString m_workingDirectory; + + // returns a qCritical() logger with a prefix already logged + // + QDebug critical() const; + + // returns a qDebug() logger with a prefix already logged + // + QDebug debug() const; + + + // returns the path where the shortcut file should be saved + // + QString shortcutPath(Locations loc) const; + + // returns the directory where the shortcut file should be saved + // + QString shortcutDirectory(Locations loc) const; + + // returns the filename of the shortcut file that should be used when saving + // + QString shortcutFilename() const; +}; + + +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + +} // namespace diff --git a/src/envwindows.cpp b/src/envwindows.cpp new file mode 100644 index 00000000..718cf2ce --- /dev/null +++ b/src/envwindows.cpp @@ -0,0 +1,236 @@ +#include "envwindows.h" +#include "env.h" +#include + +namespace env +{ + +using namespace MOBase; + +WindowsInfo::WindowsInfo() +{ + // loading ntdll.dll, the functions will be found with GetProcAddress() + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } else { + m_reported = getReportedVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); + } + + m_release = getRelease(); + m_elevated = getElevated(); +} + +bool WindowsInfo::compatibilityMode() const +{ + if (m_real == Version()) { + // don't know the real version, can't guess compatibility mode + return false; + } + + return (m_real != m_reported); +} + +const WindowsInfo::Version& WindowsInfo::reportedVersion() const +{ + return m_reported; +} + +const WindowsInfo::Version& WindowsInfo::realVersion() const +{ + return m_real; +} + +const WindowsInfo::Release& WindowsInfo::release() const +{ + return m_release; +} + +std::optional WindowsInfo::isElevated() const +{ + return m_elevated; +} + +QString WindowsInfo::toString() const +{ + QStringList sl; + + const QString reported = m_reported.toString(); + const QString real = m_real.toString(); + + // version + sl.push_back("version " + reported); + + // real version if different + if (compatibilityMode()) { + sl.push_back("real version " + real); + } + + // build.UBR, such as 17763.557 + if (m_release.UBR != 0) { + DWORD build = 0; + + if (compatibilityMode()) { + build = m_real.build; + } else { + build = m_reported.build; + } + + sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); + } + + // release ID + if (!m_release.ID.isEmpty()) { + sl.push_back("release " + m_release.ID); + } + + // buildlab string + if (!m_release.buildLab.isEmpty()) { + sl.push_back(m_release.buildLab); + } + + // product name + if (!m_release.productName.isEmpty()) { + sl.push_back(m_release.productName); + } + + // elevated + QString elevated = "?"; + if (m_elevated.has_value()) { + elevated = (*m_elevated ? "yes" : "no"); + } + + sl.push_back("elevated: " + elevated); + + return sl.join(", "); +} + +WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const +{ + // windows has been deprecating pretty much all the functions having to do + // with getting version information because apparently, people keep misusing + // them for feature detection + // + // there's still RtlGetVersion() though + + using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + + auto* RtlGetVersion = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetVersion")); + + if (!RtlGetVersion) { + qCritical() << "RtlGetVersion() not found in ntdll.dll"; + return {}; + } + + OSVERSIONINFOEX vi = {}; + vi.dwOSVersionInfoSize = sizeof(vi); + + // this apparently never fails + RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); + + return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; +} + +WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const +{ + // getting the actual windows version is more difficult because all the + // functions are lying when running in compatibility mode + // + // RtlGetNtVersionNumbers() is an undocumented function that seems to work + // fine, but it might not in the future + + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (!RtlGetNtVersionNumbers) { + qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + return {}; + } + + DWORD major=0, minor=0, build=0; + RtlGetNtVersionNumbers(&major, &minor, &build); + + // for whatever reason, the build number has 0xf0000000 set + build = 0x0fffffff & build; + + return {major, minor, build}; +} + +WindowsInfo::Release WindowsInfo::getRelease() const +{ + // there are several interesting items in the registry, but most of them + // are undocumented, not always available, and localizable + // + // most of them are used to provide as much information as possible in case + // any of the other versions fail to work + + QSettings settings( + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); + + Release r; + + // buildlab seems to be an internal name from the build system + r.buildLab = settings.value("BuildLabEx", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildLab", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildBranch", "").toString(); + } + } + + // localized name of windows, such as "Windows 10 Pro" + r.productName = settings.value("ProductName", "").toString(); + + // release ID, such as 1803 + r.ID = settings.value("ReleaseId", "").toString(); + + // some other build number, shown in winver.exe + r.UBR = settings.value("UBR", 0).toUInt(); + + return r; +} + +std::optional WindowsInfo::getElevated() const +{ + HandlePtr token; + + { + HANDLE rawToken = 0; + + if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "OpenProcessToken() failed: " << formatSystemMessageQ(e); + + return {}; + } + + token.reset(rawToken); + } + + TOKEN_ELEVATION e = {}; + DWORD size = sizeof(TOKEN_ELEVATION); + + if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "GetTokenInformation() failed: " << formatSystemMessageQ(e); + + return {}; + } + + return (e.TokenIsElevated != 0); +} + +} // namespace diff --git a/src/envwindows.h b/src/envwindows.h new file mode 100644 index 00000000..c23f99f4 --- /dev/null +++ b/src/envwindows.h @@ -0,0 +1,106 @@ +#include +#include + +namespace env +{ + +// a variety of information on windows +// +class WindowsInfo +{ +public: + struct Version + { + DWORD major=0, minor=0, build=0; + + QString toString() const + { + return QString("%1.%2.%3").arg(major).arg(minor).arg(build); + } + + friend bool operator==(const Version& a, const Version& b) + { + return + a.major == b.major && + a.minor == b.minor && + a.build == b.build; + } + + friend bool operator!=(const Version& a, const Version& b) + { + return !(a == b); + } + }; + + struct Release + { + // the BuildLab entry from the registry, may be empty + QString buildLab; + + // product name such as "Windows 10 Pro", may not be in English, may be + // empty + QString productName; + + // release ID such as 1809, may be mepty + QString ID; + + // some sub-build number, undocumented, may be empty + DWORD UBR; + + Release() + : UBR(0) + { + } + }; + + + WindowsInfo(); + + // tries to guess whether this process is running in compatibility mode + // + bool compatibilityMode() const; + + // returns the Windows version, may not correspond to the actual version + // if the process is running in compatibility mode + // + const Version& reportedVersion() const; + + // tries to guess the real Windows version that's running, can be empty + // + const Version& realVersion() const; + + // various information about the current release + // + const Release& release() const; + + // whether this process is running as administrator, may be empty if the + // information is not available + std::optional isElevated() const; + + // returns a string with all the above information on one line + // + QString toString() const; + +private: + Version m_reported, m_real; + Release m_release; + std::optional m_elevated; + + // uses RtlGetVersion() to get the version number as reported by Windows + // + Version getReportedVersion(HINSTANCE ntdll) const; + + // uses RtlGetNtVersionNumbers() to get the real version number + // + Version getRealVersion(HINSTANCE ntdll) const; + + // gets various information from the registry + // + Release getRelease() const; + + // gets whether the process is elevated + // + std::optional getElevated() const; +}; + +} // namespace diff --git a/src/main.cpp b/src/main.cpp index 09da9408..5c5ce945 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,8 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "moshortcut.h" #include "organizercore.h" +#include "env.h" +#include "envmodule.h" #include #include diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6c2ab389..87ee2c8e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -86,6 +86,7 @@ along with Mod Organizer. If not, see . #include #include "localsavegames.h" #include "listdialog.h" +#include "envshortcut.h" #include #include diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 4ee4b766..07983e12 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,35 +19,9 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" -#include "error_report.h" -#include "executableslist.h" -#include "instancemanager.h" -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#pragma comment(lib, "Wbemuuid.lib") - -using namespace MOBase; -namespace fs = std::filesystem; - -namespace MOShared { +namespace MOShared +{ bool FileExists(const std::string &filename) { @@ -269,2097 +243,4 @@ MOBase::VersionInfo createVersionInfo() } } - -namespace env -{ - -struct HandleCloser -{ - using pointer = HANDLE; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) { - ::CloseHandle(h); - } - } -}; - -using HandlePtr = std::unique_ptr; - - -struct LibraryFreer -{ - using pointer = HINSTANCE; - - void operator()(HINSTANCE h) - { - if (h != 0) { - ::FreeLibrary(h); - } - } -}; - -struct COMReleaser -{ - void operator()(IUnknown* p) - { - if (p) { - p->Release(); - } - } -}; - - -template -using COMPtr = std::unique_ptr; - - -class ShellLinkException -{ -public: - ShellLinkException(QString s) - : m_what(std::move(s)) - { - } - - const QString& what() const - { - return m_what; - } - -private: - QString m_what; -}; - -// just a wrapper around IShellLink operations that throws ShellLinkException -// on errors -// -class ShellLinkWrapper -{ -public: - ShellLinkWrapper() - { - m_link = createShellLink(); - m_file = createPersistFile(); - } - - void setPath(const QString& s) - { - if (s.isEmpty()) { - throw ShellLinkException("path cannot be empty"); - } - - const auto r = m_link->SetPath(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set target path '%1'").arg(s)); - } - - void setArguments(const QString& s) - { - const auto r = m_link->SetArguments(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set arguments '%1'").arg(s)); - } - - void setDescription(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetDescription(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set description '%1'").arg(s)); - } - - void setIcon(const QString& file, int i) - { - if (file.isEmpty()) { - return; - } - - const auto r = m_link->SetIconLocation(file.toStdWString().c_str(), i); - throwOnFail(r, QString("failed to set icon '%1' @ %2").arg(file).arg(i)); - } - - void setWorkingDirectory(const QString& s) - { - if (s.isEmpty()) { - return; - } - - const auto r = m_link->SetWorkingDirectory(s.toStdWString().c_str()); - throwOnFail(r, QString("failed to set working directory '%1'").arg(s)); - } - - void save(const QString& path) - { - const auto r = m_file->Save(path.toStdWString().c_str(), TRUE); - throwOnFail(r, QString("failed to save link '%1'").arg(path)); - } - -private: - COMPtr m_link; - COMPtr m_file; - - void throwOnFail(HRESULT r, const QString& s) - { - if (FAILED(r)) { - throw ShellLinkException(QString("%1, %2") - .arg(s) - .arg(formatSystemMessageQ(r))); - } - } - - COMPtr createShellLink() - { - void* link = nullptr; - - const auto r = CoCreateInstance( - CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, &link); - - throwOnFail(r, "failed to create IShellLink instance"); - - if (!link) { - throw ShellLinkException("creating IShellLink worked, pointer is null"); - } - - return COMPtr(static_cast(link)); - } - - COMPtr createPersistFile() - { - void* file = nullptr; - - const auto r = m_link->QueryInterface(IID_IPersistFile, &file); - throwOnFail(r, "failed to get IPersistFile interface"); - - if (!file) { - throw ShellLinkException("querying IPersistFile worked, pointer is null"); - } - - return COMPtr(static_cast(file)); - } -}; - - -Console::Console() - : m_hasConsole(false), m_in(nullptr), m_out(nullptr), m_err(nullptr) -{ - // open a console - if (!AllocConsole()) { - // failed, ignore - } - - m_hasConsole = true; - - // redirect stdin, stdout and stderr to it - freopen_s(&m_in, "CONIN$", "r", stdin); - freopen_s(&m_out, "CONOUT$", "w", stdout); - freopen_s(&m_err, "CONOUT$", "w", stderr); -} - -Console::~Console() -{ - // close redirected handles and redirect standard stream to NUL in case - // they're used after this - - if (m_err) { - std::fclose(m_err); - freopen_s(&m_err, "NUL", "w", stderr); - } - - if (m_out) { - std::fclose(m_out); - freopen_s(&m_out, "NUL", "w", stdout); - } - - if (m_in) { - std::fclose(m_in); - freopen_s(&m_in, "NUL", "r", stdin); - } - - // close console - if (m_hasConsole) { - FreeConsole(); - } -} - - -Shortcut::Shortcut() - : m_iconIndex(0) -{ -} - -Shortcut::Shortcut(const Executable& exe) - : Shortcut() -{ - m_name = exe.title(); - m_target = QFileInfo(qApp->applicationFilePath()).absoluteFilePath(); - - m_arguments = QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance()) - .arg(exe.title()); - - m_description = QString("Run %1 with ModOrganizer").arg(exe.title()); - - if (exe.usesOwnIcon()) { - m_icon = exe.binaryInfo().absoluteFilePath(); - } - - m_workingDirectory = qApp->applicationDirPath(); -} - -Shortcut& Shortcut::name(const QString& s) -{ - m_name = s; - return *this; -} - -Shortcut& Shortcut::target(const QString& s) -{ - m_target = s; - return *this; -} - -Shortcut& Shortcut::arguments(const QString& s) -{ - m_arguments = s; - return *this; -} - -Shortcut& Shortcut::description(const QString& s) -{ - m_description = s; - return *this; -} - -Shortcut& Shortcut::icon(const QString& s, int index) -{ - m_icon = s; - m_iconIndex = index; - return *this; -} - -Shortcut& Shortcut::workingDirectory(const QString& s) -{ - m_workingDirectory = s; - return *this; -} - -bool Shortcut::exists(Locations loc) const -{ - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - return QFileInfo(path).exists(); -} - -bool Shortcut::toggle(Locations loc) -{ - if (exists(loc)) { - return remove(loc); - } else { - return add(loc); - } -} - -bool Shortcut::add(Locations loc) -{ - debug() - << "adding shortcut to " << toString(loc) << ":\n" - << " . name: '" << m_name << "'\n" - << " . target: '" << m_target << "'\n" - << " . arguments: '" << m_arguments << "'\n" - << " . description: '" << m_description << "'\n" - << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" - << " . working directory: '" << m_workingDirectory << "'"; - - if (m_target.isEmpty()) { - critical() << "target is empty"; - return false; - } - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "shorcut file will be saved at '" << path << "'"; - - try - { - ShellLinkWrapper link; - - link.setPath(m_target); - link.setArguments(m_arguments); - link.setDescription(m_description); - link.setIcon(m_icon, m_iconIndex); - link.setWorkingDirectory(m_workingDirectory); - - link.save(path); - - return true; - } - catch(ShellLinkException& e) - { - critical() << e.what() << "\nshortcut file was not saved"; - } - - return false; -} - -bool Shortcut::remove(Locations loc) -{ - debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); - - const auto path = shortcutPath(loc); - if (path.isEmpty()) { - return false; - } - - debug() << "path to shortcut file is '" << path << "'"; - - if (!QFile::exists(path)) { - critical() << "can't remove '" << path << "', file not found"; - return false; - } - - if (!MOBase::shellDelete({path})) { - const auto e = ::GetLastError(); - - critical() - << "failed to remove '" << path << "', " - << formatSystemMessageQ(e); - - return false; - } - - return true; -} - -QString Shortcut::shortcutPath(Locations loc) const -{ - const auto dir = shortcutDirectory(loc); - if (dir.isEmpty()) { - return {}; - } - - const auto file = shortcutFilename(); - if (file.isEmpty()) { - return {}; - } - - return dir + QDir::separator() + file; -} - -QString Shortcut::shortcutDirectory(Locations loc) const -{ - QString dir; - - try - { - switch (loc) - { - case Desktop: - dir = MOBase::getDesktopDirectory(); - break; - - case StartMenu: - dir = MOBase::getStartMenuDirectory(); - break; - - case None: - default: - critical() << "bad location " << loc; - break; - } - } - catch(std::exception&) - { - } - - return QDir::toNativeSeparators(dir); -} - -QString Shortcut::shortcutFilename() const -{ - if (m_name.isEmpty()) { - critical() << "name is empty"; - return {}; - } - - return m_name + ".lnk"; -} - -QDebug Shortcut::debug() const -{ - return qDebug().noquote().nospace() << "system shortcut: "; -} - -QDebug Shortcut::critical() const -{ - return qCritical().noquote().nospace() << "system shortcut: "; -} - - -QString toString(Shortcut::Locations loc) -{ - switch (loc) - { - case Shortcut::None: - return "none"; - - case Shortcut::Desktop: - return "desktop"; - - case Shortcut::StartMenu: - return "start menu"; - - default: - return QString("? (%1)").arg(static_cast(loc)); - } -} - - - -class WMI -{ -public: - class failed {}; - - WMI(const std::string& ns) - { - try - { - createLocator(); - createService(ns); - setSecurity(); - } - catch(failed&) - { - } - } - - template - void query(const std::string& q, F&& f) - { - if (!m_locator || !m_service) { - return; - } - - auto enumerator = getEnumerator(q); - if (!enumerator) { - return; - } - - for (;;) - { - COMPtr object; - - { - IWbemClassObject* rawObject = nullptr; - ULONG count = 0; - auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); - - if (count == 0 || !rawObject) { - break; - } - - if (FAILED(ret)) { - qCritical() - << "enumerator->next() failed, " << formatSystemMessageQ(ret); - break; - } - - object.reset(rawObject); - } - - f(object.get()); - } - } - -private: - COMPtr m_locator; - COMPtr m_service; - - void createLocator() - { - void* rawLocator = nullptr; - - const auto ret = CoCreateInstance( - CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, - IID_IWbemLocator, &rawLocator); - - if (FAILED(ret) || !rawLocator) { - qCritical() - << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessageQ(ret); - - throw failed(); - } - - m_locator.reset(static_cast(rawLocator)); - } - - void createService(const std::string& ns) - { - IWbemServices* rawService = nullptr; - - const auto res = m_locator->ConnectServer( - _bstr_t(ns.c_str()), - nullptr, nullptr, nullptr, 0, nullptr, nullptr, - &rawService); - - if (FAILED(res) || !rawService) { - qCritical() - << "locator->ConnectServer() failed for namespace " - << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessageQ(res); - - throw failed(); - } - - m_service.reset(rawService); - } - - void setSecurity() - { - auto ret = CoSetProxyBlanket( - m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, - RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); - - if (FAILED(ret)) - { - qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); - - throw failed(); - } - } - - COMPtr getEnumerator( - const std::string& query) - { - IEnumWbemClassObject* rawEnumerator = NULL; - - auto ret = m_service->ExecQuery( - bstr_t("WQL"), - bstr_t(query.c_str()), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - NULL, - &rawEnumerator); - - if (FAILED(ret) || !rawEnumerator) - { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - - return {}; - } - - return COMPtr(rawEnumerator); - } -}; - - -class DisplayEnumerator -{ -public: - DisplayEnumerator() - : m_GetDpiForMonitor(nullptr) - { - m_shcore.reset(LoadLibraryW(L"Shcore.dll")); - - if (m_shcore) { - // windows 8.1+ only - m_GetDpiForMonitor = reinterpret_cast( - GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); - } - - // gets all monitors and the device they're running on - getDisplayDevices(); - } - - std::vector&& displays() && - { - return std::move(m_displays); - } - - const std::vector& displays() const & - { - return m_displays; - } - -private: - using GetDpiForMonitorFunction = - HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - - std::unique_ptr m_shcore; - GetDpiForMonitorFunction* m_GetDpiForMonitor; - std::vector m_displays; - - void getDisplayDevices() - { - // don't bother if it goes over 100 - for (int i=0; i<100; ++i) { - DISPLAY_DEVICEW device = {}; - device.cb = sizeof(device); - - if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { - // no more - break; - } - - // EnumDisplayDevices() seems to be returning a lot of devices that are - // not actually monitors, but those don't have the - // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set - if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { - continue; - } - - m_displays.push_back(createDisplay(device)); - } - } - - Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) - { - Metrics::Display d; - - d.adapter = QString::fromWCharArray(device.DeviceString); - d.monitor = QString::fromWCharArray(device.DeviceName); - d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); - - getDisplaySettings(device.DeviceName, d); - getDpi(d); - - return d; - } - - void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) - { - DEVMODEW dm = {}; - dm.dmSize = sizeof(dm); - - if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { - log::error("EnumDisplaySettings() failed for '{}'", d.monitor); - return; - } - - // all these fields should be available - - if (dm.dmFields & DM_DISPLAYFREQUENCY) { - d.refreshRate = dm.dmDisplayFrequency; - } - - if (dm.dmFields & DM_PELSWIDTH) { - d.resX = dm.dmPelsWidth; - } - - if (dm.dmFields & DM_PELSHEIGHT) { - d.resY = dm.dmPelsHeight; - } - } - - void getDpi(Metrics::Display& d) - { - if (!m_GetDpiForMonitor) { - // this happens on windows 7, get the desktop dpi instead - getDesktopDpi(d); - return; - } - - // there's no way to get an HMONITOR from a device name, so all monitors - // will have to be enumerated and their name checked - HMONITOR hm = findMonitor(d.monitor); - if (!hm) { - log::error("can't get dpi for monitor '{}', not found", d.monitor); - return; - } - - UINT dpiX=0, dpiY=0; - const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); - - if (FAILED(r)) { - log::error( - "GetDpiForMonitor() failed for '{}', {}", - d.monitor, formatSystemMessageQ(r)); - - return; - } - - // dpiX and dpiY are always identical, as per the documentation - d.dpi = dpiX; - } - - void getDesktopDpi(Metrics::Display& d) - { - // desktop dc - HDC dc = GetDC(0); - - if (!dc) { - const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); - return; - } - - d.dpi = GetDeviceCaps(dc, LOGPIXELSX); - - ReleaseDC(0, dc); - } - - HMONITOR findMonitor(const QString& name) - { - // passed to the enumeration callback - struct Data - { - DisplayEnumerator* self; - QString name; - HMONITOR hm; - }; - - Data data = {this, name, 0}; - - // for each monitor - EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { - auto& data = *reinterpret_cast(lp); - - MONITORINFOEX mi = {}; - mi.cbSize = sizeof(mi); - - // monitor info will include the name - if (!GetMonitorInfoW(hm, &mi)) { - const auto e = GetLastError(); - log::error( - "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); - - // error for this monitor, but continue - return TRUE; - } - - if (QString::fromWCharArray(mi.szDevice) == data.name) { - // found, stop - data.hm = hm; - return FALSE; - } - - // not found, continue to the next monitor - return TRUE; - }, reinterpret_cast(&data)); - - return data.hm; - } -}; - - -Environment::Environment() -{ - m_modules = getLoadedModules(); - m_security = getSecurityProducts(); -} - -const std::vector& Environment::loadedModules() const -{ - return m_modules; -} - -const WindowsInfo& Environment::windowsInfo() const -{ - return m_windows; -} - -const std::vector& Environment::securityProducts() const -{ - return m_security; -} - -const Metrics& Environment::metrics() const -{ - return m_metrics; -} - -void Environment::dump() const -{ - log::debug("windows: {}", windowsInfo().toString()); - - if (windowsInfo().compatibilityMode()) { - log::warn("MO seems to be running in compatibility mode"); - } - - log::debug("security products:"); - for (const auto& sp : securityProducts()) { - log::debug(" . {}", sp.toString()); - } - - log::debug("modules loaded in process:"); - for (const auto& m : loadedModules()) { - log::debug(" . {}", m.toString()); - } - - log::debug("displays:"); - for (const auto& d : m_metrics.displays()) { - log::debug(" . {}", d.toString()); - } -} - -std::vector Environment::getLoadedModules() const -{ - HandlePtr snapshot(CreateToolhelp32Snapshot( - TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); - - if (snapshot.get() == INVALID_HANDLE_VALUE) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessageQ(e); - - return {}; - } - - MODULEENTRY32 me = {}; - me.dwSize = sizeof(me); - - // first module, this shouldn't fail because there's at least the executable - if (!Module32First(snapshot.get(), &me)) - { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessageQ(e); - - return {}; - } - - std::vector v; - - for (;;) - { - const auto path = QString::fromWCharArray(me.szExePath); - if (!path.isEmpty()) { - v.push_back(Module(path, me.modBaseSize)); - } - - // next module - if (!Module32Next(snapshot.get(), &me)) { - const auto e = GetLastError(); - - // no more modules is not an error - if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessageQ(e); - } - - break; - } - } - - // sorting by display name - std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { - return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); - }); - - return v; -} - -std::vector Environment::getSecurityProducts() const -{ - std::vector v; - - { - auto fromWMI = getSecurityProductsFromWMI(); - v.insert( - v.end(), - std::make_move_iterator(fromWMI.begin()), - std::make_move_iterator(fromWMI.end())); - } - - if (auto p=getWindowsFirewall()) { - v.push_back(std::move(*p)); - } - - return v; -} - -std::vector Environment::getSecurityProductsFromWMI() const -{ - // some products may be present in multiple queries, such as a product marked - // as both antivirus and antispyware, but they'll have the same GUID, so use - // that to avoid duplicating entries - std::map map; - - auto handleProduct = [&](auto* o) { - VARIANT prop; - - // display name - auto ret = o->Get(L"displayName", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get displayName, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "displayName is a " << prop.vt << ", not a bstr"; - return; - } - - const std::wstring name = prop.bstrVal; - VariantClear(&prop); - - // product state - ret = o->Get(L"productState", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get productState, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; - return; - } - - DWORD state = 0; - if (prop.vt == VT_I4) { - state = prop.lVal; - } else { - state = prop.ulVal; - } - - VariantClear(&prop); - - // guid - ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); - if (FAILED(ret)) { - qCritical() - << "failed to get instanceGuid, " - << formatSystemMessageQ(ret); - - return; - } - - if (prop.vt != VT_BSTR) { - qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; - return; - } - - const QUuid guid(QString::fromWCharArray(prop.bstrVal)); - VariantClear(&prop); - - const auto provider = static_cast((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; - const auto definitions = state & 0xff; - - const bool active = ((scanner & 0x10) != 0); - const bool upToDate = (definitions == 0); - - map.insert({ - guid, - {guid, QString::fromStdWString(name), provider, active, upToDate}}); - }; - - { - WMI wmi("root\\SecurityCenter2"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - { - WMI wmi("root\\SecurityCenter"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); - } - - std::vector v; - - for (auto&& p : map) { - v.push_back(p.second); - } - - return v; -} - -std::optional Environment::getWindowsFirewall() const -{ - HRESULT hr = 0; - - COMPtr policy; - - { - void* rawPolicy = nullptr; - - hr = CoCreateInstance( - __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, - __uuidof(INetFwPolicy2), &rawPolicy); - - if (FAILED(hr) || !rawPolicy) { - qCritical() - << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessageQ(hr); - - return {}; - } - - policy.reset(static_cast(rawPolicy)); - } - - VARIANT_BOOL enabledVariant; - - if (policy) { - hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); - if (FAILED(hr)) - { - qCritical() - << "get_FirewallEnabled failed, " - << formatSystemMessageQ(hr); - - return {}; - } - } - - const auto enabled = (enabledVariant != VARIANT_FALSE); - if (!enabled) { - return {}; - } - - return SecurityProduct( - {}, "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); -} - - -Metrics::Metrics() -{ - m_displays = DisplayEnumerator().displays(); -} - -const std::vector& Metrics::displays() const -{ - return m_displays; -} - -QString Metrics::Display::toString() const -{ - return QString("%1*%2 %3hz dpi=%4 on %5%6") - .arg(resX) - .arg(resY) - .arg(refreshRate) - .arg(dpi) - .arg(adapter) - .arg(primary ? " (primary)" : ""); -} - - -Module::Module(QString path, std::size_t fileSize) - : m_path(std::move(path)), m_fileSize(fileSize) -{ - const auto fi = getFileInfo(); - - m_version = getVersion(fi.ffi); - m_timestamp = getTimestamp(fi.ffi); - m_versionString = fi.fileDescription; - m_md5 = getMD5(); -} - -const QString& Module::path() const -{ - return m_path; -} - -QString Module::displayPath() const -{ - return QDir::fromNativeSeparators(m_path.toLower()); -} - -std::size_t Module::fileSize() const -{ - return m_fileSize; -} - -const QString& Module::version() const -{ - return m_version; -} - -const QString& Module::versionString() const -{ - return m_versionString; -} - -const QDateTime& Module::timestamp() const -{ - return m_timestamp; -} - -const QString& Module::md5() const -{ - return m_md5; -} - -QString Module::timestampString() const -{ - if (!m_timestamp.isValid()) { - return "(no timestamp)"; - } - - return m_timestamp.toString(Qt::DateFormat::ISODate); -} - -QString Module::toString() const -{ - QStringList sl; - - // file size - sl.push_back(displayPath()); - sl.push_back(QString("%1 B").arg(m_fileSize)); - - // version - if (m_version.isEmpty() && m_versionString.isEmpty()) { - sl.push_back("(no version)"); - } else { - if (!m_version.isEmpty()) { - sl.push_back(m_version); - } - - if (!m_versionString.isEmpty() && m_versionString != m_version) { - sl.push_back(versionString()); - } - } - - // timestamp - if (m_timestamp.isValid()) { - sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); - } else { - sl.push_back("(no timestamp)"); - } - - // md5 - if (!m_md5.isEmpty()) { - sl.push_back(m_md5); - } - - return sl.join(", "); -} - -Module::FileInfo Module::getFileInfo() const -{ - const auto wspath = m_path.toStdWString(); - - // getting version info size - DWORD dummy = 0; - const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); - - if (size == 0) { - const auto e = GetLastError(); - - if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { - // not an error, no version information built into that module - return {}; - } - - qCritical().nospace().noquote() - << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // getting version info - auto buffer = std::make_unique(size); - - if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - - // the version info has two major parts: a fixed version and a localizable - // set of strings - - FileInfo fi; - fi.ffi = getFixedFileInfo(buffer.get()); - fi.fileDescription = getFileDescription(buffer.get()); - - return fi; -} - -VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const -{ - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // the fixed version info is in the root - const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no fixed file info - return {}; - } - - const auto* fi = static_cast(valuePointer); - - // signature is always 0xfeef04bd - if (fi->dwSignature != 0xfeef04bd) { - qCritical().nospace().noquote() - << "bad file info signature 0x" << hex << fi->dwSignature << " for " - << "'" << m_path << "'"; - - return {}; - } - - return *fi; -} - -QString Module::getFileDescription(std::byte* buffer) const -{ - struct LANGANDCODEPAGE - { - WORD wLanguage; - WORD wCodePage; - }; - - void* valuePointer = nullptr; - unsigned int valueSize = 0; - - // getting list of available languages - auto ret = VerQueryValueW( - buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - qCritical().nospace().noquote() - << "VerQueryValueW() for translations failed on '" << m_path << "'"; - - return {}; - } - - // number of languages - const auto count = valueSize / sizeof(LANGANDCODEPAGE); - if (count == 0) { - return {}; - } - - // using the first language in the list to get FileVersion - const auto* lcp = static_cast(valuePointer); - - const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") - .arg(lcp->wLanguage, 4, 16, QChar('0')) - .arg(lcp->wCodePage, 4, 16, QChar('0')); - - ret = VerQueryValueW( - buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); - - if (!ret || !valuePointer || valueSize == 0) { - // not an error, no file version - return {}; - } - - // valueSize includes the null terminator - return QString::fromWCharArray( - static_cast(valuePointer), valueSize - 1); -} - -QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const -{ - if (fi.dwSignature == 0) { - return {}; - } - - const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; - const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; - const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; - const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; - - if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { - return {}; - } - - return QString("%1.%2.%3.%4") - .arg(major).arg(minor).arg(maintenance).arg(build); -} - -QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const -{ - FILETIME ft = {}; - - if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { - // if the file info is invalid or doesn't have a date, use the creation - // time on the file - - // opening the file - HandlePtr h(CreateFileW( - m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); - - if (h.get() == INVALID_HANDLE_VALUE) { - const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessageQ(e); - - return {}; - } - - // getting the file time - if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { - const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't get file time for '" << m_path << "', " - << formatSystemMessageQ(e); - - return {}; - } - } else { - // use the time from the file info - ft.dwHighDateTime = fi.dwFileDateMS; - ft.dwLowDateTime = fi.dwFileDateLS; - } - - - // converting to SYSTEMTIME - SYSTEMTIME utc = {}; - - if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical().nospace().noquote() - << "FileTimeToSystemTime() failed on timestamp " - << "high=0x" << hex << ft.dwHighDateTime << " " - << "low=0x" << hex << ft.dwLowDateTime << " for " - << "'" << m_path << "'"; - - return {}; - } - - return QDateTime( - QDate(utc.wYear, utc.wMonth, utc.wDay), - QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); -} - -QString Module::getMD5() const -{ - if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { - // don't calculate md5 for system files, it's not really relevant and - // it takes a while - return {}; - } - - // opening the file - QFile f(m_path); - - if (!f.open(QFile::ReadOnly)) { - qCritical().nospace().noquote() - << "failed to open file '" << m_path << "' for md5"; - - return {}; - } - - // hashing - QCryptographicHash hash(QCryptographicHash::Md5); - if (!hash.addData(&f)) { - qCritical().nospace().noquote() - << "failed to calculate md5 for '" << m_path << "'"; - - return {}; - } - - return hash.result().toHex(); -} - - -WindowsInfo::WindowsInfo() -{ - // loading ntdll.dll, the functions will be found with GetProcAddress() - std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); - - if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; - return; - } else { - m_reported = getReportedVersion(ntdll.get()); - m_real = getRealVersion(ntdll.get()); - } - - m_release = getRelease(); - m_elevated = getElevated(); -} - -bool WindowsInfo::compatibilityMode() const -{ - if (m_real == Version()) { - // don't know the real version, can't guess compatibility mode - return false; - } - - return (m_real != m_reported); -} - -const WindowsInfo::Version& WindowsInfo::reportedVersion() const -{ - return m_reported; -} - -const WindowsInfo::Version& WindowsInfo::realVersion() const -{ - return m_real; -} - -const WindowsInfo::Release& WindowsInfo::release() const -{ - return m_release; -} - -std::optional WindowsInfo::isElevated() const -{ - return m_elevated; -} - -QString WindowsInfo::toString() const -{ - QStringList sl; - - const QString reported = m_reported.toString(); - const QString real = m_real.toString(); - - // version - sl.push_back("version " + reported); - - // real version if different - if (compatibilityMode()) { - sl.push_back("real version " + real); - } - - // build.UBR, such as 17763.557 - if (m_release.UBR != 0) { - DWORD build = 0; - - if (compatibilityMode()) { - build = m_real.build; - } else { - build = m_reported.build; - } - - sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); - } - - // release ID - if (!m_release.ID.isEmpty()) { - sl.push_back("release " + m_release.ID); - } - - // buildlab string - if (!m_release.buildLab.isEmpty()) { - sl.push_back(m_release.buildLab); - } - - // product name - if (!m_release.productName.isEmpty()) { - sl.push_back(m_release.productName); - } - - // elevated - QString elevated = "?"; - if (m_elevated.has_value()) { - elevated = (*m_elevated ? "yes" : "no"); - } - - sl.push_back("elevated: " + elevated); - - return sl.join(", "); -} - -WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const -{ - // windows has been deprecating pretty much all the functions having to do - // with getting version information because apparently, people keep misusing - // them for feature detection - // - // there's still RtlGetVersion() though - - using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); - - auto* RtlGetVersion = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetVersion")); - - if (!RtlGetVersion) { - qCritical() << "RtlGetVersion() not found in ntdll.dll"; - return {}; - } - - OSVERSIONINFOEX vi = {}; - vi.dwOSVersionInfoSize = sizeof(vi); - - // this apparently never fails - RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); - - return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; -} - -WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const -{ - // getting the actual windows version is more difficult because all the - // functions are lying when running in compatibility mode - // - // RtlGetNtVersionNumbers() is an undocumented function that seems to work - // fine, but it might not in the future - - using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); - - auto* RtlGetNtVersionNumbers = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); - - if (!RtlGetNtVersionNumbers) { - qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; - return {}; - } - - DWORD major=0, minor=0, build=0; - RtlGetNtVersionNumbers(&major, &minor, &build); - - // for whatever reason, the build number has 0xf0000000 set - build = 0x0fffffff & build; - - return {major, minor, build}; -} - -WindowsInfo::Release WindowsInfo::getRelease() const -{ - // there are several interesting items in the registry, but most of them - // are undocumented, not always available, and localizable - // - // most of them are used to provide as much information as possible in case - // any of the other versions fail to work - - QSettings settings( - R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", - QSettings::NativeFormat); - - Release r; - - // buildlab seems to be an internal name from the build system - r.buildLab = settings.value("BuildLabEx", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildLab", "").toString(); - if (r.buildLab.isEmpty()) { - r.buildLab = settings.value("BuildBranch", "").toString(); - } - } - - // localized name of windows, such as "Windows 10 Pro" - r.productName = settings.value("ProductName", "").toString(); - - // release ID, such as 1803 - r.ID = settings.value("ReleaseId", "").toString(); - - // some other build number, shown in winver.exe - r.UBR = settings.value("UBR", 0).toUInt(); - - return r; -} - -std::optional WindowsInfo::getElevated() const -{ - HandlePtr token; - - { - HANDLE rawToken = 0; - - if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessageQ(e); - - return {}; - } - - token.reset(rawToken); - } - - TOKEN_ELEVATION e = {}; - DWORD size = sizeof(TOKEN_ELEVATION); - - if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { - const auto e = GetLastError(); - - qCritical() - << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessageQ(e); - - return {}; - } - - return (e.TokenIsElevated != 0); -} - - -SecurityProduct::SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate) : - m_guid(std::move(guid)), m_name(std::move(name)), m_provider(provider), - m_active(active), m_upToDate(upToDate) -{ -} - -const QString& SecurityProduct::name() const -{ - return m_name; -} - -int SecurityProduct::provider() const -{ - return m_provider; -} - -bool SecurityProduct::active() const -{ - return m_active; -} - -bool SecurityProduct::upToDate() const -{ - return m_upToDate; -} - -QString SecurityProduct::toString() const -{ - QString s; - - s += m_name + " (" + providerToString() + ")"; - - if (!m_active) { - s += ", inactive"; - } - - if (!m_upToDate) { - s += ", definitions outdated"; - } - - if (!m_guid.isNull()) { - s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); - } - - return s; -} - -QString SecurityProduct::providerToString() const -{ - QStringList ps; - - if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { - ps.push_back("firewall"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { - ps.push_back("autoupdate"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { - ps.push_back("antivirus"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { - ps.push_back("antispyware"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { - ps.push_back("settings"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { - ps.push_back("uac"); - } - - if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { - ps.push_back("service"); - } - - if (ps.empty()) { - return "doesn't provider anything"; - } - - return ps.join("|"); -} - - -struct Process -{ - std::wstring filename; - DWORD pid; - - Process(std::wstring f, DWORD id) - : filename(std::move(f)), pid(id) - { - } -}; - -// returns the filename of the given process or the current one -// -std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) -{ - // double the buffer size 10 times - const int MaxTries = 10; - - DWORD bufferSize = MAX_PATH; - - for (int tries=0; tries(bufferSize + 1); - std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); - - DWORD writtenSize = 0; - - if (process == INVALID_HANDLE_VALUE) { - // query this process - writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); - } else { - // query another process - writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); - } - - if (writtenSize == 0) { - // hard failure - const auto e = GetLastError(); - std::wcerr << formatSystemMessage(e) << L"\n"; - break; - } else if (writtenSize >= bufferSize) { - // buffer is too small, try again - bufferSize *= 2; - } else { - // if GetModuleFileName() works, `writtenSize` does not include the null - // terminator - const std::wstring s(buffer.get(), writtenSize); - const fs::path path(s); - - return path.filename().native(); - } - } - - // something failed or the path is way too long to make sense - - std::wstring what; - if (process == INVALID_HANDLE_VALUE) { - what = L"the current process"; - } else { - what = L"pid " + std::to_wstring(reinterpret_cast(process)); - } - - std::wcerr << L"failed to get filename for " << what << L"\n"; - return {}; -} - -std::vector runningProcessesIds() -{ - // double the buffer size 10 times - const int MaxTries = 10; - - // initial size of 300 processes, unlikely to be more than that - std::size_t size = 300; - - for (int tries=0; tries(size); - std::fill(ids.get(), ids.get() + size, 0); - - DWORD bytesGiven = static_cast(size * sizeof(ids[0])); - DWORD bytesWritten = 0; - - if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) - { - const auto e = GetLastError(); - - std::wcerr - << L"failed to enumerate processes, " - << formatSystemMessage(e) << L"\n"; - - return {}; - } - - if (bytesWritten == bytesGiven) { - // no way to distinguish between an exact fit and not enough space, - // just try again - size *= 2; - continue; - } - - const auto count = bytesWritten / sizeof(ids[0]); - return std::vector(ids.get(), ids.get() + count); - } - - std::cerr << L"too many processes to enumerate"; - return {}; -} - -std::vector runningProcesses() -{ - const auto pids = runningProcessesIds(); - std::vector v; - - for (const auto& pid : pids) { - if (pid == 0) { - // the idle process has pid 0 and seems to be picked up by EnumProcesses() - continue; - } - - HandlePtr h(OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); - - if (!h) { - const auto e = GetLastError(); - - if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot for system processes, even - // when elevated - std::wcerr - << L"failed to open process " << pid << L", " - << formatSystemMessage(e) << L"\n"; - } - - continue; - } - - auto filename = processFilename(h.get()); - if (!filename.empty()) { - v.emplace_back(std::move(filename), pid); - } - } - - return v; -} - -DWORD findOtherPid() -{ - const std::wstring defaultName = L"ModOrganizer.exe"; - - std::wclog << L"looking for the other process...\n"; - - // used to skip the current process below - const auto thisPid = GetCurrentProcessId(); - std::wclog << L"this process id is " << thisPid << L"\n"; - - // getting the filename for this process, assumes the other process has the - // smae one - auto filename = processFilename(); - if (filename.empty()) { - std::wcerr - << L"can't get current process filename, defaulting to " - << defaultName << L"\n"; - - filename = defaultName; - } else { - std::wclog << L"this process filename is " << filename << L"\n"; - } - - // getting all running processes - const auto processes = runningProcesses(); - std::wclog << L"there are " << processes.size() << L" processes running\n"; - - // going through processes, trying to find one with the same name and a - // different pid than this process has - for (const auto& p : processes) { - if (p.filename == filename) { - if (p.pid != thisPid) { - return p.pid; - } - } - } - - std::wclog - << L"no process with this filename\n" - << L"MO may not be running, or it may be running as administrator\n" - << L"you can try running this again as administrator\n"; - - return 0; -} - -std::wstring tempDir() -{ - const DWORD bufferSize = MAX_PATH + 1; - wchar_t buffer[bufferSize + 1] = {}; - - const auto written = GetTempPathW(bufferSize, buffer); - if (written == 0) { - const auto e = GetLastError(); - - std::wcerr - << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; - - return {}; - } - - // `written` does not include the null terminator - return std::wstring(buffer, buffer + written); -} - -HandlePtr tempFile(const std::wstring dir) -{ - // maximum tries of incrementing the counter - const int MaxTries = 100; - - // UTC time and date will be in the filename - const auto now = std::time(0); - const auto tm = std::gmtime(&now); - - // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where - // i can go until MaxTries - std::wostringstream oss; - oss - << L"ModOrganizer-" - << std::setw(4) << (1900 + tm->tm_year) - << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) - << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" - << std::setw(2) << std::setfill(L'0') << tm->tm_hour - << std::setw(2) << std::setfill(L'0') << tm->tm_min - << std::setw(2) << std::setfill(L'0') << tm->tm_sec; - - const std::wstring prefix = oss.str(); - const std::wstring ext = L".dmp"; - - // first path to try, without counter in it - std::wstring path = dir + L"\\" + prefix + ext; - - for (int i=0; i. #ifndef UTIL_H #define UTIL_H - #include -#include - -#define WIN32_LEAN_AND_MEAN -#include - #include -#include class Executable; @@ -51,442 +44,6 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); - -namespace env -{ - -class Console -{ -public: - Console(); - ~Console(); - -private: - bool m_hasConsole; - FILE* m_in; - FILE* m_out; - FILE* m_err; -}; - - -// an application shortcut that can be either on the desktop or the start menu -// -class Shortcut -{ -public: - // location of a shortcut - // - enum Locations - { - None = 0, - - // on the desktop - Desktop, - - // in the start menu - StartMenu - }; - - - // empty shortcut - // - Shortcut(); - - // shortcut from an executable - // - explicit Shortcut(const Executable& exe); - - // sets the name of the shortcut, shown on icons and start menu entries - // - Shortcut& name(const QString& s); - - // the program to start - // - Shortcut& target(const QString& s); - - // arguments to pass - // - Shortcut& arguments(const QString& s); - - // shows in the status bar of explorer, for example - // - Shortcut& description(const QString& s); - - // path to a binary that contains the icon and its index - // - Shortcut& icon(const QString& s, int index=0); - - // "start in" option for this shortcut - // - Shortcut& workingDirectory(const QString& s); - - - // returns whether this shortcut already exists at the given location; this - // does not check whether the shortcut parameters are different, it merely if - // the .lnk file exists - // - bool exists(Locations loc) const; - - // calls remove() if exists(), or add() - // - bool toggle(Locations loc); - - // adds the shortcut to the given location - // - bool add(Locations loc); - - // removes the shortcut from the given location - // - bool remove(Locations loc); - -private: - QString m_name; - QString m_target; - QString m_arguments; - QString m_description; - QString m_icon; - int m_iconIndex; - QString m_workingDirectory; - - // returns a qCritical() logger with a prefix already logged - // - QDebug critical() const; - - // returns a qDebug() logger with a prefix already logged - // - QDebug debug() const; - - - // returns the path where the shortcut file should be saved - // - QString shortcutPath(Locations loc) const; - - // returns the directory where the shortcut file should be saved - // - QString shortcutDirectory(Locations loc) const; - - // returns the filename of the shortcut file that should be used when saving - // - QString shortcutFilename() const; -}; - - -// returns a string representation of the given location -// -QString toString(Shortcut::Locations loc); - - -// represents one module -// -class Module -{ -public: - explicit Module(QString path, std::size_t fileSize); - - // returns the module's path - // - const QString& path() const; - - // returns the module's path in lowercase and using forward slashes - // - QString displayPath() const; - - // returns the size in bytes, may be 0 - // - std::size_t fileSize() const; - - // returns the x.x.x.x version embedded from the version info, may be empty - // - const QString& version() const; - - // returns the FileVersion entry from the resource file, returns - // "(no version)" if not available - // - const QString& versionString() const; - - // returns the build date from the version info, or the creation time of the - // file on the filesystem, may be empty - // - const QDateTime& timestamp() const; - - // returns the md5 of the file, may be empty for system files - // - const QString& md5() const; - - // converts timestamp() to a string for display, returns "(no timestamp)" if - // not available - // - QString timestampString() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - // contains the information from the version resource - // - struct FileInfo - { - VS_FIXEDFILEINFO ffi; - QString fileDescription; - }; - - QString m_path; - std::size_t m_fileSize; - QString m_version; - QDateTime m_timestamp; - QString m_versionString; - QString m_md5; - - // returns information from the version resource - // - FileInfo getFileInfo() const; - - // uses VS_FIXEDFILEINFO to build the version string - // - QString getVersion(const VS_FIXEDFILEINFO& fi) const; - - // uses the file date from VS_FIXEDFILEINFO if available, or gets the - // creation date on the file - // - QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; - - // returns the md5 hash unless the path contains "\windows\" - // - QString getMD5() const; - - // gets VS_FIXEDFILEINFO from the file version info buffer - // - VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; - - // gets FileVersion from the file version info buffer - // - QString getFileDescription(std::byte* buffer) const; -}; - - -// a variety of information on windows -// -class WindowsInfo -{ -public: - struct Version - { - DWORD major=0, minor=0, build=0; - - QString toString() const - { - return QString("%1.%2.%3").arg(major).arg(minor).arg(build); - } - - friend bool operator==(const Version& a, const Version& b) - { - return - a.major == b.major && - a.minor == b.minor && - a.build == b.build; - } - - friend bool operator!=(const Version& a, const Version& b) - { - return !(a == b); - } - }; - - struct Release - { - // the BuildLab entry from the registry, may be empty - QString buildLab; - - // product name such as "Windows 10 Pro", may not be in English, may be - // empty - QString productName; - - // release ID such as 1809, may be mepty - QString ID; - - // some sub-build number, undocumented, may be empty - DWORD UBR; - - Release() - : UBR(0) - { - } - }; - - - WindowsInfo(); - - // tries to guess whether this process is running in compatibility mode - // - bool compatibilityMode() const; - - // returns the Windows version, may not correspond to the actual version - // if the process is running in compatibility mode - // - const Version& reportedVersion() const; - - // tries to guess the real Windows version that's running, can be empty - // - const Version& realVersion() const; - - // various information about the current release - // - const Release& release() const; - - // whether this process is running as administrator, may be empty if the - // information is not available - std::optional isElevated() const; - - // returns a string with all the above information on one line - // - QString toString() const; - -private: - Version m_reported, m_real; - Release m_release; - std::optional m_elevated; - - // uses RtlGetVersion() to get the version number as reported by Windows - // - Version getReportedVersion(HINSTANCE ntdll) const; - - // uses RtlGetNtVersionNumbers() to get the real version number - // - Version getRealVersion(HINSTANCE ntdll) const; - - // gets various information from the registry - // - Release getRelease() const; - - // gets whether the process is elevated - // - std::optional getElevated() const; -}; - - -// represents a security product, such as an antivirus or a firewall -// -class SecurityProduct -{ -public: - SecurityProduct( - QUuid guid, QString name, int provider, - bool active, bool upToDate); - - // display name of the product - // - const QString& name() const; - - // a bunch of _WSC_SECURITY_PROVIDER flags - // - int provider() const; - - // whether the product is active - // - bool active() const; - - // whether its definitions are up-to-date - // - bool upToDate() const; - - // string representation of the above - // - QString toString() const; - -private: - QUuid m_guid; - QString m_name; - int m_provider; - bool m_active; - bool m_upToDate; - - QString providerToString() const; -}; - - -class Metrics -{ -public: - struct Display - { - int resX=0, resY=0, dpi=0; - bool primary=false; - int refreshRate = 0; - QString monitor, adapter; - - QString toString() const; - }; - - Metrics(); - - const std::vector& displays() const; - -private: - std::vector m_displays; -}; - - -// represents the process's environment -// -class Environment -{ -public: - Environment(); - - // list of loaded modules in the current process - // - const std::vector& loadedModules() const; - - // information about the operating system - // - const WindowsInfo& windowsInfo() const; - - // information about the installed security products - // - const std::vector& securityProducts() const; - - // information about displays - // - const Metrics& metrics() const; - - // logs the environment - // - void dump() const; - -private: - std::vector m_modules; - WindowsInfo m_windows; - std::vector m_security; - Metrics m_metrics; - - std::vector getLoadedModules() const; - std::vector getSecurityProducts() const; - - std::vector getSecurityProductsFromWMI() const; - std::optional getWindowsFirewall() const; -}; - - -enum class CoreDumpTypes -{ - Mini = 1, - Data, - Full -}; - -// creates a minidump file for the given process -// -bool coredump(CoreDumpTypes type); - -// finds another process with the same name as this one and creates a minidump -// file for it -// -bool coredumpOther(CoreDumpTypes type); - -} // namespace env - - MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From d8760ed8ad688c7e69d2a5be89a8574f4bf44f74 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 18 Jul 2019 23:52:11 -0400 Subject: refactored Metrics and Display, no change in functionality --- src/env.h | 39 +++++- src/envmetrics.cpp | 348 +++++++++++++++++++++++++++++------------------------ src/envmetrics.h | 62 ++++++++-- src/envwindows.cpp | 2 +- 4 files changed, 281 insertions(+), 170 deletions(-) (limited to 'src') diff --git a/src/env.h b/src/env.h index 1e913a40..0e88263b 100644 --- a/src/env.h +++ b/src/env.h @@ -6,6 +6,9 @@ class SecurityProduct; class WindowsInfo; class Metrics; + +// used by HandlePtr, calls CloseHandle() as the deleter +// struct HandleCloser { using pointer = HANDLE; @@ -21,6 +24,25 @@ struct HandleCloser using HandlePtr = std::unique_ptr; +// used by DesktopDCPtr, calls ReleaseDC(0, dc) as the deleter +// +struct DesktopDCReleaser +{ + using pointer = HDC; + + void operator()(HDC dc) + { + if (dc != 0) { + ::ReleaseDC(0, dc); + } + } +}; + +using DesktopDCPtr = std::unique_ptr; + + +// used by LibraryPtr, calls FreeLibrary as the deleter +// struct LibraryFreer { using pointer = HINSTANCE; @@ -33,6 +55,11 @@ struct LibraryFreer } }; +using LibraryPtr = std::unique_ptr; + + +// used by COMPtr, calls Release() as the deleter +// struct COMReleaser { void operator()(IUnknown* p) @@ -43,19 +70,29 @@ struct COMReleaser } }; - template using COMPtr = std::unique_ptr; +// creates a console in the constructor and destroys it in the destructor, +// also redirects standard streams +// class Console { public: + // opens the console and redirects standard streams to it + // Console(); + + // destroys the console and redirects the standard stream to NUL + // ~Console(); private: + // whether the console was allocated successfully bool m_hasConsole; + + // standard streams FILE* m_in; FILE* m_out; FILE* m_err; diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index a6988909..784e4baf 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -10,215 +10,245 @@ namespace env using namespace MOBase; -class DisplayEnumerator +// fallback for windows 7 +// +int getDesktopDpi() { -public: - DisplayEnumerator() - : m_GetDpiForMonitor(nullptr) + // desktop DC + DesktopDCPtr dc(GetDC(0)); + + if (!dc) { + const auto e = GetLastError(); + log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + return 0; + } + + return GetDeviceCaps(dc.get(), LOGPIXELSX); +} + +// finds a monitor by device name; there's no real good way to do that except +// by enumerating all the monitors and checking their name +// +HMONITOR findMonitor(const QString& name) +{ + // passed to the enumeration callback + struct Data { - m_shcore.reset(LoadLibraryW(L"Shcore.dll")); + QString name; + HMONITOR hm; + }; - if (m_shcore) { - // windows 8.1+ only - m_GetDpiForMonitor = reinterpret_cast( - GetProcAddress(m_shcore.get(), "GetDpiForMonitor")); + Data data = {name, 0}; + + // callback + auto callback = [](HMONITOR hm, HDC, RECT*, LPARAM lp) { + auto& data = *reinterpret_cast(lp); + + MONITORINFOEX mi = {}; + mi.cbSize = sizeof(mi); + + // monitor info will include the name + if (!GetMonitorInfoW(hm, &mi)) { + const auto e = GetLastError(); + log::error( + "GetMonitorInfo() failed for '{}', {}", + data.name, formatSystemMessageQ(e)); + + // error for this monitor, but continue + return TRUE; } - // gets all monitors and the device they're running on - getDisplayDevices(); - } + if (QString::fromWCharArray(mi.szDevice) == data.name) { + // found, stop + data.hm = hm; + return FALSE; + } - std::vector&& displays() && - { - return std::move(m_displays); - } + // not found, continue to the next monitor + return TRUE; + }; - const std::vector& displays() const & - { - return m_displays; - } -private: + // for each monitor + EnumDisplayMonitors(0, nullptr, callback, reinterpret_cast(&data)); + + return data.hm; +} + +// returns the dpi for the given monitor; for systems that do not support +// per-monitor dpi (such as windows 7), this is the desktop dpi +// +int getDpi(const QString& monitorDevice) +{ using GetDpiForMonitorFunction = HRESULT WINAPI (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); - std::unique_ptr m_shcore; - GetDpiForMonitorFunction* m_GetDpiForMonitor; - std::vector m_displays; + static LibraryPtr shcore; + static GetDpiForMonitorFunction* GetDpiForMonitor = nullptr; + static bool checked = false; - void getDisplayDevices() - { - // don't bother if it goes over 100 - for (int i=0; i<100; ++i) { - DISPLAY_DEVICEW device = {}; - device.cb = sizeof(device); - - if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { - // no more - break; - } - - // EnumDisplayDevices() seems to be returning a lot of devices that are - // not actually monitors, but those don't have the - // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set - if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { - continue; - } - - m_displays.push_back(createDisplay(device)); + if (!checked) { + // try to find GetDpiForMonitor() from shcored.dll + + shcore.reset(LoadLibraryW(L"Shcore.dll")); + + if (shcore) { + // windows 8.1+ only + GetDpiForMonitor = reinterpret_cast( + GetProcAddress(shcore.get(), "GetDpiForMonitor")); } + + checked = true; } - Metrics::Display createDisplay(const DISPLAY_DEVICEW& device) - { - Metrics::Display d; + if (!GetDpiForMonitor) { + // get the desktop dpi instead + return getDesktopDpi(); + } - d.adapter = QString::fromWCharArray(device.DeviceString); - d.monitor = QString::fromWCharArray(device.DeviceName); - d.primary = (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE); - getDisplaySettings(device.DeviceName, d); - getDpi(d); + // there's no way to get an HMONITOR from a device name, so all monitors + // will have to be enumerated and their name checked + HMONITOR hm = findMonitor(monitorDevice); + if (!hm) { + log::error("can't get dpi for monitor '{}', not found", monitorDevice); + return 0; + } + + UINT dpiX=0, dpiY=0; + const auto r = GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); + + if (FAILED(r)) { + log::error( + "GetDpiForMonitor() failed for '{}', {}", + monitorDevice, formatSystemMessageQ(r)); - return d; + return 0; } - void getDisplaySettings(const wchar_t* monitorName, Metrics::Display& d) - { - DEVMODEW dm = {}; - dm.dmSize = sizeof(dm); + // dpiX and dpiY are always identical, as per the documentation + return dpiX; +} - if (!EnumDisplaySettingsW(monitorName, ENUM_CURRENT_SETTINGS, &dm)) { - log::error("EnumDisplaySettings() failed for '{}'", d.monitor); - return; - } - // all these fields should be available +Display::Display(QString adapter, QString monitorDevice, bool primary) : + m_adapter(std::move(adapter)), + m_monitorDevice(std::move(monitorDevice)), + m_primary(primary), + m_resX(0), m_resY(0), m_dpi(0), m_refreshRate(0) +{ + getSettings(); + m_dpi = getDpi(m_monitorDevice); +} - if (dm.dmFields & DM_DISPLAYFREQUENCY) { - d.refreshRate = dm.dmDisplayFrequency; - } +const QString& Display::adapter() const +{ + return m_adapter; +} - if (dm.dmFields & DM_PELSWIDTH) { - d.resX = dm.dmPelsWidth; - } +const QString& Display::monitorDevice() const +{ + return m_monitorDevice; +} - if (dm.dmFields & DM_PELSHEIGHT) { - d.resY = dm.dmPelsHeight; - } - } +bool Display::primary() +{ + return m_primary; +} - void getDpi(Metrics::Display& d) - { - if (!m_GetDpiForMonitor) { - // this happens on windows 7, get the desktop dpi instead - getDesktopDpi(d); - return; - } +int Display::resX() const +{ + return m_resX; +} - // there's no way to get an HMONITOR from a device name, so all monitors - // will have to be enumerated and their name checked - HMONITOR hm = findMonitor(d.monitor); - if (!hm) { - log::error("can't get dpi for monitor '{}', not found", d.monitor); - return; - } +int Display::resY() const +{ + return m_resY; +} - UINT dpiX=0, dpiY=0; - const auto r = m_GetDpiForMonitor(hm, MDT_EFFECTIVE_DPI, &dpiX, &dpiY); +int Display::dpi() +{ + return m_dpi; +} - if (FAILED(r)) { - log::error( - "GetDpiForMonitor() failed for '{}', {}", - d.monitor, formatSystemMessageQ(r)); +int Display::refreshRate() const +{ + return m_refreshRate; +} - return; - } +QString Display::toString() const +{ + return QString("%1*%2 %3hz dpi=%4 on %5%6") + .arg(m_resX) + .arg(m_resY) + .arg(m_refreshRate) + .arg(m_dpi) + .arg(m_adapter) + .arg(m_primary ? " (primary)" : ""); +} - // dpiX and dpiY are always identical, as per the documentation - d.dpi = dpiX; - } +void Display::getSettings() +{ + DEVMODEW dm = {}; + dm.dmSize = sizeof(dm); - void getDesktopDpi(Metrics::Display& d) - { - // desktop dc - HDC dc = GetDC(0); + const auto wsDevice = m_monitorDevice.toStdWString(); - if (!dc) { - const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); - return; - } + if (!EnumDisplaySettingsW(wsDevice.c_str(), ENUM_CURRENT_SETTINGS, &dm)) { + log::error("EnumDisplaySettings() failed for '{}'", m_monitorDevice); + return; + } - d.dpi = GetDeviceCaps(dc, LOGPIXELSX); + // all these fields should be available - ReleaseDC(0, dc); + if (dm.dmFields & DM_DISPLAYFREQUENCY) { + m_refreshRate = dm.dmDisplayFrequency; } - HMONITOR findMonitor(const QString& name) - { - // passed to the enumeration callback - struct Data - { - DisplayEnumerator* self; - QString name; - HMONITOR hm; - }; - - Data data = {this, name, 0}; - - // for each monitor - EnumDisplayMonitors(0, nullptr, [](HMONITOR hm, HDC, RECT*, LPARAM lp) { - auto& data = *reinterpret_cast(lp); - - MONITORINFOEX mi = {}; - mi.cbSize = sizeof(mi); - - // monitor info will include the name - if (!GetMonitorInfoW(hm, &mi)) { - const auto e = GetLastError(); - log::error( - "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); - - // error for this monitor, but continue - return TRUE; - } - - if (QString::fromWCharArray(mi.szDevice) == data.name) { - // found, stop - data.hm = hm; - return FALSE; - } - - // not found, continue to the next monitor - return TRUE; - }, reinterpret_cast(&data)); + if (dm.dmFields & DM_PELSWIDTH) { + m_resX = dm.dmPelsWidth; + } - return data.hm; + if (dm.dmFields & DM_PELSHEIGHT) { + m_resY = dm.dmPelsHeight; } -}; +} Metrics::Metrics() { - m_displays = DisplayEnumerator().displays(); + getDisplays(); } -const std::vector& Metrics::displays() const +const std::vector& Metrics::displays() const { return m_displays; } -QString Metrics::Display::toString() const +void Metrics::getDisplays() { - return QString("%1*%2 %3hz dpi=%4 on %5%6") - .arg(resX) - .arg(resY) - .arg(refreshRate) - .arg(dpi) - .arg(adapter) - .arg(primary ? " (primary)" : ""); + // don't bother if it goes over 100 + for (int i=0; i<100; ++i) { + DISPLAY_DEVICEW device = {}; + device.cb = sizeof(device); + + if (!EnumDisplayDevicesW(nullptr, i, &device, 0)) { + // no more + break; + } + + // EnumDisplayDevices() seems to be returning a lot of devices that are + // not actually monitors, but those don't have the + // DISPLAY_DEVICE_ATTACHED_TO_DESKTOP bit set + if ((device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0) { + continue; + } + + m_displays.emplace_back( + QString::fromWCharArray(device.DeviceString), + QString::fromWCharArray(device.DeviceName), + (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)); + } } } // namespace diff --git a/src/envmetrics.h b/src/envmetrics.h index 62fc8c49..bede36fc 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -4,25 +4,69 @@ namespace env { -class Metrics +// information about a monitor +// +class Display { public: - struct Display - { - int resX=0, resY=0, dpi=0; - bool primary=false; - int refreshRate = 0; - QString monitor, adapter; + Display(QString adapter, QString monitorDevice, bool primary); + + // display name of the adapter running the monitor + // + const QString& adapter() const; + + // internal device name of the monitor, this is not a display name + // + const QString& monitorDevice() const; + + // whether this monitor is the primary + // + bool primary(); + + // resolution + // + int resX() const; + int resY() const; + + // dpi + // + int dpi(); + + // refresh rate in hz + // + int refreshRate() const; + + // string representation + // + QString toString() const; + +private: + QString m_adapter; + QString m_monitorDevice; + bool m_primary; + int m_resX, m_resY; + int m_dpi; + int m_refreshRate; + + void getSettings(); +}; - QString toString() const; - }; +// holds various information about Windows metrics +// +class Metrics +{ +public: Metrics(); + // list of displays on the system + // const std::vector& displays() const; private: std::vector m_displays; + + void getDisplays(); }; } // namespace diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 718cf2ce..4fbd788a 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -10,7 +10,7 @@ using namespace MOBase; WindowsInfo::WindowsInfo() { // loading ntdll.dll, the functions will be found with GetProcAddress() - std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); if (!ntdll) { qCritical() << "failed to load ntdll.dll while getting version"; -- cgit v1.3.1 From f13f5e21c42b3b5bdfdc4fcab50e10abd92c8486 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 00:11:43 -0400 Subject: replaced qInfo() with log::info() --- src/mainwindow.cpp | 2 +- src/modinfo.cpp | 10 +++++----- src/modinfodialognexus.cpp | 5 +++-- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 87ee2c8e..9dbada1c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5907,7 +5907,7 @@ void MainWindow::finishUpdateInfo() auto finalMods = watcher->result(); if (finalMods.empty()) { - qInfo("None of your mods appear to have had recent file updates."); + log::info("None of your mods appear to have had recent file updates."); } std::set> organizedGames; diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 3484b644..92c7366c 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -37,6 +37,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -322,10 +323,9 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei qWarning() << tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); updatesAvailable = false; } else { - qInfo() << tr( + log::info("{}", tr( "You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. " - "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods." - ); + "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods.")); } for (auto game : organizedGames) @@ -412,7 +412,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei }); if (mods.size()) { - qInfo("Checking updates for %d mods...", mods.size()); + log::info("Checking updates for {} mods...", mods.size()); for (auto mod : mods) { organizedGames.insert(std::make_pair(mod->getGameName().toLower(), mod->getNexusID())); @@ -422,7 +422,7 @@ void ModInfo::manualUpdateCheck(PluginContainer *pluginContainer, QObject *recei NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); } } else { - qInfo("None of the selected mods can be updated."); + log::info("None of the selected mods can be updated."); } } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index e606525c..6d28cbe3 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -6,8 +6,9 @@ #include "bbcode.h" #include #include +#include -namespace shell = MOBase::shell; +using namespace MOBase; bool isValidModID(int id) { @@ -350,7 +351,7 @@ void NexusTab::onRefreshBrowser() mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); updateWebpage(); } else { - qInfo("Mod has no valid Nexus ID, info can't be updated."); + log::info("Mod has no valid Nexus ID, info can't be updated."); } } -- cgit v1.3.1 From aae6d6a5aa8d6b101fcc38388222a8a6e7ee2ec6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 01:09:19 -0400 Subject: replaced qWarning() --- src/bbcode.cpp | 9 ++++---- src/categories.cpp | 3 ++- src/directoryrefresher.cpp | 4 ++-- src/downloadmanager.cpp | 21 +++++++++--------- src/editexecutablesdialog.cpp | 19 ++++++++-------- src/executableslist.cpp | 6 +++--- src/filerenamer.cpp | 7 ++++-- src/icondelegate.cpp | 4 +++- src/installationmanager.cpp | 2 +- src/instancemanager.cpp | 14 +++++++----- src/mainwindow.cpp | 50 +++++++++++++++++++++++-------------------- src/moapplication.cpp | 5 +++-- src/modflagicondelegate.cpp | 4 +++- src/modinfo.cpp | 2 +- src/modinfodialogesps.cpp | 7 +++--- src/modlist.cpp | 7 +++--- src/modlistsortproxy.cpp | 6 ++++-- src/nexusinterface.cpp | 27 +++++++++++++---------- src/nxmaccessmanager.cpp | 2 +- src/organizercore.cpp | 46 +++++++++++++++++++-------------------- src/plugincontainer.cpp | 11 +++++----- src/pluginlist.cpp | 4 ++-- src/problemsdialog.cpp | 2 +- src/profile.cpp | 21 ++++++++++-------- src/profilesdialog.cpp | 4 ++-- src/settings.cpp | 5 +++-- 26 files changed, 161 insertions(+), 131 deletions(-) (limited to 'src') diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 323dd128..d9b7debd 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -18,13 +18,13 @@ along with Mod Organizer. If not, see . */ #include "bbcode.h" - +#include #include #include - namespace BBCode { +namespace log = MOBase::log; class BBCodeMap { @@ -88,7 +88,7 @@ public: return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); } } else { - qWarning("don't know how to deal with tag %s", qUtf8Printable(tagName)); + log::warn("don't know how to deal with tag {}", tagName); } } else { if (tagName == "*") { @@ -99,8 +99,7 @@ public: } else { // expression doesn't match. either the input string is invalid // or the expression is - qWarning("%s doesn't match the expression for %s", - qUtf8Printable(temp), qUtf8Printable(tagName)); + log::warn("{} doesn't match the expression for {}", temp, tagName); length = 0; return QString(); } diff --git a/src/categories.cpp b/src/categories.cpp index 9e5fa9f7..8f9d3ad8 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include #include +#include #include #include @@ -294,7 +295,7 @@ bool CategoryFactory::isDecendantOf(int id, int parentID) const return isDecendantOf(m_Categories[index].m_ParentID, parentID); } } else { - qWarning("%d is no valid category id", id); + log::warn("{} is no valid category id", id); return false; } } diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 3ce4691b..87305599 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -127,7 +127,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu FilesOrigin &origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority); for (const QString &filename : stealFiles) { if (filename.isEmpty()) { - qWarning("Trying to find file with no name"); + log::warn("Trying to find file with no name"); continue; } QFileInfo fileInfo(filename); @@ -143,7 +143,7 @@ void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructu QString warnStr = fileInfo.absolutePath(); if (warnStr.isEmpty()) warnStr = filename; - qWarning("file not found: %s", qUtf8Printable(warnStr)); + log::warn("file not found: {}", warnStr); } } } else { diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 648b102a..e3ceb261 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -924,7 +924,7 @@ void DownloadManager::queryInfo(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); + log::warn("re-querying file info is currently only possible with Nexus"); return; } @@ -976,7 +976,7 @@ void DownloadManager::queryInfoMd5(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); + log::warn("re-querying file info is currently only possible with Nexus"); return; } @@ -1016,7 +1016,7 @@ void DownloadManager::visitOnNexus(int index) DownloadInfo *info = m_ActiveDownloads[index]; if (info->m_FileInfo->repository != "Nexus") { - qWarning("Visiting mod page is currently only possible with Nexus"); + log::warn("Visiting mod page is currently only possible with Nexus"); return; } @@ -1614,8 +1614,9 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian } } else { if (info->m_FileInfo->fileID == 0) { - qWarning("could not determine file id for %s (state %d)", - qUtf8Printable(info->m_FileName), info->m_State); + log::warn( + "could not determine file id for {} (state {})", + info->m_FileName, info->m_State); } } @@ -1999,7 +2000,7 @@ void DownloadManager::downloadFinished(int index) resumeDownloadInt(index); } } else { - qWarning("no download index %d", index); + log::warn("no download index {}", index); } } @@ -2008,9 +2009,9 @@ void DownloadManager::downloadError(QNetworkReply::NetworkError error) { if (error != QNetworkReply::OperationCanceledError) { QNetworkReply *reply = qobject_cast(sender()); - qWarning("%s (%d)", reply != nullptr ? qUtf8Printable(reply->errorString()) - : "Download error occured", - error); + log::warn("{} ({})", + reply != nullptr ? reply->errorString() : "Download error occured", + error); } } @@ -2033,7 +2034,7 @@ void DownloadManager::metaDataChanged() } } } else { - qWarning("meta data event for unknown download"); + log::warn("meta data event for unknown download"); } } diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8929d207..3ec3d64f 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -101,9 +101,7 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const auto itor = m_executablesList.find(title); if (itor == m_executablesList.end()) { - qWarning().nospace() - << "getExecutablesList(): executable '" << title << "' not found"; - + log::warn("getExecutablesList(): executable '{}' not found", title); continue; } @@ -293,9 +291,10 @@ void EditExecutablesDialog::setEdits(const Executable& e) 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"; + log::warn( + "executable '{}' uses mod '{}' as a custom overwrite, but that mod " + "doesn't exist", + e.title(), modName->value); } } @@ -335,7 +334,7 @@ void EditExecutablesDialog::save() auto* e = selectedExe(); if (!e) { - qWarning("trying to save but nothing is selected"); + log::warn("trying to save but nothing is selected"); return; } @@ -475,13 +474,13 @@ void EditExecutablesDialog::on_remove_clicked() { auto* item = selectedItem(); if (!item) { - qWarning("trying to remove entry but nothing is selected"); + log::warn("trying to remove entry but nothing is selected"); return; } auto* exe = selectedExe(); if (!exe) { - qWarning("trying to remove entry but nothing is selected"); + log::warn("trying to remove entry but nothing is selected"); return; } @@ -646,7 +645,7 @@ void EditExecutablesDialog::on_configureLibraries_clicked() { auto* e = selectedExe(); if (!e) { - qWarning("trying to configure libraries but nothing is selected"); + log::warn("trying to configure libraries but nothing is selected"); return; } diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 0ca880cd..fbb96bd4 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -250,9 +250,9 @@ void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags) return; } - qWarning().nospace() - << "executable '" << itor->title() << "' was in the way and was " - << "renamed to '" << *newTitle << "'"; + log::warn( + "executable '{}' was in the way and was renamed to '{}'", + itor->title(), *newTitle); itor->title(*newTitle); itor = end(); diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index c5c6782b..b516c902 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -1,7 +1,10 @@ #include "filerenamer.h" +#include #include #include +using namespace MOBase; + FileRenamer::FileRenamer(QWidget* parent, QFlags flags) : m_parent(parent), m_flags(flags) { @@ -34,7 +37,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt qDebug().nospace() << "removing " << newName; // user wants to replace the file, so remove it if (!QFile(newName).remove()) { - qWarning().nospace() << "failed to remove " << newName; + log::warn("failed to remove '{}'", newName); // removal failed, warn the user and allow canceling if (!removeFailed(newName)) { qDebug().nospace() << "canceling " << oldName; @@ -62,7 +65,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt // target either didn't exist or was removed correctly if (!QFile::rename(oldName, newName)) { - qWarning().nospace() << "failed to rename " << oldName << " to " << newName; + log::warn("failed to rename '{}' to '{}'", oldName, newName); // renaming failed, warn the user and allow canceling if (!renameFailed(oldName, newName)) { diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 249dae6f..39038f3c 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -18,12 +18,14 @@ along with Mod Organizer. If not, see . */ #include "icondelegate.h" +#include #include #include #include #include #include +using namespace MOBase; IconDelegate::IconDelegate(QObject *parent) : QStyledItemDelegate(parent) @@ -54,7 +56,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, if (!QPixmapCache::find(fullIconId, &icon)) { icon = QIcon(iconId).pixmap(iconWidth, iconWidth); if (icon.isNull()) { - qWarning("failed to load icon %s", qUtf8Printable(iconId)); + log::warn("failed to load icon {}", iconId); } QPixmapCache::insert(fullIconId, icon); } diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index e443a8f2..0e50de52 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -693,7 +693,7 @@ void InstallationManager::postInstallCleanup() QFile::setPermissions(fileInfo.absoluteFilePath(), QFile::ReadOther | QFile::WriteOther); } if (!QFile::remove(fileInfo.absoluteFilePath())) { - qWarning() << "Unable to delete " << fileInfo.absoluteFilePath(); + log::warn("Unable to delete {}", fileInfo.absoluteFilePath()); } } directoriesToRemove.insert(fileInfo.absolutePath()); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index ddc2d067..55ef3fc8 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "instancemanager.h" #include "selectiondialog.h" #include +#include #include #include #include @@ -29,13 +30,13 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; static const char COMPANY_NAME[] = "Tannin"; static const char APPLICATION_NAME[] = "Mod Organizer"; static const char INSTANCE_KEY[] = "CurrentInstance"; - InstanceManager::InstanceManager() : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) { @@ -86,10 +87,13 @@ bool InstanceManager::deleteLocalInstance(const QString &instanceId) const if (!MOBase::shellDelete(QStringList(instancePath),true)) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(instancePath), ::GetLastError()); + log::warn( + "Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", + instancePath, ::GetLastError()); + if (!MOBase::removeDir(instancePath)) { - qWarning("regular delete failed too"); + log::warn("regular delete failed too"); result = false; } } @@ -153,7 +157,7 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons dialogText = dialog.textValue(); instanceId = sanitizeInstanceName(dialogText); if (instanceId != dialogText) { - if (QMessageBox::question( nullptr, + if (QMessageBox::question( nullptr, QObject::tr("Invalid instance name"), QObject::tr("The instance name \"%1\" is invalid. Use the name \"%2\" instead?").arg(dialogText,instanceId), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { @@ -323,7 +327,7 @@ QString InstanceManager::sanitizeInstanceName(const QString &name) const // Don't end in spaces and periods new_name = new_name.remove(QRegExp("\\.*$")); new_name = new_name.remove(QRegExp(" *$")); - + // Recurse until stuff stops changing if (new_name != name) { return sanitizeInstanceName(new_name); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9dbada1c..70ace8f1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -800,7 +800,7 @@ void MainWindow::setupToolbar() ui->toolBar->insertWidget(m_linksSeparator, spacer); } else { - qWarning("no separator found on the toolbar, icons won't be right-aligned"); + log::warn("no separator found on the toolbar, icons won't be right-aligned"); } } @@ -1646,9 +1646,7 @@ void MainWindow::startExeAction() auto itor = list.find(title); if (itor == list.end()) { - qWarning().nospace() - << "startExeAction(): executable '" << title << "' not found"; - + log::warn("startExeAction(): executable '{}' not found", title); return; } @@ -1874,17 +1872,18 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { // read the data we need from the sub-item, then dispose of it QTreeWidgetItem *onDemandDataItem = item->child(0); - std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString()); + const QString path = onDemandDataItem->data(0, Qt::UserRole + 1).toString(); + std::wstring wspath = path.toStdWString(); bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); - std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); + std::wstring virtualPath = (wspath + L"\\").substr(6) + ToWString(item->text(0)); DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath); if (dir != nullptr) { QIcon folderIcon = (new QFileIconProvider())->icon(QFileIconProvider::Folder); QIcon fileIcon = (new QFileIconProvider())->icon(QFileIconProvider::File); - updateTo(item, path, *dir, conflictsOnly, &fileIcon, &folderIcon); + updateTo(item, wspath, *dir, conflictsOnly, &fileIcon, &folderIcon); } else { - qWarning("failed to update view of %ls", path.c_str()); + log::warn("failed to update view of {}", path); } m_RemoveWidget.push_back(item); QTimer::singleShot(5, this, SLOT(delayedRemove())); @@ -2380,10 +2379,17 @@ void MainWindow::processUpdates() { if (currentVersion > lastVersion) { //NOP - } else if (currentVersion < lastVersion) - qWarning() << tr("Notice: Your current MO version (%1) is lower than the previously used one (%2). " - "The GUI may not downgrade gracefully, so you may experience oddities. " - "However, there should be no serious issues.").arg(currentVersion.toString()).arg(lastVersion.toString()).toStdWString(); + } else if (currentVersion < lastVersion) { + const auto text = tr( + "Notice: Your current MO version (%1) is lower than the previously used one (%2). " + "The GUI may not downgrade gracefully, so you may experience oddities. " + "However, there should be no serious issues.") + .arg(currentVersion.toString()) + .arg(lastVersion.toString()); + + log::warn("{}", text); + } + //save version in all case settings.setValue("version", currentVersion.toString()); } @@ -2924,7 +2930,7 @@ void MainWindow::refreshFilters() while (currentID != 0) { categoriesUsed.insert(currentID); if (!cycleTest.insert(currentID).second) { - qWarning("cycle in categories: %s", qUtf8Printable(SetJoin(cycleTest, ", "))); + log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); break; } currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); @@ -4008,7 +4014,7 @@ void MainWindow::moveOverwriteContentToExistingMod() } if (modAbsolutePath.isNull()) { - qWarning("Mod %s has not been found, for some reason", qUtf8Printable(result)); + log::warn("Mod {} has not been found, for some reason", result); return; } @@ -4404,7 +4410,7 @@ void MainWindow::saveArchiveList() qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); } } else { - qWarning("archive list not initialised"); + log::warn("archive list not initialised"); } } @@ -4421,7 +4427,7 @@ void MainWindow::checkModsForUpdates() m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { - qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); + log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); } } @@ -5802,7 +5808,7 @@ void MainWindow::modUpdateCheck(std::multimap IDs) m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else - qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); + log::warn("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); } } @@ -5918,7 +5924,7 @@ void MainWindow::finishUpdateInfo() } if (!finalMods.empty() && organizedGames.empty()) - qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + log::warn("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); for (auto game : organizedGames) NexusInterface::instance(&m_PluginContainer)->requestUpdates(game.second, this, QVariant(), game.first, QString()); @@ -6368,9 +6374,7 @@ void MainWindow::removeFromToolbar() auto itor = list.find(title); if (itor == list.end()) { - qWarning().nospace() - << "removeFromToolbar(): executable '" << title << "' not found"; - + log::warn("removeFromToolbar(): executable '{}' not found", title); return; } @@ -6570,7 +6574,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe if (progidx != std::string::npos) { dialog.setLabelText(line.substr(progidx + 11).c_str()); } else if (erroridx != std::string::npos) { - qWarning("%s", line.c_str()); + log::warn("{}", line); errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); } else { std::smatch match; @@ -6928,7 +6932,7 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m { QFileInfo file(url.toLocalFile()); if (!file.exists()) { - qWarning("invalid source file: %s", qUtf8Printable(file.absoluteFilePath())); + log::warn("invalid source file: {}", file.absoluteFilePath()); return; } QString target = outputDir + "/" + file.fileName(); diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 5652833a..3d55b28d 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "moapplication.h" #include #include +#include #include #include #include @@ -36,7 +37,7 @@ along with Mod Organizer. If not, see . #include -using MOBase::reportError; +using namespace MOBase; class ProxyStyle : public QProxyStyle { @@ -137,7 +138,7 @@ void MOApplication::updateStyle(const QString &fileName) if (QFile::exists(fileName)) { setStyleSheet(QString("file:///%1").arg(fileName)); } else { - qWarning("invalid stylesheet: %s", qUtf8Printable(fileName)); + log::warn("invalid stylesheet: {}", fileName); } } } diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index c3142962..7110a590 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,6 +1,8 @@ #include "modflagicondelegate.h" +#include #include +using namespace MOBase; ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED , ModInfo::FLAG_CONFLICT_OVERWRITE @@ -117,7 +119,7 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const case ModInfo::FLAG_PLUGIN_SELECTED: return QString(); case ModInfo::FLAG_TRACKED: return QStringLiteral(":/MO/gui/tracked"); default: - qWarning("ModInfo flag %d has no defined icon", flag); + log::warn("ModInfo flag {} has no defined icon", flag); return QString(); } } diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 92c7366c..ca6e8046 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -320,7 +320,7 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei } if (organizedGames.empty()) { - qWarning() << tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + log::warn("{}", tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests.")); updatesAvailable = false; } else { log::info("{}", tr( diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index fba5d39a..3130b4bd 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -3,8 +3,9 @@ #include "modinfodialog.h" #include "settings.h" #include +#include -using MOBase::reportError; +using namespace MOBase; class ESPItem { @@ -297,7 +298,7 @@ void ESPsTab::onActivate() } if (esp->isActive()) { - qWarning("ESPsTab::onActive(): item is already active"); + log::warn("ESPsTab::onActive(): item is already active"); return; } @@ -348,7 +349,7 @@ void ESPsTab::onDeactivate() } if (!esp->isActive()) { - qWarning("ESPsTab::onDeactivate(): item is already inactive"); + log::warn("ESPsTab::onDeactivate(): item is already inactive"); return; } diff --git a/src/modlist.cpp b/src/modlist.cpp index 7b71355c..df25df0d 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -275,7 +275,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QString(); } } else { - qWarning("category %d doesn't exist (may have been removed)", category); + log::warn("category {} doesn't exist (may have been removed)", category); modInfo->setCategory(category, false); return QString(); } @@ -618,8 +618,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) result = true; } break; default: { - qWarning("edit on column \"%s\" not supported", - getColumnName(index.column()).toUtf8().constData()); + log::warn( + "edit on column \"{}\" not supported", + getColumnName(index.column()).toUtf8().constData()); result = false; } break; } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 2d9ea4a5..1127c7d4 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "profile.h" #include "messagedialog.h" #include "qtgroupingproxy.h" +#include #include #include #include @@ -30,6 +31,7 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) : QSortFilterProxyModel(parent) @@ -240,7 +242,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, // nop, already compared by priority } break; default: { - qWarning() << "Sorting is not defined for column " << left.column(); + log::warn("Sorting is not defined for column {}", left.column()); } break; } return lt; @@ -474,7 +476,7 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons } if (row >= static_cast(m_Profile->numMods())) { - qWarning("invalid row index: %d", row); + log::warn("invalid row index: {}", row); return false; } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 2bcd72f3..008f3c0d 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "bbcode.h" #include #include +#include #include #include @@ -695,9 +696,13 @@ void NexusInterface::nextRequest() QTime time = QTime::currentTime(); QTime targetTime; targetTime.setHMS((time.hour() + 1) % 23, 5, 0); - QString warning = tr("You've exceeded the Nexus API rate limit and requests are now being throttled. " - "Your next batch of requests will be available in approximately %1 minutes and %2 seconds."); - qWarning() << warning.arg(time.secsTo(targetTime) / 60).arg(time.secsTo(targetTime) % 60); + QString warning = tr( + "You've exceeded the Nexus API rate limit and requests are now being throttled. " + "Your next batch of requests will be available in approximately %1 minutes and %2 seconds.") + .arg(time.secsTo(targetTime) / 60) + .arg(time.secsTo(targetTime) % 60); + + log::warn("{}", warning); return; } @@ -747,8 +752,8 @@ void NexusInterface::nextRequest() url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") .arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires); } else { - qWarning() << tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " - "or the download link was generated by a different account than the one stored in Mod Organizer."); + log::warn("{}", tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " + "or the download link was generated by a different account than the one stored in Mod Organizer.")); return; } } break; @@ -828,16 +833,16 @@ void NexusInterface::requestFinished(std::list::iterator iter) m_User.limits(parseLimits(reply)); if (!m_User.exhausted()) { - qWarning("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team."); + log::warn("You appear to be making requests to the Nexus API too quickly and are being throttled. Please inform the MO2 team."); } else { - qWarning("All API requests have been consumed and are now being denied."); + log::warn("All API requests have been consumed and are now being denied."); } emit requestsChanged(getAPIStats(), m_User); - qWarning("Error: %s", reply->errorString().toUtf8().constData()); + log::warn("Error: {}", reply->errorString()); } else { - qWarning("request failed: %s", reply->errorString().toUtf8().constData()); + log::warn("request failed: {}", reply->errorString()); } emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), reply->errorString()); } else { @@ -940,7 +945,7 @@ void NexusInterface::requestError(QNetworkReply::NetworkError) { QNetworkReply *reply = qobject_cast(sender()); if (reply == nullptr) { - qWarning("invalid sender type"); + log::warn("invalid sender type"); return; } @@ -955,7 +960,7 @@ void NexusInterface::requestTimeout() { QTimer *timer = qobject_cast(sender()); if (timer == nullptr) { - qWarning("invalid sender type"); + log::warn("invalid sender type"); return; } for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c413e156..9f40894e 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -612,7 +612,7 @@ void NXMAccessManager::clearCookies() if (jar != nullptr) { jar->clear(); } else { - qWarning("failed to clear cookies, invalid cookie jar"); + log::warn("failed to clear cookies, invalid cookie jar"); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 400f5391..dbff1a2a 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -200,26 +200,26 @@ bool checkService() try { serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); if (!serviceManagerHandle) { - qWarning("failed to open service manager (query status) (error %d)", GetLastError()); + log::warn("failed to open service manager (query status) (error {})", GetLastError()); throw 1; } serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); if (!serviceHandle) { - qWarning("failed to open EventLog service (query status) (error %d)", GetLastError()); + log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); throw 2; } if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - qWarning("failed to get size of service config (error %d)", GetLastError()); + log::warn("failed to get size of service config (error {})", GetLastError()); throw 3; } DWORD serviceConfigSize = bytesNeeded; serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { - qWarning("failed to query service config (error %d)", GetLastError()); + log::warn("failed to query service config (error {})", GetLastError()); throw 4; } @@ -230,14 +230,14 @@ bool checkService() if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - qWarning("failed to get size of service status (error %d)", GetLastError()); + log::warn("failed to get size of service status (error {})", GetLastError()); throw 5; } DWORD serviceStatusSize = bytesNeeded; serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { - qWarning("failed to query service status (error %d)", GetLastError()); + log::warn("failed to query service status (error {})", GetLastError()); throw 6; } @@ -402,8 +402,9 @@ void OrganizerCore::storeSettings() if (result == QSettings::NoError) { QString errMsg = commitSettings(iniFile); if (!errMsg.isEmpty()) { - qWarning("settings file not writable, may be locked by another " - "application, trying direct write"); + log::warn( + "settings file not writable, may be locked by another " + "application, trying direct write"); writeTarget = iniFile; result = storeSettings(iniFile); } @@ -1381,9 +1382,9 @@ bool OrganizerCore::previewFileWithAlternatives( // sanity check, this shouldn't happen unless the caller passed an // incorrect id - qWarning().nospace() - << "selected preview origin " << selectedOrigin << " not found in " - << "list of alternatives"; + log::warn( + "selected preview origin {} not found in list of alternatives", + selectedOrigin); } for (int id : origins) { @@ -1798,8 +1799,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, currentDirectory = exe.workingDirectory(); } } catch (const std::runtime_error &) { - qWarning("\"%s\" not set up as executable", - qUtf8Printable(executable)); + log::warn("\"{}\" not set up as executable", executable); binary = QFileInfo(executable); } } @@ -1881,7 +1881,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL // Wait for a an event on the handle, a key press, mouse click or timeout res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON); if (res == WAIT_FAILED) { - qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError(); + log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError()); break; } @@ -1897,7 +1897,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL if (res == WAIT_OBJECT_0) { // process we were waiting on has completed if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode)) - qWarning() << "Failed getting exit code of complete process :" << GetLastError(); + log::warn("Failed getting exit code of complete process: {}", GetLastError()); CloseHandle(handle); handle = INVALID_HANDLE_VALUE; originalHandle = false; @@ -1962,7 +1962,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde DWORD pids[querySize]; size_t found = querySize; if (!::GetVFSProcessList(&found, pids)) { - qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!"; + log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!"); return INVALID_HANDLE_VALUE; } @@ -1974,7 +1974,7 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); if (handle == INVALID_HANDLE_VALUE) { - qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); + log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], GetLastError()); continue; } @@ -2118,7 +2118,7 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, dir.entryList(QStringList() << "*.esm", QDir::Files)) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esm)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esm)); + log::warn("failed to activate {}", esm); continue; } @@ -2134,7 +2134,7 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, dir.entryList(QStringList() << "*.esl", QDir::Files)) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esl)); + log::warn("failed to activate {}", esl); continue; } @@ -2150,7 +2150,7 @@ void OrganizerCore::updateModsActiveState(const QList &modIndices, for (const QString &esp : esps) { const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp)); if (file.get() == nullptr) { - qWarning("failed to activate %s", qUtf8Printable(esp)); + log::warn("failed to activate {}", esp); continue; } @@ -2558,7 +2558,7 @@ std::vector OrganizerCore::activeProblems() const // of a "log spam". But since this is a sevre error which will most likely make the // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it // easier for the user to notice the warning. - qWarning("hook.dll found in game folder: %s", qUtf8Printable(hookdll)); + log::warn("hook.dll found in game folder: {}", hookdll); problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND); } return problems; @@ -2604,7 +2604,7 @@ void OrganizerCore::startGuidedFix(unsigned int) const bool OrganizerCore::saveCurrentLists() { if (m_DirectoryUpdate) { - qWarning("not saving lists during directory update"); + log::warn("not saving lists during directory update"); return false; } @@ -2698,7 +2698,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, result.reserve(result.size() + saveMap.size()); result.insert(result.end(), saveMap.begin(), saveMap.end()); } else { - qWarning("local save games not supported by this game plugin"); + log::warn("local save games not supported by this game plugin"); } } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 2126c5ef..d47fa2c6 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -69,7 +69,7 @@ bool PluginContainer::verifyPlugin(IPlugin *plugin) if (plugin == nullptr) { return false; } else if (!plugin->init(new OrganizerProxy(m_Organizer, this, plugin->name()))) { - qWarning("plugin failed to initialize"); + log::warn("plugin failed to initialize"); return false; } return true; @@ -167,9 +167,10 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); } else { - qWarning("plugin \"%s\" failed to load. If this plugin is for an older version of MO " - "you have to update it or delete it if no update exists.", - qUtf8Printable(pluginName)); + log::warn( + "plugin \"{}\" failed to load. If this plugin is for an older version of MO " + "you have to update it or delete it if no update exists.", + pluginName); } } } @@ -298,7 +299,7 @@ void PluginContainer::loadPlugins() m_PluginLoaders.push_back(pluginLoader.release()); } else { m_FailedPlugins.push_back(pluginName); - qWarning("plugin \"%s\" failed to load (may be outdated)", qUtf8Printable(pluginName)); + log::warn("plugin \"{}\" failed to load (may be outdated)", pluginName); } } } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 2edb92f5..2fb743d0 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -417,7 +417,7 @@ void PluginList::addInformation(const QString &name, const QString &message) if (iter != m_ESPsByName.end()) { m_AdditionalInfo[name.toLower()].m_Messages.append(message); } else { - qWarning("failed to associate message for \"%s\"", qUtf8Printable(name)); + log::warn("failed to associate message for \"{}\"", name); } } @@ -694,7 +694,7 @@ void PluginList::setState(const QString &name, PluginStates state) { m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) || m_ESPs[iter->second].m_ForceEnabled; } else { - qWarning("Plugin not found: %s", qUtf8Printable(name)); + log::warn("Plugin not found: {}", name); } } diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 1e8e800f..da09935b 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -96,7 +96,7 @@ void ProblemsDialog::startFix() { QObject *fixButton = QObject::sender(); if (fixButton == NULL) { - qWarning("no button"); + log::warn("no button"); return; } IPluginDiagnose *plugin = reinterpret_cast(fixButton ->property("fix").value()); diff --git a/src/profile.cpp b/src/profile.cpp index 01906903..d4778305 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -125,7 +125,7 @@ Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) findProfileSettings(); if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qUtf8Printable(directory.path())); + log::warn("missing modlist.txt in {}", directory.path()); touchFile(m_Directory.filePath("modlist.txt")); } @@ -307,7 +307,7 @@ void Profile::renameModInAllProfiles(const QString& oldName, const QString& newN if (modList.exists()) renameModInList(modList, oldName, newName); else - qWarning("Profile has no modlist.txt : %s", qUtf8Printable(profileIter.filePath())); + log::warn("Profile has no modlist.txt: {}", profileIter.filePath()); } } @@ -328,7 +328,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr if (line.length() == 0) { // ignore empty lines - qWarning("mod list contained invalid data: empty line"); + log::warn("mod list contained invalid data: empty line"); continue; } @@ -343,7 +343,7 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr if (modName.isEmpty()) { // file broken? - qWarning("mod list contained invalid data: missing mod name"); + log::warn("mod list contained invalid data: missing mod name"); continue; } @@ -424,8 +424,9 @@ void Profile::refreshModStatus() m_ModStatus[modIndex].m_Priority = index++; } } else { - qWarning("no mod state for \"%s\" (profile \"%s\")", - qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); + log::warn( + "no mod state for \"{}\" (profile \"{}\")", + modName, m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; } @@ -495,8 +496,10 @@ void Profile::dumpModStatus() const { for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { ModInfo::Ptr info = ModInfo::getByIndex(i); - qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, - m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + log::warn( + "{}: {} - {} ({})", + i, info->name(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); } } @@ -803,7 +806,7 @@ bool Profile::localSettingsEnabled() const QStringList missingFiles; for (QString file : m_GamePlugin->iniFiles()) { if (!QFile::exists(m_Directory.filePath(file))) { - qWarning("missing %s in %s", qUtf8Printable(file), qUtf8Printable(m_Directory.path())); + log::warn("missing {} in {}", file, m_Directory.path()); missingFiles << file; } } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index f9ea655f..d7863fc8 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -214,9 +214,9 @@ void ProfilesDialog::on_removeProfileButton_clicked() delete item; } if (!shellDelete(QStringList(profilePath))) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qUtf8Printable(profilePath), ::GetLastError()); + log::warn("Failed to shell-delete \"{}\" (errorcode {}), trying regular delete", profilePath, ::GetLastError()); if (!removeDir(profilePath)) { - qWarning("regular delete failed too"); + log::warn("regular delete failed too"); } } } diff --git a/src/settings.cpp b/src/settings.cpp index e622d632..92ae2251 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -165,8 +165,9 @@ void Settings::registerPlugin(IPlugin *plugin) for (const PluginSetting &setting : plugin->settings()) { QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); if (!temp.convert(setting.defaultValue.type())) { - qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", - qUtf8Printable(temp.toString()), qUtf8Printable(setting.key), qUtf8Printable(plugin->name())); + log::warn( + "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", + temp.toString(), setting.key, plugin->name()); temp = setting.defaultValue; } m_PluginSettings[plugin->name()][setting.key] = temp; -- cgit v1.3.1 From e071dfdfaa369a475a2d93df623c1696feee56ba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 02:47:13 -0400 Subject: changed qCritical() to log::error() removed now unused vlog() --- src/browserdialog.cpp | 12 ++++---- src/categories.cpp | 10 +++---- src/downloadlist.cpp | 5 ++-- src/downloadmanager.cpp | 10 +++++-- src/envmodule.cpp | 66 +++++++++++++++++------------------------- src/envsecurity.cpp | 58 +++++++++++++------------------------ src/envshortcut.cpp | 56 +++++++++++++++++------------------ src/envshortcut.h | 9 ------ src/envwindows.cpp | 19 ++++++------ src/executableslist.cpp | 10 +++---- src/filerenamer.cpp | 2 +- src/filterwidget.cpp | 5 +++- src/forcedloaddialogwidget.cpp | 9 +++--- src/installationmanager.cpp | 7 ++--- src/loglist.cpp | 18 ------------ src/mainwindow.cpp | 34 +++++++++++----------- src/moapplication.cpp | 10 ++++--- src/modinfo.cpp | 5 +--- src/modinfodialog.cpp | 12 ++++---- src/modinfodialogconflicts.cpp | 6 ++-- src/modinfodialogfiletree.cpp | 9 +++--- src/modinfodialogimages.cpp | 9 +++--- src/modinforegular.cpp | 20 ++++++------- src/modlist.cpp | 12 ++++---- src/modlistsortproxy.cpp | 2 +- src/nexusinterface.cpp | 28 +++++++++--------- src/organizercore.cpp | 22 +++++++------- src/overwriteinfodialog.cpp | 6 ++-- src/persistentcookiejar.cpp | 8 +++-- src/plugincontainer.cpp | 5 ++-- src/pluginlist.cpp | 10 +++---- src/profile.cpp | 4 +-- src/settings.cpp | 14 ++------- src/settingsdialog.cpp | 1 - src/shared/directoryentry.cpp | 23 ++++++++------- src/shared/error_report.h | 2 -- src/syncoverwritedialog.cpp | 3 +- src/texteditor.cpp | 7 +++-- src/transfersavesdialog.cpp | 13 ++++----- 39 files changed, 251 insertions(+), 310 deletions(-) (limited to 'src') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index e186ad63..1fde7f15 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -24,9 +24,10 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "report.h" #include "persistentcookiejar.h" +#include "settings.h" #include -#include "settings.h" +#include #include #include @@ -38,6 +39,7 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; BrowserDialog::BrowserDialog(QWidget *parent) @@ -192,12 +194,12 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply) try { QWebEnginePage *page = qobject_cast(sender()); if (page == nullptr) { - qCritical("sender not a page"); + log::error("sender not a page"); return; } BrowserView *view = qobject_cast(page->view()); if (view == nullptr) { - qCritical("no view?"); + log::error("no view?"); return; } @@ -206,14 +208,14 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply) if (isVisible()) { MessageDialog::showMessage(tr("failed to start download"), this); } - qCritical("exception downloading unsupported content: %s", e.what()); + log::error("exception downloading unsupported content: {}", e.what()); } } void BrowserDialog::downloadRequested(const QNetworkRequest &request) { - qCritical("download request %s ignored", request.url().toString().toUtf8().constData()); + log::error("download request {} ignored", request.url().toString()); } diff --git a/src/categories.cpp b/src/categories.cpp index 8f9d3ad8..7acf6ff5 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -62,8 +62,9 @@ void CategoryFactory::loadCategories() ++lineNum; QList cells = line.split('|'); if (cells.count() != 4) { - qCritical("invalid category line %d: %s (%d cells)", - lineNum, line.constData(), cells.count()); + log::error( + "invalid category line {}: {} ({} cells)", + lineNum, line.constData(), cells.count()); } else { std::vector nexusIDs; if (cells[2].length() > 0) { @@ -73,7 +74,7 @@ void CategoryFactory::loadCategories() bool ok = false; int temp = iter->toInt(&ok); if (!ok) { - qCritical("invalid category id %s", iter->constData()); + log::error("invalid category id {}", iter->constData()); } nexusIDs.push_back(temp); } @@ -83,8 +84,7 @@ void CategoryFactory::loadCategories() int id = cells[0].toInt(&cell0Ok); int parentID = cells[3].trimmed().toInt(&cell3Ok); if (!cell0Ok || !cell3Ok) { - qCritical("invalid category line %d: %s", - lineNum, line.constData()); + log::error("invalid category line {}: {}", lineNum, line.constData()); } addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); } diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 5e698e0e..36bc2b7f 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -19,12 +19,13 @@ along with Mod Organizer. If not, see . #include "downloadlist.h" #include "downloadmanager.h" +#include #include #include #include - #include +using namespace MOBase; DownloadList::DownloadList(DownloadManager *manager, QObject *parent) : QAbstractTableModel(parent), m_Manager(manager) @@ -192,7 +193,7 @@ void DownloadList::update(int row) else if (row < this->rowCount()) emit dataChanged(this->index(row, 0, QModelIndex()), this->index(row, this->columnCount(QModelIndex())-1, QModelIndex())); else - qCritical("invalid row %d in download list, update failed", row); + log::error("invalid row {} in download list, update failed", row); } QString DownloadList::sizeFormat(quint64 size) const diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index e3ceb261..348b2108 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -660,7 +660,7 @@ void DownloadManager::removeFile(int index, bool deleteFile) if ((download->m_State == STATE_STARTED) || (download->m_State == STATE_DOWNLOADING)) { // shouldn't have been possible - qCritical("tried to remove active download"); + log::error("tried to remove active download"); endDisableDirWatcher(); return; } @@ -798,7 +798,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) emit update(-1); endDisableDirWatcher(); } catch (const std::exception &e) { - qCritical("failed to remove download: %s", e.what()); + log::error("failed to remove download: {}", e.what()); } refreshList(); } @@ -2069,7 +2069,11 @@ void DownloadManager::writeData(DownloadInfo *info) if (ret < info->m_Reply->size()) { QString fileName = info->m_FileName; // m_FileName may be destroyed after setState setState(info, DownloadState::STATE_CANCELED); - qCritical(QString("Unable to write download \"%2\" to drive (return %1)").arg(ret).arg(info->m_FileName).toLocal8Bit()); + + log::error( + "Unable to write download \"{}\" to drive (return {})", + info->m_FileName, ret); + reportError(tr("Unable to write download to drive (return %1).\n" "Check the drive's available storage.\n\n" "Canceling download \"%2\"...").arg(ret).arg(fileName)); diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 1717da15..aae4e0b1 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -1,6 +1,7 @@ #include "envmodule.h" #include "env.h" #include +#include namespace env { @@ -114,9 +115,9 @@ Module::FileInfo Module::getFileInfo() const return {}; } - qCritical().nospace().noquote() - << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); + log::error( + "GetFileVersionInfoSizeW() failed on '{}', {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -127,9 +128,9 @@ Module::FileInfo Module::getFileInfo() const if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { const auto e = GetLastError(); - qCritical().nospace().noquote() - << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessageQ(e); + log::error( + "GetFileVersionInfoW() failed on '{}', {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -161,9 +162,9 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const // signature is always 0xfeef04bd if (fi->dwSignature != 0xfeef04bd) { - qCritical().nospace().noquote() - << "bad file info signature 0x" << hex << fi->dwSignature << " for " - << "'" << m_path << "'"; + log::error( + "bad file info signature {:#x} for '{}'", + fi->dwSignature, m_path); return {}; } @@ -187,9 +188,7 @@ QString Module::getFileDescription(std::byte* buffer) const buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); if (!ret || !valuePointer || valueSize == 0) { - qCritical().nospace().noquote() - << "VerQueryValueW() for translations failed on '" << m_path << "'"; - + log::error("VerQueryValueW() for translations failed on '{}'", m_path); return {}; } @@ -254,9 +253,9 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const if (h.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessageQ(e); + log::error( + "can't open file '{}' for timestamp, {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -264,9 +263,10 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const // getting the file time if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { const auto e = GetLastError(); - qCritical().nospace().noquote() - << "can't get file time for '" << m_path << "', " - << formatSystemMessageQ(e); + + log::error( + "can't get file time for '{}', {}", + m_path, formatSystemMessageQ(e)); return {}; } @@ -281,11 +281,9 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const SYSTEMTIME utc = {}; if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical().nospace().noquote() - << "FileTimeToSystemTime() failed on timestamp " - << "high=0x" << hex << ft.dwHighDateTime << " " - << "low=0x" << hex << ft.dwLowDateTime << " for " - << "'" << m_path << "'"; + log::error( + "FileTimeToSystemTime() failed on timestamp high={:#x} low={:#x} for '{}'", + ft.dwHighDateTime, ft.dwLowDateTime, m_path); return {}; } @@ -307,18 +305,14 @@ QString Module::getMD5() const QFile f(m_path); if (!f.open(QFile::ReadOnly)) { - qCritical().nospace().noquote() - << "failed to open file '" << m_path << "' for md5"; - + log::error("failed to open file '{}' for md5", m_path); return {}; } // hashing QCryptographicHash hash(QCryptographicHash::Md5); if (!hash.addData(&f)) { - qCritical().nospace().noquote() - << "failed to calculate md5 for '" << m_path << "'"; - + log::error("failed to calculate md5 for '{}'", m_path); return {}; } @@ -334,11 +328,7 @@ std::vector getLoadedModules() if (snapshot.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessageQ(e); - + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessageQ(e)); return {}; } @@ -349,10 +339,7 @@ std::vector getLoadedModules() if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); - - qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessageQ(e); - + log::error("Module32First() failed, {}", formatSystemMessageQ(e)); return {}; } @@ -371,8 +358,7 @@ std::vector getLoadedModules() // no more modules is not an error if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessageQ(e); + log::error("Module32Next() failed, {}", formatSystemMessageQ(e)); } break; diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 559ce4ad..015e4000 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -1,6 +1,7 @@ #include "envsecurity.h" #include "env.h" #include +#include #include #include @@ -57,8 +58,7 @@ public: } if (FAILED(ret)) { - qCritical() - << "enumerator->next() failed, " << formatSystemMessageQ(ret); + log::error("enum->next() failed, {}", formatSystemMessageQ(ret)); break; } @@ -82,9 +82,9 @@ private: IID_IWbemLocator, &rawLocator); if (FAILED(ret) || !rawLocator) { - qCritical() - << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessageQ(ret); + log::error( + "CoCreateInstance for WbemLocator failed, {}", + formatSystemMessageQ(ret)); throw failed(); } @@ -102,10 +102,9 @@ private: &rawService); if (FAILED(res) || !rawService) { - qCritical() - << "locator->ConnectServer() failed for namespace " - << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessageQ(res); + log::error( + "locator->ConnectServer() failed for namespace '{}', {}", + ns, formatSystemMessageQ(res)); throw failed(); } @@ -121,9 +120,7 @@ private: if (FAILED(ret)) { - qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); - + log::error("CoSetProxyBlanket() failed, {}", formatSystemMessageQ(ret)); throw failed(); } } @@ -142,10 +139,7 @@ private: if (FAILED(ret) || !rawEnumerator) { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - + log::error("query '{}' failed, {}", query, formatSystemMessageQ(ret)); return {}; } @@ -256,15 +250,12 @@ std::vector getSecurityProductsFromWMI() // display name auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() - << "failed to get displayName, " - << formatSystemMessageQ(ret); - + log::error("failed to get displayName, {}", formatSystemMessageQ(ret)); return; } if (prop.vt != VT_BSTR) { - qCritical() << "displayName is a " << prop.vt << ", not a bstr"; + log::error("displayName is a {}, not a bstr", prop.vt); return; } @@ -274,15 +265,12 @@ std::vector getSecurityProductsFromWMI() // product state ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() - << "failed to get productState, " - << formatSystemMessageQ(ret); - + log::error("failed to get productState, {}", formatSystemMessageQ(ret)); return; } if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; + log::error("productState is a {}, is not a VT_UI4", prop.vt); return; } @@ -298,15 +286,12 @@ std::vector getSecurityProductsFromWMI() // guid ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() - << "failed to get instanceGuid, " - << formatSystemMessageQ(ret); - + log::error("failed to get instanceGuid, {}", formatSystemMessageQ(ret)); return; } if (prop.vt != VT_BSTR) { - qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + log::error("instanceGuid is a {}, is not a bstr", prop.vt); return; } @@ -362,9 +347,9 @@ std::optional getWindowsFirewall() __uuidof(INetFwPolicy2), &rawPolicy); if (FAILED(hr) || !rawPolicy) { - qCritical() - << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessageQ(hr); + log::error( + "CoCreateInstance for NetFwPolicy2 failed, {}", + formatSystemMessageQ(hr)); return {}; } @@ -378,10 +363,7 @@ std::optional getWindowsFirewall() hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); if (FAILED(hr)) { - qCritical() - << "get_FirewallEnabled failed, " - << formatSystemMessageQ(hr); - + log::error("get_FirewallEnabled failed, {}", formatSystemMessageQ(hr)); return {}; } } diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 30ef4633..1deb9dad 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -3,6 +3,7 @@ #include "executableslist.h" #include "instancemanager.h" #include +#include namespace env { @@ -218,17 +219,24 @@ bool Shortcut::toggle(Locations loc) bool Shortcut::add(Locations loc) { - debug() - << "adding shortcut to " << toString(loc) << ":\n" - << " . name: '" << m_name << "'\n" - << " . target: '" << m_target << "'\n" - << " . arguments: '" << m_arguments << "'\n" - << " . description: '" << m_description << "'\n" - << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" - << " . working directory: '" << m_workingDirectory << "'"; + log::debug( + "adding shortcut to {}:\n" + " . name: '{}'\n" + " . target: '{}'\n" + " . arguments: '{}'\n" + " . description: '{}'\n" + " . icon: '{}' @ {}\n" + " . working directory: '{}'", + toString(loc), + m_name, + m_target, + m_arguments, + m_description, + m_icon, m_iconIndex, + m_workingDirectory); if (m_target.isEmpty()) { - critical() << "target is empty"; + log::error("shortcut: target is empty"); return false; } @@ -237,7 +245,7 @@ bool Shortcut::add(Locations loc) return false; } - debug() << "shorcut file will be saved at '" << path << "'"; + log::debug("shorcut file will be saved at '{}'", path); try { @@ -255,7 +263,7 @@ bool Shortcut::add(Locations loc) } catch(ShellLinkException& e) { - critical() << e.what() << "\nshortcut file was not saved"; + log::error("{}\nshortcut file was not saved", e.what()); } return false; @@ -263,26 +271,26 @@ bool Shortcut::add(Locations loc) bool Shortcut::remove(Locations loc) { - debug() << "removing shortcut for '" << m_name << "' from " << toString(loc); + log::debug("removing shortcut for '{}' from {}", m_name, toString(loc)); const auto path = shortcutPath(loc); if (path.isEmpty()) { return false; } - debug() << "path to shortcut file is '" << path << "'"; + log::debug("path to shortcut file is '{}'", path); if (!QFile::exists(path)) { - critical() << "can't remove '" << path << "', file not found"; + log::error("can't remove shortcut '{}', file not found", path); return false; } if (!MOBase::shellDelete({path})) { const auto e = ::GetLastError(); - critical() - << "failed to remove '" << path << "', " - << formatSystemMessageQ(e); + log::error( + "failed to remove shortcut '{}', {}", + path, formatSystemMessageQ(e)); return false; } @@ -323,7 +331,7 @@ QString Shortcut::shortcutDirectory(Locations loc) const case None: default: - critical() << "bad location " << loc; + log::error("shortcut: bad location {}", loc); break; } } @@ -337,23 +345,13 @@ QString Shortcut::shortcutDirectory(Locations loc) const QString Shortcut::shortcutFilename() const { if (m_name.isEmpty()) { - critical() << "name is empty"; + log::error("shortcut name is empty"); return {}; } return m_name + ".lnk"; } -QDebug Shortcut::debug() const -{ - return qDebug().noquote().nospace() << "system shortcut: "; -} - -QDebug Shortcut::critical() const -{ - return qCritical().noquote().nospace() << "system shortcut: "; -} - QString toString(Shortcut::Locations loc) { diff --git a/src/envshortcut.h b/src/envshortcut.h index 904b3ab7..82eea191 100644 --- a/src/envshortcut.h +++ b/src/envshortcut.h @@ -84,15 +84,6 @@ private: int m_iconIndex; QString m_workingDirectory; - // returns a qCritical() logger with a prefix already logged - // - QDebug critical() const; - - // returns a qDebug() logger with a prefix already logged - // - QDebug debug() const; - - // returns the path where the shortcut file should be saved // QString shortcutPath(Locations loc) const; diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 4fbd788a..8a98036a 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -1,6 +1,7 @@ #include "envwindows.h" #include "env.h" #include +#include namespace env { @@ -13,7 +14,7 @@ WindowsInfo::WindowsInfo() LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; + log::error("failed to load ntdll.dll while getting version"); return; } else { m_reported = getReportedVersion(ntdll.get()); @@ -122,7 +123,7 @@ WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const GetProcAddress(ntdll, "RtlGetVersion")); if (!RtlGetVersion) { - qCritical() << "RtlGetVersion() not found in ntdll.dll"; + log::error("RtlGetVersion() not found in ntdll.dll"); return {}; } @@ -149,7 +150,7 @@ WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); if (!RtlGetNtVersionNumbers) { - qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + log::error("RtlGetNtVersionNumbers not found in ntdll.dll"); return {}; } @@ -207,9 +208,9 @@ std::optional WindowsInfo::getElevated() const if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { const auto e = GetLastError(); - qCritical() - << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessageQ(e); + log::error( + "while trying to check if process is elevated, " + "OpenProcessToken() failed: {}", formatSystemMessageQ(e)); return {}; } @@ -223,9 +224,9 @@ std::optional WindowsInfo::getElevated() const if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { const auto e = GetLastError(); - qCritical() - << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessageQ(e); + log::error( + "while trying to check if process is elevated, " + "GetTokenInformation() failed: {}", formatSystemMessageQ(e)); return {}; } diff --git a/src/executableslist.cpp b/src/executableslist.cpp index fbb96bd4..2408e8f3 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -243,9 +243,9 @@ void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags) 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"; + log::error( + "executable '{}' was in the way but could not be renamed", + exe.title()); return; } @@ -289,9 +289,7 @@ std::optional ExecutablesList::makeNonConflictingTitle( title = prefix + QString(" (%1)").arg(i); } - qCritical().nospace() - << "ran out of executable titles for prefix '" << prefix << "'"; - + log::error("ran out of executable titles for prefix '{}'", prefix); return {}; } diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index b516c902..8835f52f 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -10,7 +10,7 @@ FileRenamer::FileRenamer(QWidget* parent, QFlags flags) { // sanity check for flags if ((m_flags & (HIDE|UNHIDE)) == 0) { - qCritical("renameFile() missing hide flag"); + log::error("renameFile() missing hide flag"); // doesn't really matter, it's just for text m_flags = HIDE; } diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 44cbb274..0638add3 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -1,5 +1,8 @@ #include "filterwidget.h" #include "eventfilter.h" +#include + +using namespace MOBase; FilterWidgetProxyModel::FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent) : QSortFilterProxyModel(parent), m_filter(fw) @@ -80,7 +83,7 @@ QModelIndex FilterWidget::map(const QModelIndex& index) if (m_proxy) { return m_proxy->mapToSource(index); } else { - qCritical() << "FilterWidget::map() called, but proxy isn't set up"; + log::error("FilterWidget::map() called, but proxy isn't set up"); return index; } } diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp index b92838c3..b84f785f 100644 --- a/src/forcedloaddialogwidget.cpp +++ b/src/forcedloaddialogwidget.cpp @@ -1,9 +1,8 @@ #include "forcedloaddialogwidget.h" #include "ui_forcedloaddialogwidget.h" - -#include - #include "executableinfo.h" +#include +#include using namespace MOBase; @@ -85,7 +84,7 @@ void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked() if (fileInfo.exists()) { ui->libraryPathEdit->setText(filePath); } else { - qCritical("%ls does not exist", filePath.toStdWString().c_str()); + log::error("{} does not exist", filePath); } } } @@ -102,7 +101,7 @@ void ForcedLoadDialogWidget::on_processBrowseButton_clicked() if (fileInfo.exists()) { ui->processEdit->setText(fileName); } else { - qCritical("%ls does not exist", fileInfo.filePath().toStdWString().c_str()); + log::error("{} does not exist", fileInfo.filePath()); } } } diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 0e50de52..fd971f47 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -263,7 +263,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '/'); } if (targetFile == nullptr) { - qCritical() << "Failed to find backslash in " << data[i]->getFileName(); + log::error("Failed to find backslash in {}", data[i]->getFileName()); continue; } else { // skip the slash @@ -527,7 +527,7 @@ bool InstallationManager::testOverwrite(GuessedValue &modName, bool *me settingsFile.write(originalSettings); settingsFile.close(); } else { - qCritical("failed to restore original settings: %s", qUtf8Printable(metaFilename)); + log::error("failed to restore original settings: {}", metaFilename); } return true; } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { @@ -856,8 +856,7 @@ bool InstallationManager::install(const QString &fileName, } } } catch (const IncompatibilityException &e) { - qCritical("plugin \"%s\" incompatible: %s", - qUtf8Printable(installer->name()), e.what()); + log::error("plugin \"{}\" incompatible: {}", installer->name(), e.what()); } // act upon the installation result. at this point the files have already been diff --git a/src/loglist.cpp b/src/loglist.cpp index 207f412b..c34ac76e 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -196,21 +196,3 @@ void LogList::copyToClipboard() QApplication::clipboard()->setText(QString::fromStdString(s)); } - - -void vlog(const char *format, ...) -{ - va_list argList; - va_start(argList, format); - - static const int BUFFERSIZE = 1000; - - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; - - vsnprintf(buffer, BUFFERSIZE, format, argList); - - qCritical("%s", buffer); - - va_end(argList); -} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 70ace8f1..ad87ba03 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1212,14 +1212,14 @@ void MainWindow::createHelpMenu() QFile file(dirIter.filePath()); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; + log::error("Failed to open {}", fileName); continue; } QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//TL")) { QStringList params = firstLine.mid(4).trimmed().split('#'); if (params.size() != 2) { - qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters"; + log::error("invalid header line for tutorial {}, expected 2 parameters", fileName); continue; } QAction *tutAction = new QAction(params.at(0), tutorialMenu); @@ -1323,7 +1323,7 @@ void MainWindow::hookUpWindowTutorials() QString fileName = dirIter.fileName(); QFile file(dirIter.filePath()); if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; + log::error("Failed to open {}", fileName); continue; } QString firstLine = QString::fromUtf8(file.readLine()); @@ -1369,7 +1369,7 @@ void MainWindow::showEvent(QShowEvent *event) TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); } } else { - qCritical() << firstStepsTutorial << " missing"; + log::error("{} missing", firstStepsTutorial); QPoint pos = ui->toolBar->mapToGlobal(QPoint()); pos.rx() += ui->toolBar->width() / 2; pos.ry() += ui->toolBar->height(); @@ -1636,7 +1636,7 @@ void MainWindow::startExeAction() QAction *action = qobject_cast(sender()); if (action == nullptr) { - qCritical("not an action?"); + log::error("not an action?"); return; } @@ -3415,7 +3415,7 @@ void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tab { unsigned int index = ModInfo::getIndex(modName); if (index == UINT_MAX) { - qCritical("failed to resolve mod name %s", qUtf8Printable(modName)); + log::error("failed to resolve mod name {}", modName); return; } @@ -3500,7 +3500,7 @@ void MainWindow::visitOnNexus_clicked() if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); } else { - qCritical() << "mod '" << info->name() << "' has no nexus id"; + log::error("mod '{}' has no nexus id", info->name()); } } } @@ -4038,7 +4038,7 @@ void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) MessageDialog::showMessage(tr("Move successful."), this); } else { - qCritical("Move operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + log::error("Move operation failed: {}", windowsErrorString(::GetLastError())); } m_OrganizerCore.refreshModList(); @@ -4067,7 +4067,7 @@ void MainWindow::clearOverwrite() updateProblemsButton(); m_OrganizerCore.refreshModList(); } else { - qCritical("Delete operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + log::error("Delete operation failed: {}", windowsErrorString(::GetLastError())); } } } @@ -4311,7 +4311,7 @@ void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int refere void MainWindow::addRemoveCategories_MenuHandler() { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } @@ -4352,7 +4352,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { void MainWindow::replaceCategories_MenuHandler() { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } @@ -4547,7 +4547,7 @@ void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, categoryBox->setChecked(categoryID == info->getPrimaryCategory()); action->setDefaultWidget(categoryBox); } catch (const std::exception &e) { - qCritical("failed to create category checkbox: %s", e.what()); + log::error("failed to create category checkbox: {}", e.what()); } action->setData(categoryID); @@ -4559,7 +4559,7 @@ void MainWindow::addPrimaryCategoryCandidates() { QMenu *menu = qobject_cast(sender()); if (menu == nullptr) { - qCritical("not a menu?"); + log::error("not a menu?"); return; } menu->clear(); @@ -6067,7 +6067,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa toggleMO2EndorseState(); if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) { - qCritical("failed to disconnect endorsement slot"); + log::error("failed to disconnect endorsement slot"); } } @@ -6527,11 +6527,11 @@ void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) secAttributes.lpSecurityDescriptor = nullptr; if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - qCritical("failed to create stdout reroute"); + log::error("failed to create stdout reroute"); } if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - qCritical("failed to correctly set up the stdout reroute"); + log::error("failed to correctly set up the stdout reroute"); *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; } } @@ -6965,7 +6965,7 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m success = shellCopy(file.absoluteFilePath(), target, true, this); } if (!success) { - qCritical("file operation failed: %s", qUtf8Printable(windowsErrorString(::GetLastError()))); + log::error("file operation failed: {}", windowsErrorString(::GetLastError())); } } diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 3d55b28d..370a23b5 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -115,13 +115,15 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) try { return QApplication::notify(receiver, event); } catch (const std::exception &e) { - qCritical("uncaught exception in handler (object %s, eventtype %d): %s", - receiver->objectName().toUtf8().constData(), event->type(), e.what()); + log::error( + "uncaught exception in handler (object {}, eventtype {}): {}", + receiver->objectName(), event->type(), e.what()); reportError(tr("an error occurred: %1").arg(e.what())); return false; } catch (...) { - qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", - receiver->objectName().toUtf8().constData(), event->type()); + log::error( + "uncaught non-std exception in handler (object {}, eventtype {})", + receiver->objectName(), event->type()); reportError(tr("an error occurred")); return false; } diff --git a/src/modinfo.cpp b/src/modinfo.cpp index ca6e8046..5a05e7ca 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -530,10 +530,7 @@ QUrl ModInfo::parseCustomURL() const const auto url = QUrl::fromUserInput(getCustomURL()); if (!url.isValid()) { - qCritical() - << "mod '" << name() << "' has an invalid custom url " - << "'" << getCustomURL() << "'"; - + log::error("mod '{}' has an invalid custom url '{}'", name(), getCustomURL()); return {}; } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 47ac84be..a7a6b0d7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -176,7 +176,7 @@ void ModInfoDialog::createTabs() // check for tabs in the ui not having a corresponding tab in the list int count = ui->tabWidget->count(); if (count < 0 || count > static_cast(m_tabs.size())) { - qCritical() << "mod info dialog has more tabs than expected"; + log::error("mod info dialog has more tabs than expected"); count = static_cast(m_tabs.size()); } @@ -239,13 +239,13 @@ void ModInfoDialog::setMod(const QString& name) { unsigned int index = ModInfo::getIndex(name); if (index == UINT_MAX) { - qCritical() << "failed to resolve mod name " << name; + log::error("failed to resolve mod name {}", name); return; } auto mod = ModInfo::getByIndex(index); if (!mod) { - qCritical() << "mod by index " << index << " is null"; + log::error("mod by index {} is null", index); return; } @@ -307,7 +307,7 @@ void ModInfoDialog::update(bool firstTime) // changed tabInfo->tab->activated(); } else { - qCritical() << "tab index " << oldTab << " not found"; + log::error("tab index {} not found", oldTab); } } } @@ -400,7 +400,7 @@ void ModInfoDialog::reAddTabs( if (itor == orderedNames.end()) { // this shouldn't happen, it means there's a tab in the UI that's no // in the list - qCritical() << "can't sort tabs, '" << objectName << "' not found"; + log::error("can't sort tabs, '{}' not found", objectName); canSort = false; } } @@ -753,7 +753,7 @@ void ModInfoDialog::onTabMoved() } if (!found) { - qCritical() << "unknown tab at index " << i; + log::error("unknown tab at index {}", i); } } } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 511d48ad..d16d548c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -365,7 +365,7 @@ void for_each_in_selection(QTreeView* tree, F&& f) const auto* model = dynamic_cast(tree->model()); if (!model) { - qCritical() << "tree doesn't have a ConflictListModel"; + log::error("tree doesn't have a ConflictListModel"); return; } @@ -454,7 +454,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) auto* model = dynamic_cast(tree->model()); if (!model) { - qCritical() << "list doesn't have a ConflictListModel"; + log::error("list doesn't have a ConflictListModel"); return; } @@ -633,7 +633,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) const auto* model = dynamic_cast(tree->model()); if (!model) { - qCritical() << "tree doesn't have a ConflictListModel"; + log::error("tree doesn't have a ConflictListModel"); return {}; } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 0b519932..219ddf35 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -5,8 +5,9 @@ #include "filerenamer.h" #include #include +#include -using MOBase::reportError; +using namespace MOBase; namespace shell = MOBase::shell; // if there are more than 50 selected items in the filetree, don't bother @@ -230,19 +231,19 @@ bool FileTreeTab::deleteFileRecursive(const QModelIndex& parent) if (m_fs->isDir(index)) { if (!deleteFileRecursive(index)) { - qCritical() << "failed to delete" << m_fs->fileName(index); + log::error("failed to delete {}", m_fs->fileName(index)); return false; } } else { if (!m_fs->remove(index)) { - qCritical() << "failed to delete", m_fs->fileName(index); + log::error("failed to delete {}", m_fs->fileName(index)); return false; } } } if (!m_fs->remove(parent)) { - qCritical() << "failed to delete" << m_fs->fileName(parent); + log::error("failed to delete {}", m_fs->fileName(parent)); return false; } diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 69866902..10362058 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -2,7 +2,9 @@ #include "ui_modinfodialog.h" #include "settings.h" #include "utility.h" +#include +using namespace MOBase; using namespace ImagesTabHelpers; QSize resizeWithAspectRatio(const QSize& original, const QSize& available) @@ -896,10 +898,9 @@ void File::ensureOriginalLoaded() QImageReader reader(m_path); if (!reader.read(&m_original)) { - qCritical().noquote().nospace() - << "failed to load '" << m_path << "'\n" - << reader.errorString() << " " - << "(error " << static_cast(reader.error()) << ")"; + log::error( + "failed to load '{}'\n{} (error {})", + m_path, reader.errorString(), static_cast(reader.error())); m_failed = true; } diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 448447e1..074fa9e2 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -68,8 +68,7 @@ ModInfoRegular::~ModInfoRegular() try { saveMeta(); } catch (const std::exception &e) { - qCritical("failed to save meta information for \"%s\": %s", - qUtf8Printable(m_Name), e.what()); + log::error("failed to save meta information for \"{}\": {}", m_Name, e.what()); } } @@ -258,14 +257,14 @@ void ModInfoRegular::saveMeta() if (metaFile.status() == QSettings::NoError) { m_MetaInfoChanged = false; } else { - qCritical() - << QString("failed to write %1/meta.ini: error %2") - .arg(absolutePath()).arg(metaFile.status()); + log::error( + "failed to write {}/meta.ini: error {}", + absolutePath(), metaFile.status()); } } else { - qCritical() - << QString("failed to write %1/meta.ini: error %2") - .arg(absolutePath()).arg(metaFile.status()); + log::error( + "failed to write {}/meta.ini: error {}", + absolutePath(), metaFile.status()); } } } @@ -425,14 +424,13 @@ bool ModInfoRegular::setName(const QString &name) return false; } if (!modDir.rename(tempName, name)) { - qCritical("rename to final name failed after successful rename to intermediate name"); + log::error("rename to final name failed after successful rename to intermediate name"); modDir.rename(tempName, m_Name); return false; } } else { if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { - qCritical("failed to rename mod %s (errorcode %d)", - qUtf8Printable(name), ::GetLastError()); + log::error("failed to rename mod {} (errorcode {})", name, ::GetLastError()); return false; } } diff --git a/src/modlist.cpp b/src/modlist.cpp index df25df0d..6ebd0e8b 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -271,7 +271,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const int categoryIdx = categoryFactory.getCategoryIndex(category); return categoryFactory.getCategoryName(categoryIdx); } catch (const std::exception &e) { - qCritical("failed to retrieve category name: %s", e.what()); + log::error("failed to retrieve category name: {}", e.what()); return QString(); } } else { @@ -449,7 +449,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const try { return modInfo->getDescription(); } catch (const std::exception &e) { - qCritical("invalid mod description: %s", e.what()); + log::error("invalid mod description: {}", e.what()); return QString(); } } else if (column == COL_VERSION) { @@ -488,7 +488,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const try { categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; } catch (const std::exception &e) { - qCritical("failed to generate tooltip: %s", e.what()); + log::error("failed to generate tooltip: {}", e.what()); return QString(); } } @@ -636,9 +636,9 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) try { m_ModStateChanged(info->name(), newState); } catch (const std::exception &e) { - qCritical("failed to invoke state changed notification: %s", e.what()); + log::error("failed to invoke state changed notification: {}", e.what()); } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); + log::error("failed to invoke state changed notification: unknown exception"); } } @@ -834,7 +834,7 @@ void ModList::modInfoChanged(ModInfo::Ptr info) emit dataChanged(index(row, 0), index(row, columnCount())); emit postDataChanged(); } else { - qCritical("modInfoChanged not called after modInfoAboutToChange"); + log::error("modInfoChanged not called after modInfoAboutToChange"); } m_ChangeInfo.name = QString(); } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 1127c7d4..d330e0c2 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -196,7 +196,7 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory())); lt = leftCatName < rightCatName; } catch (const std::exception &e) { - qCritical("failed to compare categories: %s", e.what()); + log::error("failed to compare categories: {}", e.what()); } } } diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 008f3c0d..c797aed6 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -41,12 +41,11 @@ using namespace MOShared; void throttledWarning(const APIUserAccount& user) { - qCritical() << - QString( - "You have fewer than %1 requests remaining (%2). Only downloads and " - "login validation are being allowed.") - .arg(APIUserAccount::ThrottleThreshold) - .arg(user.remainingRequests()); + log::error( + "You have fewer than {} requests remaining ({}). Only downloads and " + "login validation are being allowed.", + APIUserAccount::ThrottleThreshold, + user.remainingRequests()); } @@ -344,7 +343,7 @@ QString NexusInterface::getGameURL(QString gameName) const if (game != nullptr) { return "https://www.nexusmods.com/" + game->gameNexusName().toLower(); } else { - qCritical("getGameURL can't find plugin for %s", qUtf8Printable(gameName)); + log::error("getGameURL can't find plugin for {}", gameName); return ""; } } @@ -355,7 +354,7 @@ QString NexusInterface::getOldModsURL(QString gameName) const if (game != nullptr) { return "https://" + game->gameNexusName().toLower() + ".nexusmods.com/mods"; } else { - qCritical("getOldModsURL can't find plugin for %s", qUtf8Printable(gameName)); + log::error("getOldModsURL can't find plugin for {}", gameName); return ""; } } @@ -464,7 +463,7 @@ int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant IPluginGame *game = getGame(gameName); if (game == nullptr) { - qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName)); + log::error("requestUpdates can't find plugin for {}", gameName); return -1; } @@ -521,7 +520,7 @@ int NexusInterface::requestFileInfo(QString gameName, int modID, int fileID, QOb { IPluginGame *gamePlugin = getGame(gameName); if (gamePlugin == nullptr) { - qCritical("requestFileInfo can't find plugin for %s", qUtf8Printable(gameName)); + log::error("requestFileInfo can't find plugin for {}", gameName); return -1; } @@ -687,7 +686,7 @@ void NexusInterface::nextRequest() } else if (getAccessManager()->validateWaiting()) { return; } else { - qCritical() << tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API."); + log::error("{}", tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API.")); } } @@ -949,10 +948,9 @@ void NexusInterface::requestError(QNetworkReply::NetworkError) return; } - qCritical("request (%s) error: %s (%d)", - qUtf8Printable(reply->url().toString()), - qUtf8Printable(reply->errorString()), - reply->error()); + log::error( + "request ({}) error: {} ({})", + reply->url().toString(), reply->errorString(), reply->error()); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dbff1a2a..725371e9 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -224,7 +224,7 @@ bool checkService() } if (serviceConfig->dwStartType == SERVICE_DISABLED) { - qCritical("Windows Event Log service is disabled!"); + log::error("Windows Event Log service is disabled!"); serviceRunning = false; } @@ -242,7 +242,7 @@ bool checkService() } if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { - qCritical("Windows Event Log service is not running"); + log::error("Windows Event Log service is not running"); serviceRunning = false; } } @@ -437,7 +437,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access) hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hProcessSnap == INVALID_HANDLE_VALUE) { lastError = GetLastError(); - qCritical("unable to get snapshot of processes (error %d)", lastError); + log::error("unable to get snapshot of processes (error {})", lastError); return false; } @@ -446,7 +446,7 @@ bool OrganizerCore::testForSteam(bool *found, bool *access) pe32.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hProcessSnap, &pe32)) { lastError = GetLastError(); - qCritical("unable to get first process (error %d)", lastError); + log::error("unable to get first process (error {})", lastError); CloseHandle(hProcessSnap); return false; } @@ -486,7 +486,7 @@ return true; void OrganizerCore::updateExecutablesList(QSettings &settings) { if (m_PluginContainer == nullptr) { - qCritical("can't update executables list now"); + log::error("can't update executables list now"); return; } @@ -657,7 +657,7 @@ void OrganizerCore::downloadRequested(QNetworkReply *reply, QString gameName, in } } catch (const std::exception &e) { MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow()); - qCritical("exception starting download: %s", e.what()); + log::error("exception starting download: {}", e.what()); } } @@ -1552,7 +1552,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, bool steamFound = true; bool steamAccess = true; if (!testForSteam(&steamFound, &steamAccess)) { - qCritical("unable to determine state of Steam"); + log::error("unable to determine state of Steam"); } if (!steamFound) { @@ -1569,9 +1569,9 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, steamFound = true; steamAccess = true; if (!testForSteam(&steamFound, &steamAccess)) { - qCritical("unable to determine state of Steam"); + log::error("unable to determine state of Steam"); } else if (!steamFound) { - qCritical("could not find Steam"); + log::error("could not find Steam"); } } else if (result == QDialogButtonBox::Cancel) { @@ -1592,14 +1592,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, if (result == QDialogButtonBox::Yes) { WCHAR cwd[MAX_PATH]; if (!GetCurrentDirectory(MAX_PATH, cwd)) { - qCritical("unable to get current directory (error %d)", GetLastError()); + log::error("unable to get current directory (error {})", GetLastError()); cwd[0] = L'\0'; } if (!Helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - qCritical("unable to relaunch MO as admin"); + log::error("unable to relaunch MO as admin"); return INVALID_HANDLE_VALUE; } qApp->exit(0); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 5ee8d76c..cc4ae849 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -121,18 +121,18 @@ bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); if (m_FileSystemModel->isDir(childIndex)) { if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); return false; } } else { if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + log::error("failed to delete {}", m_FileSystemModel->fileName(childIndex)); return false; } } } if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + log::error("failed to delete {}", m_FileSystemModel->fileName(index)); return false; } return true; diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 1ed463c6..670bf382 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -1,8 +1,10 @@ #include "persistentcookiejar.h" +#include #include #include #include +using namespace MOBase; PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *parent) : QNetworkCookieJar(parent), m_FileName(fileName) @@ -24,7 +26,7 @@ void PersistentCookieJar::clear() { void PersistentCookieJar::save() { QTemporaryFile file; if (!file.open()) { - qCritical("failed to save cookies: couldn't create temporary file"); + log::error("failed to save cookies: couldn't create temporary file"); return; } QDataStream data(&file); @@ -40,14 +42,14 @@ void PersistentCookieJar::save() { QFile oldCookies(m_FileName); if (oldCookies.exists()) { if (!oldCookies.remove()) { - qCritical("failed to save cookies: failed to remove %s", qUtf8Printable(m_FileName)); + log::error("failed to save cookies: failed to remove {}", m_FileName); return; } } // if it doesn't exists that's fine } if (!file.copy(m_FileName)) { - qCritical("failed to save cookies: failed to write %s", qUtf8Printable(m_FileName)); + log::error("failed to save cookies: failed to write {}", m_FileName); } } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index d47fa2c6..36daec52 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -291,8 +291,9 @@ void PluginContainer::loadPlugins() std::unique_ptr pluginLoader(new QPluginLoader(pluginName, this)); if (pluginLoader->instance() == nullptr) { m_FailedPlugins.push_back(pluginName); - qCritical("failed to load plugin %s: %s", - qUtf8Printable(pluginName), qUtf8Printable(pluginLoader->errorString())); + log::error( + "failed to load plugin {}: {}", + pluginName, pluginLoader->errorString()); } else { if (registerPlugin(pluginLoader->instance(), pluginName)) { qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 2fb743d0..e436d7f6 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -309,7 +309,7 @@ int PluginList::findPluginByPriority(int priority) return i; } } - qCritical(QString("No plugin with priority %1").arg(priority).toLocal8Bit()); + log::error("No plugin with priority {}", priority); return -1; } @@ -824,7 +824,7 @@ void PluginList::updateIndices() continue; } if (m_ESPs[i].m_Priority >= static_cast(m_ESPs.size())) { - qCritical("invalid plugin priority: %d", m_ESPs[i].m_Priority); + log::error("invalid plugin priority: {}", m_ESPs[i].m_Priority); continue; } m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; @@ -1067,9 +1067,9 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int this->index(0, 0), this->index(static_cast(m_ESPs.size()), columnCount())); } catch (const std::exception &e) { - qCritical("failed to invoke state changed notification: %s", e.what()); + log::error("failed to invoke state changed notification: {}", e.what()); } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); + log::error("failed to invoke state changed notification: unknown exception"); } } @@ -1368,7 +1368,7 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, m_Masters.insert(QString(iter->c_str())); } } catch (const std::exception &e) { - qCritical("failed to parse plugin file %s: %s", qUtf8Printable(fullPath), e.what()); + log::error("failed to parse plugin file {}: {}", fullPath, e.what()); m_IsMaster = false; m_IsLight = false; m_IsLightFlagged = false; diff --git a/src/profile.cpp b/src/profile.cpp index d4778305..555de89a 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -572,7 +572,7 @@ void Profile::setModsEnabled(const QList &modsToEnable, const QLis QList dirtyMods; for (auto idx : modsToEnable) { if (idx >= m_ModStatus.size()) { - qCritical() << tr("invalid mod index: %1").arg(idx); + log::error("invalid mod index: {}", idx); continue; } if (!m_ModStatus[idx].m_Enabled) { @@ -582,7 +582,7 @@ void Profile::setModsEnabled(const QList &modsToEnable, const QLis } for (auto idx : modsToDisable) { if (idx >= m_ModStatus.size()) { - qCritical() << tr("invalid mod index: %1").arg(idx); + log::error("invalid mod index: {}", idx); continue; } if (ModInfo::getByIndex(idx)->alwaysEnabled()) { diff --git a/src/settings.cpp b/src/settings.cpp index 92ae2251..9c303442 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -221,9 +221,7 @@ QString Settings::deObfuscate(const QString key) } else { const auto e = GetLastError(); if (e != ERROR_NOT_FOUND) { - qCritical().nospace() - << "Retrieving encrypted data failed: " - << formatSystemMessageQ(e); + log::error("Retrieving encrypted data failed: {}", formatSystemMessageQ(e)); } } delete[] keyData; @@ -368,11 +366,7 @@ bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { const auto e = GetLastError(); - - qCritical().nospace() - << "Storing API key failed: " - << formatSystemMessageQ(e); - + log::error("Storing API key failed: {}", formatSystemMessageQ(e)); return false; } @@ -493,9 +487,7 @@ void Settings::setSteamLogin(QString username, QString password) } if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); - qCritical().nospace() - << "Storing or deleting password failed: " - << formatSystemMessageQ(e); + log::error("Storing or deleting password failed: {}", formatSystemMessageQ(e)); } } diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 0dae31ac..99943d04 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -485,7 +485,6 @@ void SettingsDialog::onValidatorStateChanged( for (auto&& line : log.split("\n")) { addNexusLog(line); } - } updateNexusState(); } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 9d9edd85..2cdbac74 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include "leaktrace.h" #include "error_report.h" +#include #include #include #include @@ -35,6 +36,8 @@ along with Mod Organizer. If not, see . namespace MOShared { +namespace log = MOBase::log; + static const int MAXPATH_UNICODE = 32767; class OriginConnection { @@ -103,7 +106,7 @@ public: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - vlog("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); + log::error("failed to change name lookup from {} to {}", oldName, newName); } } @@ -714,14 +717,14 @@ void DirectoryEntry::removeFile(FileEntry::Index index) if (iter != m_Files.end()) { m_Files.erase(iter); } else { - vlog("file \"%ls\" not in directory \"%ls\"", - m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); + log::error( + "file \"{}\" not in directory \"{}\"", + m_FileRegister->getFile(index)->getName(), this->getName()); } } else { - vlog("file \"%ls\" not in directory \"%ls\", directory empty", - m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); + log::error( + "file \"{}\" not in directory \"{}\", directory empty", + m_FileRegister->getFile(index)->getName(), this->getName()); } } @@ -844,7 +847,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry *temp = findSubDirectory(pathComponent); if (temp != nullptr) { if (len >= path.size()) { - vlog("unexpected end of path"); + log::error("unexpected end of path"); return FileEntry::Ptr(); } return temp->searchFile(path.substr(len + 1), directory); @@ -988,7 +991,7 @@ bool FileRegister::removeFile(FileEntry::Index index) m_Files.erase(index); return true; } else { - vlog("invalid file index for remove: %lu", index); + log::error("invalid file index for remove: {}", index); return false; } } @@ -1002,7 +1005,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) m_Files.erase(iter); } } else { - vlog("invalid file index for remove (for origin): %lu", index); + log::error("invalid file index for remove (for origin): {}", index); } } diff --git a/src/shared/error_report.h b/src/shared/error_report.h index a003ee09..17b25645 100644 --- a/src/shared/error_report.h +++ b/src/shared/error_report.h @@ -30,5 +30,3 @@ void reportError(LPCSTR format, ...); void reportError(LPCWSTR format, ...); } // namespace MOShared - -void vlog(const char* format, ...); diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index 4ee4716e..b1643b2d 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "ui_syncoverwritedialog.h" #include #include +#include #include #include @@ -86,7 +87,7 @@ void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *director if (subDir != nullptr) { readTree(fileInfo.absoluteFilePath(), subDir, newItem); } else { - qCritical("no directory structure for %s?", qUtf8Printable(file)); + log::error("no directory structure for {}?", file); delete newItem; newItem = nullptr; } diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 130cd76f..0c0eb1cc 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -1,7 +1,10 @@ #include "texteditor.h" #include "utility.h" +#include #include +using namespace MOBase; + TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), @@ -249,7 +252,7 @@ QWidget* TextEditor::wrapEditWidget() auto index = splitter->indexOf(this); if (index == -1) { - qCritical( + log::error( "TextEditor: cannot wrap edit widget to display a toolbar, " "parent is a splitter, but widget isn't in it"); @@ -260,7 +263,7 @@ QWidget* TextEditor::wrapEditWidget() } else { // unknown parent - qCritical( + log::error( "TextEditor: cannot wrap edit widget to display a toolbar, " "no parent or parent has no layout"); diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index 130df14f..1b211fd3 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "isavegame.h" #include "savegameinfo.h" #include +#include #include #include @@ -186,7 +187,7 @@ void TransferSavesDialog::on_moveToLocalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellMove(source, destination, this); }, - "Failed to move %s to %s")) { + "Failed to move {} to {}")) { refreshGlobalSaves(); refreshGlobalCharacters(); refreshLocalSaves(); @@ -203,7 +204,7 @@ void TransferSavesDialog::on_copyToLocalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellCopy(source, destination, this); }, - "Failed to copy %s to %s")) { + "Failed to copy {} to {}")) { refreshLocalSaves(); refreshLocalCharacters(); } @@ -218,7 +219,7 @@ void TransferSavesDialog::on_moveToGlobalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellMove(source, destination, this); }, - "Failed to move %s to %s")) { + "Failed to move {} to {}")) { refreshGlobalSaves(); refreshGlobalCharacters(); refreshLocalSaves(); @@ -235,7 +236,7 @@ void TransferSavesDialog::on_copyToGlobalBtn_clicked() [this](const QString &source, const QString &destination) -> bool { return shellCopy(source, destination, this); }, - "Failed to copy %s to %s")) { + "Failed to copy {} to {}")) { refreshGlobalSaves(); refreshGlobalCharacters(); } @@ -340,9 +341,7 @@ bool TransferSavesDialog::transferCharacters( } if (!method(sourceFile.absoluteFilePath(), destinationFile)) { - qCritical(errmsg, - sourceFile.absoluteFilePath().toUtf8().constData(), - qUtf8Printable(destinationFile)); + log::error(errmsg, sourceFile.absoluteFilePath(), destinationFile); } } } -- cgit v1.3.1 From b3d0ddb0b75da4abd59cae1508d983945c8e235d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 04:21:45 -0400 Subject: changed qDebug() to log::debug() removed some commented out logging --- src/categories.cpp | 4 +- src/downloadlistwidget.cpp | 5 ++- src/downloadmanager.cpp | 35 ++++++++--------- src/executableslist.cpp | 4 +- src/filerenamer.cpp | 43 +++++++++++---------- src/installationmanager.cpp | 16 ++++---- src/instancemanager.cpp | 2 +- src/loadmechanism.cpp | 13 ++++--- src/mainwindow.cpp | 37 +++++++----------- src/messagedialog.cpp | 6 ++- src/modinfodialog.cpp | 4 +- src/modinfodialogconflicts.cpp | 24 ++++++++---- src/modinfodialogfiletree.cpp | 12 +++--- src/modinforegular.cpp | 5 ++- src/modlist.cpp | 7 ++-- src/modlistsortproxy.cpp | 2 +- src/nexusinterface.cpp | 8 ++-- src/nxmaccessmanager.cpp | 4 +- src/organizercore.cpp | 41 +++++++++++--------- src/persistentcookiejar.cpp | 2 +- src/plugincontainer.cpp | 14 +++---- src/pluginlist.cpp | 6 +-- src/profile.cpp | 16 ++++---- src/qtgroupingproxy.cpp | 87 +++++++++++++++++------------------------- src/selfupdater.cpp | 18 ++++----- src/settings.cpp | 7 ++-- src/usvfsconnector.cpp | 19 ++++----- 27 files changed, 213 insertions(+), 228 deletions(-) (limited to 'src') diff --git a/src/categories.cpp b/src/categories.cpp index 7acf6ff5..12b18998 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -360,10 +360,10 @@ unsigned int CategoryFactory::resolveNexusID(int nexusID) const { std::map::const_iterator iter = m_NexusMap.find(nexusID); if (iter != m_NexusMap.end()) { - qDebug("nexus category id %d maps to internal %d", nexusID, iter->second); + log::debug("nexus category id {} maps to internal {}", nexusID, iter->second); return iter->second; } else { - qDebug("nexus category id %d not mapped", nexusID); + log::debug("nexus category id {} not mapped", nexusID); return 0U; } } diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index e2a6f321..85d27831 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "downloadlist.h" #include "downloadlistwidget.h" +#include #include #include #include @@ -29,6 +30,8 @@ along with Mod Organizer. If not, see . #include #include +using namespace MOBase; + void DownloadProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QModelIndex sourceIndex = m_SortProxy->mapToSource(index); @@ -286,7 +289,7 @@ void DownloadListWidget::issueDelete() void DownloadListWidget::issueRemoveFromView() { - qDebug() << "removing from view: " << m_ContextRow; + log::debug("removing from view: {}", m_ContextRow); emit removeDownload(m_ContextRow, false); } diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 348b2108..d2556faa 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -352,7 +352,7 @@ void DownloadManager::refreshList() } } if (orphans.size() > 0) { - qDebug("%d orphaned meta files will be deleted", orphans.size()); + log::debug("{} orphaned meta files will be deleted", orphans.size()); shellDelete(orphans, true); } @@ -379,7 +379,7 @@ void DownloadManager::refreshList() } //if (m_ActiveDownloads.size() != downloadsBefore) { - qDebug("Downloads after refresh: %d", m_ActiveDownloads.size()); + log::debug("Downloads after refresh: {}", m_ActiveDownloads.size()); //} emit update(-1); @@ -401,7 +401,7 @@ bool DownloadManager::addDownload(const QStringList &URLs, QString gameName, } QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); - qDebug("selected download url: %s", qUtf8Printable(preferredUrl.toString())); + log::debug("selected download url: {}", preferredUrl.toString()); QNetworkRequest request(preferredUrl); request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, gameName, modID, fileID, fileInfo); @@ -562,9 +562,9 @@ void DownloadManager::addNXMDownload(const QString &url) break; } } - qDebug("add nxm download: %s", qUtf8Printable(url)); + log::debug("add nxm download: {}", url); if (foundGame == nullptr) { - qDebug("download requested for wrong game (game: %s, url: %s)", qUtf8Printable(m_ManagedGame->gameShortName()), qUtf8Printable(nxmInfo.game())); + log::debug("download requested for wrong game (game: {}, url: {})", m_ManagedGame->gameShortName(), nxmInfo.game()); QMessageBox::information(nullptr, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " "has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok); return; @@ -572,13 +572,14 @@ void DownloadManager::addNXMDownload(const QString &url) for (auto tuple : m_PendingDownloads) { if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) { - QString debugStr("download requested is already queued (mod: %1, file: %2)"); - QString infoStr(tr("There is already a download queued for this file.\n\nMod %1\nFile %2")); + const auto infoStr = + tr("There is already a download queued for this file.\n\nMod %1\nFile %2") + .arg(nxmInfo.modId()).arg(nxmInfo.fileId()); - debugStr = debugStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId()); - infoStr = infoStr.arg(nxmInfo.modId()).arg(nxmInfo.fileId()); + log::debug( + "download requested is already queued (mod: {}, file: {})", + nxmInfo.modId(), nxmInfo.fileId()); - qDebug(qUtf8Printable(debugStr)); QMessageBox::information(nullptr, tr("Already Queued"), infoStr, QMessageBox::Ok); return; } @@ -622,7 +623,7 @@ void DownloadManager::addNXMDownload(const QString &url) infoStr = infoStr.arg(QStringLiteral("")); } - qDebug(qUtf8Printable(debugStr)); + log::debug("{}", debugStr); QMessageBox::information(nullptr, tr("Already Started"), infoStr, QMessageBox::Ok); return; } @@ -883,7 +884,7 @@ void DownloadManager::resumeDownloadInt(int index) if (info->m_State == STATE_ERROR) { info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); } - qDebug("request resume from url %s", qUtf8Printable(info->currentURL())); + log::debug("request resume from url {}", info->currentURL()); QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); request.setHeader(QNetworkRequest::UserAgentHeader, m_NexusInterface->getAccessManager()->userAgent()); if (info->m_State != STATE_ERROR) { @@ -896,7 +897,7 @@ void DownloadManager::resumeDownloadInt(int index) std::get<2>(info->m_SpeedDiff) = 0; std::get<3>(info->m_SpeedDiff) = 0; std::get<4>(info->m_SpeedDiff) = 0; - qDebug("resume at %lld bytes", info->m_ResumePos); + log::debug("resume at {} bytes", info->m_ResumePos); startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); } emit update(index); @@ -993,11 +994,11 @@ void DownloadManager::queryInfoMd5(int index) downloadFile.setFileName(m_OrganizerCore->downloadsPath() + "\\" + info->m_FileName); } if (!downloadFile.exists()) { - qDebug("Can't find download file %s", info->m_FileName); + log::debug("Can't find download file {}", info->m_FileName); return; } if (!downloadFile.open(QIODevice::ReadOnly)) { - qDebug("Can't open download file %s", info->m_FileName); + log::debug("Can't open download file {}", info->m_FileName); return; } info->m_Hash = QCryptographicHash::hash(downloadFile.readAll(), QCryptographicHash::Md5); @@ -1384,7 +1385,7 @@ void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadMana m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->gameName, info->m_FileInfo->modID, this, info->m_DownloadID, QString())); } break; case STATE_FETCHINGMODINFO_MD5: { - qDebug(qUtf8Printable(QString("Searching %1 for MD5 of %2").arg(info->m_GamesToQuery[0]).arg(QString(info->m_Hash.toHex())))); + log::debug("Searching {} for MD5 of {}", info->m_GamesToQuery[0], QString(info->m_Hash.toHex())); m_RequestIDs.insert(m_NexusInterface->requestInfoFromMd5(info->m_GamesToQuery[0], info->m_Hash, this, info->m_DownloadID, QString())); } break; case STATE_READY: { @@ -1780,7 +1781,7 @@ void DownloadManager::nxmFileInfoFromMd5Available(QString gameName, QVariant use if (chosenIdx < 0) { chosenIdx = i; //intentional to not break in order to check other results } else { - qDebug("Multiple active files found during MD5 search. Defaulting to time stamps..."); + log::debug("Multiple active files found during MD5 search. Defaulting to time stamps..."); chosenIdx = -1; break; } diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 2408e8f3..3f76bb6f 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -166,7 +166,7 @@ std::vector ExecutablesList::getPluginExecutables( void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game) { - qDebug("resetting plugin executables"); + log::debug("resetting plugin executables"); Q_ASSERT(game != nullptr); @@ -295,7 +295,7 @@ std::optional ExecutablesList::makeNonConflictingTitle( void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game) { - qDebug() << "upgrading executables list"; + log::debug("upgrading executables list"); Q_ASSERT(game != nullptr); diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index 8835f52f..a97d7742 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -18,10 +18,10 @@ FileRenamer::FileRenamer(QWidget* parent, QFlags flags) FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) { - qDebug().nospace() << "renaming " << oldName << " to " << newName; + log::debug("renaming {} to {}", oldName, newName); if (QFileInfo(newName).exists()) { - qDebug().nospace() << newName << " already exists"; + log::debug("{} already exists", newName); // target file already exists, confirm replacement auto answer = confirmReplace(newName); @@ -29,24 +29,25 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt switch (answer) { case DECISION_SKIP: { // user wants to skip this file - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } case DECISION_REPLACE: { - qDebug().nospace() << "removing " << newName; + log::debug("removing {}", newName); + // user wants to replace the file, so remove it if (!QFile(newName).remove()) { log::warn("failed to remove '{}'", newName); // removal failed, warn the user and allow canceling if (!removeFailed(newName)) { - qDebug().nospace() << "canceling " << oldName; + log::debug("canceling {}", oldName); // user wants to cancel return RESULT_CANCEL; } // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } @@ -56,7 +57,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt case DECISION_CANCEL: // fall-through default: { // user wants to stop - qDebug().nospace() << "canceling"; + log::debug("canceling"); return RESULT_CANCEL; } } @@ -70,17 +71,17 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt // renaming failed, warn the user and allow canceling if (!renameFailed(oldName, newName)) { // user wants to cancel - qDebug().nospace() << "canceling"; + log::debug("canceling"); return RESULT_CANCEL; } // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; + log::debug("skipping {}", oldName); return RESULT_SKIP; } // everything worked - qDebug().nospace() << "successfully renamed " << oldName << " to " << newName; + log::debug("successfully renamed {} to {}", oldName, newName); return RESULT_OK; } @@ -88,12 +89,12 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) { if (m_flags & REPLACE_ALL) { // user wants to silently replace all - qDebug().nospace() << "user has selected replace all"; + log::debug("user has selected replace all"); return DECISION_REPLACE; } else if (m_flags & REPLACE_NONE) { // user wants to silently skip all - qDebug().nospace() << "user has selected replace none"; + log::debug("user has selected replace none"); return DECISION_SKIP; } @@ -117,28 +118,28 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) switch (answer) { case QMessageBox::Yes: - qDebug().nospace() << "user wants to replace"; + log::debug("user wants to replace"); return DECISION_REPLACE; case QMessageBox::No: - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return DECISION_SKIP; case QMessageBox::YesToAll: - qDebug().nospace() << "user wants to replace all"; + log::debug("user wants to replace all"); // remember the answer m_flags |= REPLACE_ALL; return DECISION_REPLACE; case QMessageBox::NoToAll: - qDebug().nospace() << "user wants to replace none"; + log::debug("user wants to replace none"); // remember the answer m_flags |= REPLACE_NONE; return DECISION_SKIP; case QMessageBox::Cancel: // fall-through default: - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return DECISION_CANCEL; } } @@ -158,12 +159,12 @@ bool FileRenamer::removeFailed(const QString& name) if (answer == QMessageBox::Cancel) { // user wants to stop - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return false; } // skip this one and continue - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return true; } @@ -182,11 +183,11 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) if (answer == QMessageBox::Cancel) { // user wants to stop - qDebug().nospace() << "user wants to cancel"; + log::debug("user wants to cancel"); return false; } // skip this one and continue - qDebug().nospace() << "user wants to skip"; + log::debug("user wants to skip"); return true; } diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index fd971f47..89d0079f 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -398,7 +398,7 @@ bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *nod for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) || (!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) { - qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData()); + log::debug("{} on the top level", (*iter)->getData().name.toQString()); return true; } } @@ -424,7 +424,7 @@ DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *da (currentNode->numNodes() == 1)) { currentNode = *currentNode->nodesBegin(); } else { - qDebug("not a simple archive"); + log::debug("not a simple archive"); return nullptr; } } @@ -576,7 +576,7 @@ bool InstallationManager::doInstall(GuessedValue &modName, QString game QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory); - qDebug("installing to \"%s\"", qUtf8Printable(targetDirectoryNative)); + log::debug("installing to \"{}\"", targetDirectoryNative); m_InstallationProgress = new QProgressDialog(m_ParentWidget); ON_BLOCK_EXIT([this] () { @@ -764,7 +764,7 @@ bool InstallationManager::install(const QString &fileName, if ((modID == 0) && (guessedModID != -1)) { modID = guessedModID; } else if (modID != guessedModID) { - qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); + log::debug("passed mod id: {}, guessed id: {}", modID, guessedModID); } modName.update(guessedModName, GUESS_GOOD); @@ -774,7 +774,7 @@ bool InstallationManager::install(const QString &fileName, if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { m_CurrentFile = fileInfo.fileName(); } - qDebug("using mod name \"%s\" (id %d) -> %s", qUtf8Printable(modName), modID, qUtf8Printable(m_CurrentFile)); + log::debug("using mod name \"{}\" (id {}) -> {}", QString(modName), modID, m_CurrentFile); //If there's an archive already open, close it. This happens with the bundle //installer when it uncompresses a split archive, then finds it has a real archive @@ -785,9 +785,9 @@ bool InstallationManager::install(const QString &fileName, bool archiveOpen = m_ArchiveHandler->open(fileName, new MethodCallback(this, &InstallationManager::queryPassword)); if (!archiveOpen) { - qDebug("integrated archiver can't open %s: %s (%d)", - qUtf8Printable(fileName), - qUtf8Printable(getErrorString(m_ArchiveHandler->getLastError())), + log::debug("integrated archiver can't open {}: {} ({})", + fileName, + getErrorString(m_ArchiveHandler->getLastError()), m_ArchiveHandler->getLastError()); } ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 55ef3fc8..fdc30e22 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -224,7 +224,7 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint); if (selection.exec() == QDialog::Rejected) { - qDebug("rejected"); + log::debug("rejected"); throw MOBase::MyException(QObject::tr("Canceled")); } diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 8f0529ce..4d6cebd4 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -141,7 +142,7 @@ void LoadMechanism::deactivateScriptExtender() { vfsDLLName = ToQString(AppConfig::vfs64DLLName()); } - qDebug("USVFS DLL Name: " + vfsDLLName.toLatin1()); + log::debug("USVFS DLL Name: {}", vfsDLLName); if (vfsDLLName != "") { if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) { // remove dll from SE plugins directory @@ -215,8 +216,8 @@ void LoadMechanism::activateScriptExtender() QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL)); QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL); - qDebug("DLL USVFS Target Path: " + targetPath.toLatin1()); - qDebug("DLL USVFS VFS DLL Path: " + vfsDLLPath.toLatin1()); + log::debug("DLL USVFS Target Path: {}", targetPath); + log::debug("DLL USVFS VFS DLL Path: {}", vfsDLLPath); QFile dllFile(targetPath); @@ -297,17 +298,17 @@ void LoadMechanism::activate(EMechanism mechanism) { switch (mechanism) { case LOAD_MODORGANIZER: { - qDebug("Load Mechanism: Mod Organizer"); + log::debug("Load Mechanism: Mod Organizer"); deactivateProxyDLL(); deactivateScriptExtender(); } break; case LOAD_SCRIPTEXTENDER: { - qDebug("Load Mechanism: ScriptExtender"); + log::debug("Load Mechanism: ScriptExtender"); deactivateProxyDLL(); activateScriptExtender(); } break; case LOAD_PROXYDLL: { - qDebug("Load Mechanism: Proxy DLL"); + log::debug("Load Mechanism: Proxy DLL"); deactivateScriptExtender(); activateProxyDLL(); } break; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ad87ba03..e502bdb1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -867,7 +867,7 @@ void MainWindow::updatePinnedExecutables() exeAction->setStatusTip(exe.binaryInfo().filePath()); if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { - qDebug("failed to connect trigger?"); + log::debug("failed to connect trigger?"); } if (m_linksSeparator) { @@ -1711,7 +1711,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) // ensure the new index is valid if (index < 0 || index >= ui->profileBox->count()) { - qDebug("invalid profile index, using last profile"); + log::debug("invalid profile index, using last profile"); ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); } @@ -2060,7 +2060,7 @@ void MainWindow::refreshSaveList() QDir savesDir = currentSavesDir(); savesDir.setNameFilters(filters); - qDebug("reading save games from %s", qUtf8Printable(savesDir.absolutePath())); + log::debug("reading save games from {}", savesDir.absolutePath()); QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); for (const QFileInfo &file : files) { @@ -2261,15 +2261,6 @@ void MainWindow::fixCategories() void MainWindow::setupNetworkProxy(bool activate) { QNetworkProxyFactory::setUseSystemConfiguration(activate); -/* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest); - query.setProtocolTag("http"); - QList proxies = QNetworkProxyFactory::systemProxyForQuery(query); - if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) { - qDebug("Using proxy: %s", qUtf8Printable(proxies.at(0).hostName())); - QNetworkProxy::setApplicationProxy(proxies[0]); - } else { - qDebug("Not using proxy"); - }*/ } @@ -2456,7 +2447,7 @@ void MainWindow::unlock() { //If you come through here with a null lock pointer, it's a bug! if (m_LockDialog == nullptr) { - qDebug("Unlocking main window when already unlocked"); + log::debug("Unlocking main window when already unlocked"); return; } --m_LockCount; @@ -3259,7 +3250,7 @@ void MainWindow::displayModInformation( ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) { if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) { - qDebug("A different mod information dialog is open. If this is incorrect, please restart MO"); + log::debug("A different mod information dialog is open. If this is incorrect, please restart MO"); return; } std::vector flags = modInfo->getFlags(); @@ -4325,7 +4316,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { int maxRow = -1; for (const QPersistentModelIndex &idx : selected) { - qDebug("change categories on: %s", qUtf8Printable(idx.data().toString())); + log::debug("change categories on: {}", idx.data().toString()); QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); if (modIdx.row() != m_ContextIdx.row()) { addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); @@ -4407,7 +4398,7 @@ void MainWindow::saveArchiveList() } } if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); + log::debug("{} saved", QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName())); } } else { log::warn("archive list not initialised"); @@ -5364,7 +5355,7 @@ void MainWindow::installTranslator(const QString &name) QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { if (m_CurrentLanguage.contains(QRegularExpression("^.*_(EN|en)(-.*)?$"))) { - qDebug("localization file %s not found", qUtf8Printable(fileName)); + log::debug("localization file %s not found", fileName); } // we don't actually expect localization files for English (en, en-us, en-uk, and any variation thereof) } @@ -5389,7 +5380,7 @@ void MainWindow::languageChange(const QString &newLanguage) installTranslator(QFileInfo(fileName).baseName()); } ui->retranslateUi(this); - qDebug("loaded language %s", qUtf8Printable(newLanguage)); + log::debug("loaded language {}", newLanguage); ui->profileBox->setItemText(0, QObject::tr("")); @@ -5634,7 +5625,7 @@ void MainWindow::openDataOriginExplorer_clicked() const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); - qDebug().nospace() << "opening in explorer: " << fullPath; + log::debug("opening in explorer: {}", fullPath); shell::ExploreFile(fullPath); } @@ -6120,7 +6111,7 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, int, QNetworkReply::NetworkError error, const QString &errorString) { if (error == QNetworkReply::ContentAccessDenied || error == QNetworkReply::ContentNotFoundError) { - qDebug(qUtf8Printable(tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID))); + log::debug("{}", tr("Mod ID %1 no longer seems to be available on Nexus.").arg(modID)); } else { MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this); } @@ -6587,7 +6578,7 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe std::string dependency(match[2].first, match[2].second); m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { - qDebug("[loot] %s", line.c_str()); + log::debug("[loot] {}", line); } } } @@ -6632,7 +6623,7 @@ void MainWindow::on_bossButton_clicked() try { m_OrganizerCore.prepareVFS(); } catch (const UsvfsConnectorException &e) { - qDebug(e.what()); + log::debug("{}", e.what()); return; } catch (const std::exception &e) { QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); @@ -6662,7 +6653,7 @@ void MainWindow::on_bossButton_clicked() if (isJobHandle) { if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { - qDebug("no more processes in job"); + log::debug("no more processes in job"); break; } else { if (lastProcessID != info.ProcessIdList[0]) { diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 6c6de3e7..78a5dd4d 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -19,10 +19,13 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "ui_messagedialog.h" +#include #include #include #include +using namespace MOBase; + MessageDialog::MessageDialog(const QString &text, QWidget *reference) : QDialog(reference), ui(new Ui::MessageDialog) @@ -81,7 +84,8 @@ void MessageDialog::resizeEvent(QResizeEvent *event) void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront) { - qDebug("%s", qUtf8Printable(text)); + log::debug("{}", text); + if (reference != nullptr) { if (bringToFront || (qApp->activeWindow() != nullptr)) { MessageDialog *dialog = new MessageDialog(text, reference); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a7a6b0d7..4b1e2f76 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -556,9 +556,7 @@ void ModInfoDialog::switchToTab(ModInfoTabIDs id) } // this could happen if the tab is not visible right now - qDebug() - << "can't switch to tab ID " << static_cast(id) - << ", not available"; + log::debug("can't switch to tab ID {}, not available", static_cast(id)); } MOShared::FilesOrigin* ModInfoDialog::getOrigin() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d16d548c..9a5d9d8d 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -438,10 +438,18 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) const auto n = smallSelectionSize(tree); - qDebug().nospace().noquote() - << (visible ? "unhiding" : "hiding") << " " - << (n > max_small_selection ? "a lot of" : QString("%1").arg(n)) - << " conflict files"; + // logging + { + const QString action = (visible ? "unhiding" : "hiding"); + + QString files; + if (n > max_small_selection) + files = "a lot of"; + else + files = QString("%1").arg(n); + + log::debug("{} {} conflict files", action, files); + } QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); @@ -467,7 +475,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) if (visible) { if (!item->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->relativeName() << ", skipping"; + log::debug("cannot unhide {}, skipping", item->relativeName()); return true; } @@ -475,7 +483,7 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } else { if (!item->canHide()) { - qDebug().nospace() << "cannot hide " << item->relativeName() << ", skipping"; + log::debug("cannot hide {}, skipping", item->relativeName()); return true; } @@ -504,10 +512,10 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) return true; }); - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; + log::debug("{} conflict files done", (visible ? "unhiding" : "hiding")); if (changed) { - qDebug().nospace() << "triggering refresh"; + log::debug("triggering refresh"); if (origin()) { emitOriginModified(); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 219ddf35..207c792d 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -257,9 +257,9 @@ void FileTreeTab::changeVisibility(bool visible) bool changed = false; bool stop = false; - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << selection.size() << " filetree files"; + log::debug( + "{} {} filetree files", + (visible ? "unhiding" : "hiding"), selection.size()); QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); @@ -280,13 +280,13 @@ void FileTreeTab::changeVisibility(bool visible) if (visible) { if (!canUnhideFile(false, path)) { - qDebug().nospace() << "cannot unhide " << path << ", skipping"; + log::debug("cannot unhide {}, skipping", path); continue; } result = unhideFile(renamer, path); } else { if (!canHideFile(false, path)) { - qDebug().nospace() << "cannot hide " << path << ", skipping"; + log::debug("cannot hide {}, skipping", path); continue; } result = hideFile(renamer, path); @@ -312,7 +312,7 @@ void FileTreeTab::changeVisibility(bool visible) } } - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; + log::debug("{} filetree files done", (visible ? "unhiding" : "hiding")); if (changed) { if (origin()) { diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 074fa9e2..6e11befc 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -883,8 +883,9 @@ std::vector ModInfoRegular::getIniTweaks() const int numTweaks = metaFile.beginReadArray("INI Tweaks"); if (numTweaks != 0) { - qDebug("%d active ini tweaks in %s", - numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); + log::debug( + "{} active ini tweaks in {}", + numTweaks, QDir::toNativeSeparators(metaFileName)); } for (int i = 0; i < numTweaks; ++i) { diff --git a/src/modlist.cpp b/src/modlist.cpp index 6ebd0e8b..39f51d72 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1013,9 +1013,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); for (auto url : mimeData->urls()) { - //qDebug("URL drop requested: %s -> %s", qUtf8Printable(url.url()), qUtf8Printable(modDir.canonicalPath())); if (!url.isLocalFile()) { - qDebug("URL drop ignored: \"%s\" is not a local file", qUtf8Printable(url.url())); + log::debug("URL drop ignored: \"{}\" is not a local file", url.url()); continue; } @@ -1035,7 +1034,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa originName = overwriteName; relativePath = overwriteDir.relativeFilePath(sourceFile); } else { - qDebug("URL drop ignored: \"%s\" is not a known file to MO", qUtf8Printable(sourceFile)); + log::debug("URL drop ignored: \"{}\" is not a known file to MO", sourceFile); continue; } @@ -1047,7 +1046,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa if (sourceList.count()) { if (!shellMove(sourceList, targetList)) { - qDebug("Failed to move file (error %d)", ::GetLastError()); + log::debug("Failed to move file (error {})", ::GetLastError()); return false; } } diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index d330e0c2..77ffad96 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -482,7 +482,7 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons QModelIndex idx = sourceModel()->index(row, 0, parent); if (!idx.isValid()) { - qDebug("invalid mod index"); + log::debug("invalid mod index"); return false; } if (sourceModel()->hasChildren(idx)) { diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index c797aed6..0e2bb45b 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -316,15 +316,15 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo } else { modID = strtol(candidate.c_str(), nullptr, 10); } - qDebug("mod id guessed: %s -> %d", qUtf8Printable(fileName), modID); + log::debug("mod id guessed: {} -> {}", fileName, modID); } else if (std::regex_search(fileNameUTF8.constData(), result, simpleexp)) { - qDebug("simple expression matched, using name only"); + log::debug("simple expression matched, using name only"); modName = QString::fromUtf8(result[1].str().c_str()); modName = modName.replace('_', ' ').trimmed(); modID = -1; } else { - qDebug("no expression matched!"); + log::debug("no expression matched!"); modName.clear(); modID = -1; } @@ -860,7 +860,7 @@ void NexusInterface::requestFinished(std::list::iterator iter) if (nexusError.length() == 0) { nexusError = tr("empty response"); } - qDebug("nexus error: %s", qUtf8Printable(nexusError)); + log::debug("nexus error: {}", nexusError); emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), nexusError); } else { QJsonDocument responseDoc = QJsonDocument::fromJson(data); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 9f40894e..fd1dc0c1 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -600,9 +600,9 @@ void NXMAccessManager::showCookies() const { QUrl url(NexusBaseUrl + "/"); for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { - qDebug("%s - %s (expires: %s)", + log::debug("{} - {} (expires: {})", cookie.name().constData(), cookie.value().constData(), - qUtf8Printable(cookie.expirationDate().toString())); + cookie.expirationDate().toString()); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 725371e9..f6802673 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -89,9 +89,9 @@ static bool isOnline() if (addresses.count() == 0) { continue; } - qDebug("interface %s seems to be up (address: %s)", - qUtf8Printable(iter->humanReadableName()), - qUtf8Printable(addresses[0].ip().toString())); + log::debug("interface {} seems to be up (address: {})", + iter->humanReadableName(), + addresses[0].ip().toString()); connected = true; } } @@ -543,7 +543,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, if (isOnline() && !m_Settings.offlineMode()) { m_Updater.testForUpdate(); } else { - qDebug("user doesn't seem to be connected to the internet"); + log::debug("user doesn't seem to be connected to the internet"); } } } @@ -605,7 +605,7 @@ bool OrganizerCore::nexusApi(bool retry) QString apiKey; if (m_Settings.getNexusApiKey(apiKey)) { // credentials stored or user entered them manually - qDebug("attempt to verify nexus api key"); + log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); return true; } else { @@ -627,7 +627,7 @@ void OrganizerCore::startMOUpdate() void OrganizerCore::downloadRequestedNXM(const QString &url) { - qDebug("download requested: %s", qUtf8Printable(url)); + log::debug("download requested: {}", url); if (nexusApi()) { m_PendingDownloads.append(url); } else { @@ -1208,7 +1208,9 @@ QString OrganizerCore::findJavaInstallation(const QString& jarFile) 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()); + log::debug( + "failed to determine binary type of \"{}\": {}", + QString::fromWCharArray(buffer), ::GetLastError()); } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) { return QString::fromWCharArray(buffer); } @@ -1459,7 +1461,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, // need to remove our stored load order because it may be outdated if a foreign tool changed the // file time. After removing that file, refreshESPList will use the file time as the order if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - qDebug("removing loadorder.txt"); + log::debug("removing loadorder.txt"); QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } refreshDirectoryStructure(); @@ -1627,7 +1629,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, m_USVFS.updateForcedLibraries(forcedLibraries); } catch (const UsvfsConnectorException &e) { - qDebug(e.what()); + log::debug(e.what()); return INVALID_HANDLE_VALUE; } catch (const std::exception &e) { QMessageBox::warning(window, tr("Error"), e.what()); @@ -1694,17 +1696,16 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, .arg(QDir::toNativeSeparators(cwdPath), QDir::toNativeSeparators(binPath), arguments); - qDebug() << "Spawning proxyed process <" << cmdline << ">"; + log::debug("Spawning proxyed process <{}>", cmdline); return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, QCoreApplication::applicationDirPath(), true); } else { - qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">"; + log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); return startBinary(binary, arguments, currentDirectory, true); } } else { - qDebug("start of \"%s\" canceled by plugin", - qUtf8Printable(binary.absoluteFilePath())); + log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); return INVALID_HANDLE_VALUE; } } @@ -1872,9 +1873,11 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL processName += QString(" (%1)").arg(currentPID); if (uilock) uilock->setProcessName(processName); - qDebug() << "Waiting for" - << (originalHandle ? "spawned" : "usvfs") - << "process completion :" << qUtf8Printable(processName); + + log::debug( + "Waiting for {} process completion: {}", + (originalHandle ? "spawned" : "usvfs"), processName); + newHandle = false; } @@ -1943,11 +1946,11 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL } if (res == WAIT_OBJECT_0) - qDebug() << "Waiting for process completion successfull"; + log::debug("Waiting for process completion successfull"); else if (uiunlocked) - qDebug() << "Waiting for process completion aborted by UI"; + log::debug("Waiting for process completion aborted by UI"); else - qDebug() << "Waiting for process completion not successfull :" << res; + log::debug("Waiting for process completion not successfull: {}", res); if (handle != INVALID_HANDLE_VALUE) ::CloseHandle(handle); diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 670bf382..8657f356 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -13,7 +13,7 @@ PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *paren } PersistentCookieJar::~PersistentCookieJar() { - qDebug("save %s", qUtf8Printable(m_FileName)); + log::debug("save {}", m_FileName); save(); } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 36daec52..62cdff1e 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -91,7 +91,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) { // generic treatment for all plugins IPlugin *pluginObj = qobject_cast(plugin); if (pluginObj == nullptr) { - qDebug("not an IPlugin"); + log::debug("not an IPlugin"); return false; } plugin->setProperty("filename", fileName); @@ -164,7 +164,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) for (QObject *proxiedPlugin : matchingPlugins) { if (proxiedPlugin != nullptr) { if (registerPlugin(proxiedPlugin, pluginName)) { - qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); + log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); } else { log::warn( @@ -191,7 +191,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) } } - qDebug("no matching plugin interface"); + log::debug("no matching plugin interface"); return false; } @@ -225,7 +225,7 @@ void PluginContainer::unloadPlugins() QPluginLoader *loader = m_PluginLoaders.back(); m_PluginLoaders.pop_back(); if ((loader != nullptr) && !loader->unload()) { - qDebug("failed to unload %s: %s", qUtf8Printable(loader->fileName()), qUtf8Printable(loader->errorString())); + log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString()); } delete loader; } @@ -274,13 +274,13 @@ void PluginContainer::loadPlugins() loadCheck.open(QIODevice::WriteOnly); QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); + log::debug("looking for plugins in {}", QDir::toNativeSeparators(pluginPath)); QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); while (iter.hasNext()) { iter.next(); if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) { - qDebug("plugin \"%s\" blacklisted", qUtf8Printable(iter.fileName())); + log::debug("plugin \"{}\" blacklisted", iter.fileName()); continue; } loadCheck.write(iter.fileName().toUtf8()); @@ -296,7 +296,7 @@ void PluginContainer::loadPlugins() pluginName, pluginLoader->errorString()); } else { if (registerPlugin(pluginLoader->instance(), pluginName)) { - qDebug("loaded plugin \"%s\"", qUtf8Printable(QFileInfo(pluginName).fileName())); + log::debug("loaded plugin \"{}\"", QFileInfo(pluginName).fileName()); m_PluginLoaders.push_back(pluginLoader.release()); } else { m_FailedPlugins.push_back(pluginName); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index e436d7f6..6718641f 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -481,7 +481,7 @@ void PluginList::writeLockedOrder(const QString &fileName) const file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); } file.commit(); - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName))); + log::debug("{} saved", QDir::toNativeSeparators(fileName)); } @@ -506,7 +506,7 @@ void PluginList::saveTo(const QString &lockedOrderFileName } } if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(deleterFileName))); + log::debug("{} saved", QDir::toNativeSeparators(deleterFileName)); } } else if (QFile::exists(deleterFileName)) { shellDelete(QStringList() << deleterFileName); @@ -521,7 +521,7 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) return true; } - qDebug("setting file times on esps"); + log::debug("setting file times on esps"); for (ESPInfo &esp : m_ESPs) { std::wstring espName = ToWString(esp.m_Name); diff --git a/src/profile.cpp b/src/profile.cpp index 555de89a..27616986 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -40,7 +40,6 @@ along with Mod Organizer. If not, see . #include #include #include // for QStringList -#include // for qDebug, qWarning, etc #include // for qUtf8Printable #include #include @@ -232,7 +231,6 @@ void Profile::doWriteModlist() } for (std::map::const_reverse_iterator iter = m_ModIndexByPriority.crbegin(); iter != m_ModIndexByPriority.crend(); iter++ ) { - //qDebug(QString("write mod %1 to priority %2").arg(iter->first).arg(iter->second).toLocal8Bit()); // the priority order was inverted on load so it has to be inverted again unsigned int index = iter->second; if (index != UINT_MAX) { @@ -253,7 +251,7 @@ void Profile::doWriteModlist() } if (file.commitIfDifferent(m_LastModlistHash)) { - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(fileName))); + log::debug("{} saved", QDir::toNativeSeparators(fileName)); } } catch (const std::exception &e) { reportError(tr("failed to write mod list: %1").arg(e.what())); @@ -292,7 +290,7 @@ void Profile::createTweakedIniFile() .arg(formatSystemMessageQ(e))); } - qDebug("%s saved", qUtf8Printable(QDir::toNativeSeparators(tweakedIni))); + log::debug("{} saved", QDir::toNativeSeparators(tweakedIni)); } // static @@ -364,8 +362,9 @@ void Profile::renameModInList(QFile &modList, const QString &oldName, const QStr } if (renamed) - qDebug("Renamed %d \"%s\" mod to \"%s\" in %s", - renamed, qUtf8Printable(oldName), qUtf8Printable(newName), qUtf8Printable(modList.fileName())); + log::debug( + "Renamed {} \"{}\" mod to \"{}\" in {}", + renamed, oldName, newName, modList.fileName()); } void Profile::refreshModStatus() @@ -431,8 +430,9 @@ void Profile::refreshModStatus() modStatusModified = true; } } else { - qDebug("mod not found: \"%s\" (profile \"%s\")", - qUtf8Printable(modName), qUtf8Printable(m_Directory.path())); + log::debug( + "mod not found: \"{}\" (profile \"{}\")", + modName, m_Directory.path()); // need to rewrite the modlist to fix this modStatusModified = true; } diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index 5fcb84d3..ff9539d7 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -18,11 +18,14 @@ #include "qtgroupingproxy.h" +#include #include #include #include +using namespace MOBase; + /*! \class QtGroupingProxy \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level. @@ -86,7 +89,6 @@ QtGroupingProxy::setGroupedColumn( int groupedColumn ) QList QtGroupingProxy::belongsTo( const QModelIndex &idx ) { - //qDebug() << __FILE__ << __FUNCTION__; QList rowDataList; //get all the data for this index from the model @@ -106,7 +108,7 @@ QtGroupingProxy::belongsTo( const QModelIndex &idx ) i.next(); int role = i.key(); QVariant variant = i.value(); - // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant; + if ( variant.type() == QVariant::List ) { //a list of variants get's expanded to multiple rows @@ -162,7 +164,7 @@ QtGroupingProxy::buildTree() m_parentCreateList.clear(); int max = sourceModel()->rowCount( m_rootNode ); - //qDebug() << QString("building tree with %1 leafs.").arg( max ); + //WARNING: these have to be added in order because the addToGroups function is optimized for //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best. for( int row = 0; row < max; row++ ) @@ -232,9 +234,6 @@ QtGroupingProxy::addSourceRow( const QModelIndex &idx ) int updatedGroup = -1; if( !data.isEmpty() ) { - // qDebug() << QString("index %1 belongs to group %2").arg( row ) - // .arg( data[0][Qt::DisplayRole].toString() ); - foreach( const RowData &cachedData, m_groupMaps ) { //when this matches the index belongs to an existing group @@ -316,21 +315,12 @@ QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const pc.row = parent.row(); m_parentCreateList << pc; - //dumpParentCreateList(); - // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() ); - // for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) - // { - // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex << - // " | " << m_parentCreateList[i].row; - // } - return m_parentCreateList.size() - 1; } QModelIndex QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const { - // qDebug() << "index requested for: (" << row << "," << column << "), " << parent; if( !hasIndex(row, column, parent) ) { return QModelIndex(); } @@ -350,17 +340,15 @@ QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const QModelIndex QtGroupingProxy::parent( const QModelIndex &index ) const { - //qDebug() << "parent: " << index; if( !index.isValid() ) return QModelIndex(); int parentCreateIndex = index.internalId(); - //qDebug() << "parentCreateIndex: " << parentCreateIndex; if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() ) return QModelIndex(); struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; - //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")"; + //only items at column 0 have children return createIndex( pc.row, 0, pc.parentCreateIndex ); } @@ -368,12 +356,10 @@ QtGroupingProxy::parent( const QModelIndex &index ) const int QtGroupingProxy::rowCount( const QModelIndex &index ) const { - //qDebug() << "rowCount: " << index; if( !index.isValid() ) { //the number of top level groups + the number of non-grouped items int rows = m_groupMaps.count() + m_groupHash.value( std::numeric_limits::max() ).count(); - //qDebug() << rows << " in root group"; return rows; } @@ -382,12 +368,10 @@ QtGroupingProxy::rowCount( const QModelIndex &index ) const { qint64 groupIndex = index.row(); int rows = m_groupHash.value( groupIndex ).count(); - //qDebug() << rows << " in group " << m_groupMaps[groupIndex]; return rows; } else { QModelIndex originalIndex = mapToSource( index ); int rowCount = sourceModel()->rowCount( originalIndex ); - //qDebug() << "original item: rowCount == " << rowCount; return rowCount; } } @@ -447,7 +431,7 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const { if( !index.isValid() ) return QVariant(); - // qDebug() << __FUNCTION__ << index << " role: " << role; + int row = index.row(); int column = index.column(); if( isGroup( index ) ) @@ -495,11 +479,9 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const } } - //qDebug() << __FUNCTION__ << "is a group"; //use cached or precalculated data if( m_groupMaps[row][column].contains( Qt::DisplayRole ) ) { - // qDebug() << "Using cached data for " << row << "x" << column << ": " << m_groupMaps[row][column].value(Qt::DisplayRole).toString(); if ((m_flags & FLAG_NOGROUPNAME) != 0) { QModelIndex parentIndex = this->index( row, 0, index.parent() ); QModelIndex childIndex = this->index( 0, column, parentIndex ); @@ -526,18 +508,17 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const function = mapToSource(childIndex).data(m_aggregateRole).toInt(); } - //qDebug() << __FUNCTION__ << "childCount: " << childCount; //Need a parentIndex with column == 0 because only those have children. QModelIndex parentIndex = this->index( row, 0, index.parent() ); for( int childRow = 0; childRow < childCount; childRow++ ) { QModelIndex childIndex = this->index( childRow, column, parentIndex ); QVariant data = mapToSource( childIndex ).data( role ); - //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type()); + if( data.isValid() && !variantsOfChildren.contains( data ) ) variantsOfChildren << data; } - //qDebug() << "gathered this data from children: " << variantsOfChildren; + //saving in cache ItemData roleMap = m_groupMaps[row].value( column ); foreach( const QVariant &variant, variantsOfChildren ) @@ -547,8 +528,6 @@ QtGroupingProxy::data( const QModelIndex &index, int role ) const } } - //qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role]; - if( variantsOfChildren.count() == 0 ) return QVariant(); @@ -621,34 +600,30 @@ QtGroupingProxy::isGroup( const QModelIndex &index ) const QModelIndex QtGroupingProxy::mapToSource( const QModelIndex &index ) const { - //qDebug() << "mapToSource: " << index; if( !index.isValid() ) { return m_rootNode; } if( isGroup( index ) ) { - //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString(); return m_rootNode; } QModelIndex proxyParent = index.parent(); - //qDebug() << "parent: " << proxyParent; QModelIndex originalParent = mapToSource( proxyParent ); - //qDebug() << "originalParent: " << originalParent; + int originalRow = index.row(); if( originalParent == m_rootNode ) { int indexInGroup = index.row(); if( !proxyParent.isValid() ) indexInGroup -= m_groupMaps.count(); - //qDebug() << "indexInGroup" << indexInGroup; + QList childRows = m_groupHash.value( proxyParent.row() ); if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 ) return QModelIndex(); originalRow = childRows.at( indexInGroup ); - //qDebug() << "originalRow: " << originalRow; } return sourceModel()->index( originalRow, index.column(), originalParent ); } @@ -674,7 +649,7 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const QModelIndex proxyParent; QModelIndex sourceParent = idx.parent(); - //qDebug() << "sourceParent: " << sourceParent; + int proxyRow = idx.row(); int sourceRow = idx.row(); @@ -708,15 +683,12 @@ QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const proxyParent = QModelIndex(); // if the proxy item is not in a group it will be below the groups. int groupLength = m_groupMaps.count(); - //qDebug() << "groupNames length: " << groupLength; int i = m_groupHash.value( std::numeric_limits::max() ).indexOf( sourceRow ); - //qDebug() << "index in hash: " << i; + proxyRow = groupLength + i; } } - //qDebug() << "proxyParent: " << proxyParent; - //qDebug() << "proxyRow: " << proxyRow; return this->index( proxyRow, idx.column(), proxyParent ); } @@ -731,9 +703,9 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const return 0; } + //only if the grouped column has the editable flag set allow the //actions leading to setData on the source (edit & drop) - // qDebug() << idx; if( isGroup( idx ) ) { // dumpGroups(); @@ -749,7 +721,7 @@ QtGroupingProxy::flags( const QModelIndex &idx ) const m_rootNode.parent() ); if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 ) { - qDebug("row %d is not checkable", originalRow); + log::debug("row {} is not checkable", originalRow); checkable = false; } } @@ -892,9 +864,7 @@ QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int star if( parent != m_rootNode ) { //an item will be added to an original index, remap and pass it on - // qDebug() << parent; QModelIndex proxyParent = mapFromSource( parent ); - // qDebug() << proxyParent; beginInsertRows( proxyParent, start, end ); } } @@ -914,7 +884,12 @@ QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int en { //an item was added to an original index, remap and pass it on QModelIndex proxyParent = mapFromSource( parent ); - qDebug() << proxyParent; + + QString s; + QDebug debug(&s); + debug << proxyParent; + log::debug("{}", s); + //beginInsertRows had to be called in modelRowsAboutToBeInserted() endInsertRows(); } @@ -951,9 +926,7 @@ QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start else { //child item(s) of an original item will be removed, remap and pass it on - // qDebug() << parent; QModelIndex proxyParent = mapFromSource( parent ); - // qDebug() << proxyParent; beginRemoveRows( proxyParent, start, end ); } } @@ -1044,16 +1017,24 @@ QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const void QtGroupingProxy::dumpGroups() const { - qDebug() << "m_groupHash: "; + QString s; + QDebug debug(&s); + + debug << "m_groupHash:\n"; for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ ) { - qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex ); + debug << groupIndex << " : " << m_groupHash.value( groupIndex ) << "\n"; } - qDebug() << "m_groupMaps: "; + debug << "m_groupMaps:\n"; for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ ) - qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ); - qDebug() << m_groupHash.value( std::numeric_limits::max() ); + { + debug << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ) << "\n"; + } + + debug << m_groupHash.value( std::numeric_limits::max() ); + + log::debug("{}", s); } diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index e967b27c..0ca39b19 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -150,23 +150,23 @@ void SelfUpdater::testForUpdate() VersionInfo newestVer(newest["tag_name"].toString()); if (newestVer > this->m_MOVersion) { m_UpdateCandidate = newest; - qDebug("update available: %s -> %s", - qUtf8Printable(this->m_MOVersion.displayString(3)), - qUtf8Printable(newestVer.displayString(3))); + log::debug("update available: {} -> {}", + this->m_MOVersion.displayString(3), + newestVer.displayString(3)); emit updateAvailable(); } else if (newestVer < this->m_MOVersion) { // this could happen if the user switches from using prereleases to // stable builds. Should we downgrade? - qDebug("This version is newer than the latest released one: %s -> %s", - qUtf8Printable(this->m_MOVersion.displayString(3)), - qUtf8Printable(newestVer.displayString(3))); + log::debug("This version is newer than the latest released one: {} -> {}", + this->m_MOVersion.displayString(3), + newestVer.displayString(3)); } } }); } //Catch all is bad by design, should be improved catch (...) { - qDebug("Unable to connect to github.com to check version"); + log::debug("Unable to connect to github.com to check version"); } } @@ -230,7 +230,7 @@ void SelfUpdater::closeProgress() void SelfUpdater::openOutputFile(const QString &fileName) { QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName; - qDebug("downloading to %s", qUtf8Printable(outputPath)); + log::debug("downloading to {}", outputPath); m_UpdateFile.setFileName(outputPath); m_UpdateFile.open(QIODevice::WriteOnly); } @@ -312,7 +312,7 @@ void SelfUpdater::downloadFinished() return; } - qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); + log::debug("download: {}", m_UpdateFile.fileName()); try { installUpdate(); diff --git a/src/settings.cpp b/src/settings.cpp index 9c303442..ff5b9976 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -54,7 +54,6 @@ along with Mod Organizer. If not, see . #include #include // for Qt::UserRole, etc -#include // for qDebug, qWarning #include // For ShellExecuteW, HINSTANCE, etc #include // For storage @@ -635,7 +634,7 @@ void Settings::updateServers(const QList &servers) QVariantMap val = m_Settings.value(key).toMap(); QDate lastSeen = val["lastSeen"].toDate(); if (lastSeen.daysTo(now) > 30) { - qDebug("removing server %s since it hasn't been available for downloads in over a month", qUtf8Printable(key)); + log::debug("removing server {} since it hasn't been available for downloads in over a month", key); m_Settings.remove(key); } } @@ -758,10 +757,10 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) { if (first_update) { - qDebug("Changed settings:"); + log::debug("Changed settings:"); first_update = false; } - qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data()); + log::debug(" {}={}", k, m_Settings.value(k).toString()); } m_Settings.endGroup(); } diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 197955b8..5918c8a5 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -60,8 +60,7 @@ LogWorker::LogWorker() "yyyy-MM-dd_hh-mm-ss"))) { m_LogFile.open(QIODevice::WriteOnly); - qDebug("usvfs log messages are written to %s", - qUtf8Printable(m_LogFile.fileName())); + log::debug("usvfs log messages are written to {}", m_LogFile.fileName()); } LogWorker::~LogWorker() @@ -129,7 +128,10 @@ UsvfsConnector::UsvfsConnector() USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); InitLogging(false); - qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, params.crashDumpsPath); + log::debug( + "Initializing VFS <{}, {}, {}, {}>", + params.instanceName, static_cast(params.logLevel), + static_cast(params.crashDumpsType), params.crashDumpsPath); CreateVFS(¶ms); @@ -168,7 +170,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) int files = 0; int dirs = 0; - qDebug("Updating VFS mappings..."); + log::debug("Updating VFS mappings..."); ClearVirtualMappings(); @@ -196,14 +198,7 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } } - qDebug("VFS mappings updated ", dirs, files); - /* - size_t dumpSize = 0; - CreateVFSDump(nullptr, &dumpSize); - std::unique_ptr buffer(new char[dumpSize]); - CreateVFSDump(buffer.get(), &dumpSize); - qDebug(buffer.get()); - */ + log::debug("VFS mappings updated ", dirs, files); } void UsvfsConnector::updateParams( -- cgit v1.3.1 From f49efd6d448dccd4100fa46e2ebf1690d97033cc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 04:54:47 -0400 Subject: replaced formatSystemMessageQ() with formatSystemMessage() replaced windowsErrorString() with formatSystemMessage() --- src/envmetrics.cpp | 6 +++--- src/envmodule.cpp | 14 +++++++------- src/envsecurity.cpp | 20 ++++++++++---------- src/envshortcut.cpp | 4 ++-- src/envwindows.cpp | 4 ++-- src/main.cpp | 2 +- src/mainwindow.cpp | 25 ++++++++++++++++++------- src/organizercore.cpp | 6 ++++-- src/profile.cpp | 7 +++++-- src/settings.cpp | 6 +++--- 10 files changed, 55 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index 784e4baf..b1b9bd2e 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -19,7 +19,7 @@ int getDesktopDpi() if (!dc) { const auto e = GetLastError(); - log::error("can't get desktop DC, {}", formatSystemMessageQ(e)); + log::error("can't get desktop DC, {}", formatSystemMessage(e)); return 0; } @@ -52,7 +52,7 @@ HMONITOR findMonitor(const QString& name) const auto e = GetLastError(); log::error( "GetMonitorInfo() failed for '{}', {}", - data.name, formatSystemMessageQ(e)); + data.name, formatSystemMessage(e)); // error for this monitor, but continue return TRUE; @@ -121,7 +121,7 @@ int getDpi(const QString& monitorDevice) if (FAILED(r)) { log::error( "GetDpiForMonitor() failed for '{}', {}", - monitorDevice, formatSystemMessageQ(r)); + monitorDevice, formatSystemMessage(r)); return 0; } diff --git a/src/envmodule.cpp b/src/envmodule.cpp index aae4e0b1..8cea414a 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -117,7 +117,7 @@ Module::FileInfo Module::getFileInfo() const log::error( "GetFileVersionInfoSizeW() failed on '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -130,7 +130,7 @@ Module::FileInfo Module::getFileInfo() const log::error( "GetFileVersionInfoW() failed on '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -255,7 +255,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const log::error( "can't open file '{}' for timestamp, {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -266,7 +266,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const log::error( "can't get file time for '{}', {}", - m_path, formatSystemMessageQ(e)); + m_path, formatSystemMessage(e)); return {}; } @@ -328,7 +328,7 @@ std::vector getLoadedModules() if (snapshot.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessageQ(e)); + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); return {}; } @@ -339,7 +339,7 @@ std::vector getLoadedModules() if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); - log::error("Module32First() failed, {}", formatSystemMessageQ(e)); + log::error("Module32First() failed, {}", formatSystemMessage(e)); return {}; } @@ -358,7 +358,7 @@ std::vector getLoadedModules() // no more modules is not an error if (e != ERROR_NO_MORE_FILES) { - log::error("Module32Next() failed, {}", formatSystemMessageQ(e)); + log::error("Module32Next() failed, {}", formatSystemMessage(e)); } break; diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 015e4000..376be4df 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -58,7 +58,7 @@ public: } if (FAILED(ret)) { - log::error("enum->next() failed, {}", formatSystemMessageQ(ret)); + log::error("enum->next() failed, {}", formatSystemMessage(ret)); break; } @@ -84,7 +84,7 @@ private: if (FAILED(ret) || !rawLocator) { log::error( "CoCreateInstance for WbemLocator failed, {}", - formatSystemMessageQ(ret)); + formatSystemMessage(ret)); throw failed(); } @@ -104,7 +104,7 @@ private: if (FAILED(res) || !rawService) { log::error( "locator->ConnectServer() failed for namespace '{}', {}", - ns, formatSystemMessageQ(res)); + ns, formatSystemMessage(res)); throw failed(); } @@ -120,7 +120,7 @@ private: if (FAILED(ret)) { - log::error("CoSetProxyBlanket() failed, {}", formatSystemMessageQ(ret)); + log::error("CoSetProxyBlanket() failed, {}", formatSystemMessage(ret)); throw failed(); } } @@ -139,7 +139,7 @@ private: if (FAILED(ret) || !rawEnumerator) { - log::error("query '{}' failed, {}", query, formatSystemMessageQ(ret)); + log::error("query '{}' failed, {}", query, formatSystemMessage(ret)); return {}; } @@ -250,7 +250,7 @@ std::vector getSecurityProductsFromWMI() // display name auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get displayName, {}", formatSystemMessageQ(ret)); + log::error("failed to get displayName, {}", formatSystemMessage(ret)); return; } @@ -265,7 +265,7 @@ std::vector getSecurityProductsFromWMI() // product state ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get productState, {}", formatSystemMessageQ(ret)); + log::error("failed to get productState, {}", formatSystemMessage(ret)); return; } @@ -286,7 +286,7 @@ std::vector getSecurityProductsFromWMI() // guid ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { - log::error("failed to get instanceGuid, {}", formatSystemMessageQ(ret)); + log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); return; } @@ -349,7 +349,7 @@ std::optional getWindowsFirewall() if (FAILED(hr) || !rawPolicy) { log::error( "CoCreateInstance for NetFwPolicy2 failed, {}", - formatSystemMessageQ(hr)); + formatSystemMessage(hr)); return {}; } @@ -363,7 +363,7 @@ std::optional getWindowsFirewall() hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); if (FAILED(hr)) { - log::error("get_FirewallEnabled failed, {}", formatSystemMessageQ(hr)); + log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr)); return {}; } } diff --git a/src/envshortcut.cpp b/src/envshortcut.cpp index 1deb9dad..99495c39 100644 --- a/src/envshortcut.cpp +++ b/src/envshortcut.cpp @@ -100,7 +100,7 @@ private: if (FAILED(r)) { throw ShellLinkException(QString("%1, %2") .arg(s) - .arg(formatSystemMessageQ(r))); + .arg(formatSystemMessage(r))); } } @@ -290,7 +290,7 @@ bool Shortcut::remove(Locations loc) log::error( "failed to remove shortcut '{}', {}", - path, formatSystemMessageQ(e)); + path, formatSystemMessage(e)); return false; } diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 8a98036a..3932a9b5 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -210,7 +210,7 @@ std::optional WindowsInfo::getElevated() const log::error( "while trying to check if process is elevated, " - "OpenProcessToken() failed: {}", formatSystemMessageQ(e)); + "OpenProcessToken() failed: {}", formatSystemMessage(e)); return {}; } @@ -226,7 +226,7 @@ std::optional WindowsInfo::getElevated() const log::error( "while trying to check if process is elevated, " - "GetTokenInformation() failed: {}", formatSystemMessageQ(e)); + "GetTokenInformation() failed: {}", formatSystemMessage(e)); return {}; } diff --git a/src/main.cpp b/src/main.cpp index 5c5ce945..f53a574e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -464,7 +464,7 @@ void preloadDll(const QString& filename) if (!LoadLibraryW(dllPath.toStdWString().c_str())) { const auto e = GetLastError(); - log::warn("failed to load {}: {}", dllPath, formatSystemMessageQ(e)); + log::warn("failed to load {}: {}", dllPath, formatSystemMessage(e)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e502bdb1..8a8a99ef 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4029,7 +4029,8 @@ void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) MessageDialog::showMessage(tr("Move successful."), this); } else { - log::error("Move operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("Move operation failed: {}", formatSystemMessage(e)); } m_OrganizerCore.refreshModList(); @@ -4058,7 +4059,8 @@ void MainWindow::clearOverwrite() updateProblemsButton(); m_OrganizerCore.refreshModList(); } else { - log::error("Delete operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("Delete operation failed: {}", formatSystemMessage(e)); } } } @@ -6819,8 +6821,13 @@ void MainWindow::on_restoreButton_clicked() if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || !shellCopy(lockedName + "." + choice, lockedName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + + const auto e = GetLastError(); + + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); } m_OrganizerCore.refreshESPList(true); } @@ -6841,8 +6848,11 @@ void MainWindow::on_restoreModsButton_clicked() QString choice = queryRestore(modlistName); if (!choice.isEmpty()) { if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + QMessageBox::critical( + this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1") + .arg(formatSystemMessage(e))); } m_OrganizerCore.refreshModList(false); } @@ -6956,7 +6966,8 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m success = shellCopy(file.absoluteFilePath(), target, true, this); } if (!success) { - log::error("file operation failed: {}", windowsErrorString(::GetLastError())); + const auto e = GetLastError(); + log::error("file operation failed: {}", formatSystemMessage(e)); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index f6802673..b61ebde8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -354,7 +354,7 @@ QString OrganizerCore::commitSettings(const QString &iniFile) // make a second attempt using qt functions but if that fails print the // error from the first attempt if (!renameFile(iniFile + ".new", iniFile)) { - return windowsErrorString(err); + return QString::fromStdWString(formatSystemMessage(err)); } } return QString(); @@ -387,10 +387,12 @@ void OrganizerCore::storeSettings() + QString::fromStdWString(AppConfig::iniFileName()); if (QFileInfo(iniFile).exists()) { if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { + const auto e = GetLastError(); QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), tr("An error occurred trying to update MO settings to %1: %2") - .arg(iniFile, windowsErrorString(::GetLastError()))); + .arg(iniFile) + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } } diff --git a/src/profile.cpp b/src/profile.cpp index 27616986..6de1b097 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -265,7 +265,10 @@ void Profile::createTweakedIniFile() QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { - reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); + const auto e = GetLastError(); + reportError( + tr("failed to update tweaked ini file, wrong settings may be used: %1") + .arg(QString::fromStdWString(formatSystemMessage(e)))); return; } @@ -287,7 +290,7 @@ void Profile::createTweakedIniFile() if (error) { const auto e = ::GetLastError(); reportError(tr("failed to create tweaked ini: %1") - .arg(formatSystemMessageQ(e))); + .arg(QString::fromStdWString(formatSystemMessage(e)))); } log::debug("{} saved", QDir::toNativeSeparators(tweakedIni)); diff --git a/src/settings.cpp b/src/settings.cpp index ff5b9976..5ad066b2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -220,7 +220,7 @@ QString Settings::deObfuscate(const QString key) } else { const auto e = GetLastError(); if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessageQ(e)); + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); } } delete[] keyData; @@ -365,7 +365,7 @@ bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessageQ(e)); + log::error("Storing API key failed: {}", formatSystemMessage(e)); return false; } @@ -486,7 +486,7 @@ void Settings::setSteamLogin(QString username, QString password) } if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); - log::error("Storing or deleting password failed: {}", formatSystemMessageQ(e)); + log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); } } -- cgit v1.3.1 From 5304d52f9373e0078674af79b656e2e4d010ca90 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 19 Jul 2019 06:53:52 -0400 Subject: removed some useless logging initializing usvfs logging now logs strings for log level and crash dump type --- src/downloadmanager.cpp | 3 --- src/main.cpp | 2 -- src/organizercore.cpp | 27 ++++++++++--------------- src/usvfsconnector.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 61 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index d2556faa..1f86f9aa 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -378,9 +378,6 @@ void DownloadManager::refreshList() } } - //if (m_ActiveDownloads.size() != downloadsBefore) { - log::debug("Downloads after refresh: {}", m_ActiveDownloads.size()); - //} emit update(-1); //let watcher trigger refreshes again diff --git a/src/main.cpp b/src/main.cpp index f53a574e..0adfc110 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -623,8 +623,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, QImage image(pluginSplash); if (!image.isNull()) { image.save(dataPath + "/splash.png"); - } else { - log::debug("no plugin splash"); } } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index b61ebde8..92372d82 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -77,26 +77,21 @@ CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; static bool isOnline() { - QList interfaces = QNetworkInterface::allInterfaces(); - - bool connected = false; - for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; - ++iter) { - if ((iter->flags() & QNetworkInterface::IsUp) - && (iter->flags() & QNetworkInterface::IsRunning) - && !(iter->flags() & QNetworkInterface::IsLoopBack)) { - auto addresses = iter->addressEntries(); - if (addresses.count() == 0) { - continue; + const auto runningFlags = + QNetworkInterface::IsUp | QNetworkInterface::IsRunning; + + for (auto&& i : QNetworkInterface::allInterfaces()) { + if (!(i.flags() & QNetworkInterface::IsLoopBack)) { + if (i.flags() & runningFlags) { + auto addresses = i.addressEntries(); + if (!addresses.empty()) { + return true; + } } - log::debug("interface {} seems to be up (address: {})", - iter->humanReadableName(), - addresses[0].ip().toString()); - connected = true; } } - return connected; + return false; } static bool renameFile(const QString &oldName, const QString &newName, diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 5918c8a5..b5e6edb1 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -118,6 +118,48 @@ CrashDumpsType crashDumpsType(int type) } } +QString toString(LogLevel lv) +{ + switch (lv) + { + case LogLevel::Debug: + return "debug"; + + case LogLevel::Info: + return "info"; + + case LogLevel::Warning: + return "warning"; + + case LogLevel::Error: + return "error"; + + default: + return QString("%1").arg(static_cast(lv)); + } +} + +QString toString(CrashDumpsType t) +{ + switch (t) + { + case CrashDumpsType::None: + return "none"; + + case CrashDumpsType::Mini: + return "mini"; + + case CrashDumpsType::Data: + return "data"; + + case CrashDumpsType::Full: + return "full"; + + default: + return QString("%1").arg(static_cast(t)); + } +} + UsvfsConnector::UsvfsConnector() { USVFSParameters params; @@ -129,9 +171,14 @@ UsvfsConnector::UsvfsConnector() InitLogging(false); log::debug( - "Initializing VFS <{}, {}, {}, {}>", - params.instanceName, static_cast(params.logLevel), - static_cast(params.crashDumpsType), params.crashDumpsPath); + "initializing usvfs:\n" + " . instance: {}\n" + " . log: {}\n" + " . dump: {} ({})", + params.instanceName, + toString(params.logLevel), + params.crashDumpsPath, + toString(params.crashDumpsType)); CreateVFS(¶ms); -- cgit v1.3.1 From 3c7712a32dd5079a9543485b6a85d548460faefd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 05:34:31 -0400 Subject: moved setLogLevel() to OrganizerCore moved context menu to LogList --- src/loglist.cpp | 61 +++++++++++++++++++++++++++++++++++++++++++-------- src/loglist.h | 16 +++++++++----- src/mainwindow.cpp | 57 ++--------------------------------------------- src/mainwindow.h | 4 ---- src/mainwindow.ui | 14 ------------ src/organizercore.cpp | 12 ++++++++++ src/organizercore.h | 2 ++ 7 files changed, 78 insertions(+), 88 deletions(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index c34ac76e..26aea682 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -18,14 +18,7 @@ along with Mod Organizer. If not, see . */ #include "loglist.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include "organizercore.h" using namespace MOBase; @@ -158,7 +151,7 @@ QVariant LogModel::headerData(int, Qt::Orientation, int) const LogList::LogList(QWidget* parent) - : QTreeView(parent) + : QTreeView(parent), m_core(nullptr) { setModel(&LogModel::instance()); @@ -171,6 +164,10 @@ LogList::LogList(QWidget* parent) setAutoScroll(true); scrollToBottom(); + connect( + this, &QWidget::customContextMenuRequested, + [&](auto&& pos){ onContextMenu(pos); }); + connect( model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(scrollToBottom())); @@ -180,6 +177,11 @@ LogList::LogList(QWidget* parent) this, SLOT(scrollToBottom())); } +void LogList::setCore(OrganizerCore& core) +{ + m_core = &core; +} + void LogList::copyToClipboard() { std::string s; @@ -196,3 +198,44 @@ void LogList::copyToClipboard() QApplication::clipboard()->setText(QString::fromStdString(s)); } + +QMenu* LogList::createMenu(QWidget* parent) +{ + auto* menu = new QMenu(parent); + + menu->addAction(tr("Copy& Log"), [&]{ copyToClipboard(); }); + menu->addSeparator(); + + auto* levels = new QMenu(tr("&Level")); + menu->addMenu(levels); + + auto* ag = new QActionGroup(menu); + + auto addAction = [&](auto&& text, auto&& level) { + auto* a = new QAction(text, ag); + + a->setCheckable(true); + a->setChecked(log::getDefault().level() == level); + + connect(a, &QAction::triggered, [this, level]{ + if (m_core) { + m_core->setLogLevel(level); + } + }); + + levels->addAction(a); + }; + + addAction(tr("&Debug"), log::Debug); + addAction(tr("&Info"), log::Info); + addAction(tr("&Warnings"), log::Warning); + addAction(tr("&Errors"), log::Error); + + return menu; +} + +void LogList::onContextMenu(const QPoint& pos) +{ + auto* menu = createMenu(this); + menu->popup(viewport()->mapToGlobal(pos)); +} diff --git a/src/loglist.h b/src/loglist.h index d1f7a2ad..ae827ca7 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -20,14 +20,11 @@ along with Mod Organizer. If not, see . #ifndef LOGBUFFER_H #define LOGBUFFER_H -#include -#include -#include -#include -#include -#include +#include #include +class OrganizerCore; + class LogModel : public QAbstractItemModel { Q_OBJECT @@ -65,7 +62,14 @@ class LogList : public QTreeView public: LogList(QWidget* parent=nullptr); + void setCore(OrganizerCore& core); + void copyToClipboard(); + QMenu* createMenu(QWidget* parent=nullptr); + +private: + OrganizerCore* m_core; + void onContextMenu(const QPoint& pos); }; #endif // LOGBUFFER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8a8a99ef..761e9843 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,7 +59,6 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" #include "lockeddialog.h" #include "waitingonclosedialog.h" -#include "loglist.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" @@ -358,7 +357,7 @@ MainWindow::MainWindow(QSettings &initSettings m_CategoryFactory.loadCategories(); - setupLogMenu(); + ui->logList->setCore(m_OrganizerCore); int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); @@ -813,36 +812,6 @@ void MainWindow::setupActionMenu(QAction* a) tb->setPopupMode(QToolButton::InstantPopup); } -void MainWindow::setupLogMenu() -{ - connect(ui->logList, &QWidget::customContextMenuRequested, [&](auto&& pos){ - auto* menu = new QMenu(ui->logList); - - menu->addAction(tr("Copy& Log"), [&]{ ui->logList->copyToClipboard(); }); - menu->addSeparator(); - - auto* levels = new QMenu(tr("&Level")); - menu->addMenu(levels); - - auto* ag = new QActionGroup(menu); - - auto addAction = [&](auto&& text, auto&& level) { - auto* a = new QAction(text, ag); - a->setCheckable(true); - a->setChecked(log::getDefault().level() == level); - connect(a, &QAction::triggered, [this, level]{ setLogLevel(level); }); - levels->addAction(a); - }; - - addAction(tr("&Debug"), log::Debug); - addAction(tr("&Info"), log::Info); - addAction(tr("&Warnings"), log::Warning); - addAction(tr("&Errors"), log::Error); - - menu->popup(ui->logList->viewport()->mapToGlobal(pos)); - }); -} - void MainWindow::updatePinnedExecutables() { for (auto* a : ui->toolBar->actions()) { @@ -1432,11 +1401,6 @@ bool MainWindow::confirmExit() void MainWindow::cleanup() { - if (ui->logList->model() != nullptr) { - disconnect(ui->logList->model(), nullptr, nullptr, nullptr); - ui->logList->setModel(nullptr); - } - QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); m_IntegratedBrowser.close(); m_SaveMetaTimer.stop(); @@ -5317,24 +5281,12 @@ void MainWindow::on_actionSettings_triggered() m_statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - setLogLevel(settings.logLevel()); + m_OrganizerCore.setLogLevel(settings.logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); } -void MainWindow::setLogLevel(log::Levels level) -{ - auto& s = m_OrganizerCore.settings(); - - s.setLogLevel(level); - - m_OrganizerCore.updateVFSParams( - s.logLevel(), s.crashDumpsType(), s.executablesBlacklist()); - - log::getDefault().setLevel(s.logLevel()); -} - void MainWindow::on_actionNexus_triggered() { const IPluginGame *game = m_OrganizerCore.managedGame(); @@ -6858,11 +6810,6 @@ void MainWindow::on_restoreModsButton_clicked() } } -void MainWindow::on_actionLogCopy_triggered() -{ - ui->logList->copyToClipboard(); -} - void MainWindow::on_categoriesAndBtn_toggled(bool checked) { if (checked) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 74993667..aa49205d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -636,13 +636,10 @@ private slots: void search_activated(); void searchClear_activated(); - void setupLogMenu(); void resetActionIcons(); void updateModCount(); void updatePluginCount(); - void setLogLevel(MOBase::log::Levels level); - private slots: // ui slots // actions void on_actionAdd_Profile_triggered(); @@ -696,7 +693,6 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_actionLogCopy_triggered(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index fc2bcdd3..6c6d0bca 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1757,20 +1757,6 @@ p, li { white-space: pre-wrap; } Log - - - Copy &Log - - - Copy &Log - - - Copy log to clipboard - - - Copy log to clipboard - - diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 92372d82..1e164525 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -731,6 +731,18 @@ void OrganizerCore::updateVFSParams( m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); } +void OrganizerCore::setLogLevel(log::Levels level) +{ + m_Settings.setLogLevel(level); + + updateVFSParams( + m_Settings.logLevel(), + m_Settings.crashDumpsType(), + m_Settings.executablesBlacklist()); + + log::getDefault().setLevel(m_Settings.logLevel()); +} + bool OrganizerCore::cycleDiagnostics() { if (int maxDumps = settings().crashDumpsMax()) removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); diff --git a/src/organizercore.h b/src/organizercore.h index c368d101..2aa7e707 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -196,6 +196,8 @@ public: MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist); + void setLogLevel(MOBase::log::Levels level); + bool cycleDiagnostics(); static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } -- cgit v1.3.1 From 6f1a8f5b7af3018b38fd40375d46776b2eefe684 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 05:40:09 -0400 Subject: added clear option to log list --- src/loglist.cpp | 15 ++++++++++++++- src/loglist.h | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index 26aea682..01cbf6ce 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -45,6 +45,13 @@ void LogModel::add(MOBase::log::Entry e) emit entryAdded(std::move(e)); } +void LogModel::clear() +{ + beginResetModel(); + m_entries.clear(); + endResetModel(); +} + const std::deque& LogModel::entries() const { return m_entries; @@ -199,12 +206,18 @@ void LogList::copyToClipboard() QApplication::clipboard()->setText(QString::fromStdString(s)); } +void LogList::clear() +{ + static_cast(model())->clear(); +} + QMenu* LogList::createMenu(QWidget* parent) { auto* menu = new QMenu(parent); - menu->addAction(tr("Copy& Log"), [&]{ copyToClipboard(); }); + menu->addAction(tr("&Copy"), [&]{ copyToClipboard(); }); menu->addSeparator(); + menu->addAction(tr("C&lear"), [&]{ clear(); }); auto* levels = new QMenu(tr("&Level")); menu->addMenu(levels); diff --git a/src/loglist.h b/src/loglist.h index ae827ca7..0b25dfd1 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -34,6 +34,8 @@ public: static LogModel& instance(); void add(MOBase::log::Entry e); + void clear(); + const std::deque& entries() const; protected: @@ -65,6 +67,8 @@ public: void setCore(OrganizerCore& core); void copyToClipboard(); + void clear(); + QMenu* createMenu(QWidget* parent=nullptr); private: -- cgit v1.3.1 From 9aa7a5a36682bf706ddb7cdcba2e9ea0bb23c5dc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 05:44:44 -0400 Subject: renamed log actions --- src/loglist.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index 01cbf6ce..192913b6 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -215,9 +215,9 @@ QMenu* LogList::createMenu(QWidget* parent) { auto* menu = new QMenu(parent); - menu->addAction(tr("&Copy"), [&]{ copyToClipboard(); }); + menu->addAction(tr("&Copy all"), [&]{ copyToClipboard(); }); menu->addSeparator(); - menu->addAction(tr("C&lear"), [&]{ clear(); }); + menu->addAction(tr("C&lear all"), [&]{ clear(); }); auto* levels = new QMenu(tr("&Level")); menu->addMenu(levels); -- cgit v1.3.1 From e4cf2c314d6397c5d73bcf567d4420171238bd29 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 1 Aug 2019 22:50:42 -0400 Subject: missing bracket --- src/settingsdialog.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 99943d04..0dae31ac 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -485,6 +485,7 @@ void SettingsDialog::onValidatorStateChanged( for (auto&& line : log.split("\n")) { addNexusLog(line); } + } updateNexusState(); } -- cgit v1.3.1 From 6b5c9675ae1e8b343dcbc9192d43c63e482a30bd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 08:43:46 -0400 Subject: moved SettingsTab out of Settings split general tab --- src/CMakeLists.txt | 3 + src/settings.cpp | 204 ++++--------------------------- src/settings.h | 65 ++++------ src/settingsdialog.cpp | 105 ---------------- src/settingsdialog.h | 63 +++------- src/settingsdialoggeneral.cpp | 274 ++++++++++++++++++++++++++++++++++++++++++ src/settingsdialoggeneral.h | 53 ++++++++ 7 files changed, 392 insertions(+), 375 deletions(-) create mode 100644 src/settingsdialoggeneral.cpp create mode 100644 src/settingsdialoggeneral.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9785dc3d..29c419b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,7 @@ SET(organizer_SRCS spawn.cpp singleinstance.cpp settingsdialog.cpp + settingsdialoggeneral.cpp settings.cpp selfupdater.cpp selectiondialog.cpp @@ -151,6 +152,7 @@ SET(organizer_HDRS spawn.h singleinstance.h settingsdialog.h + settingsdialoggeneral.h settings.h selfupdater.h selectiondialog.h @@ -431,6 +433,7 @@ set(profiles set(settings settings settingsdialog + settingsdialoggeneral ) set(utilities diff --git a/src/settings.cpp b/src/settings.cpp index 5ad066b2..0911b155 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "pluginsetting.h" #include "serverinfo.h" #include "settingsdialog.h" +#include "settingsdialoggeneral.h" #include "versioninfo.h" #include "appconfig.h" #include "organizercore.h" @@ -81,6 +82,23 @@ private: }; +SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : m_parent(m_parent) + , m_Settings(m_parent->settingsRef()) + , m_dialog(m_dialog) + , ui(m_dialog.ui) +{ +} + +SettingsTab::~SettingsTab() +{} + +QWidget* SettingsTab::parentWidget() +{ + return &m_dialog; +} + + Settings *Settings::s_Instance = nullptr; @@ -663,63 +681,9 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } -void Settings::addLanguages(QComboBox *languageBox) -{ - std::vector> languages; - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); - QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; - QRegExp exp(pattern); - while (langIter.hasNext()) { - langIter.next(); - QString file = langIter.fileName(); - if (exp.exactMatch(file)) { - QString languageCode = exp.cap(1); - QLocale locale(languageCode); - QString languageString = QString("%1 (%2)").arg(locale.nativeLanguageName()).arg(locale.nativeCountryName()); //QLocale::languageToString(locale.language()); - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (traditional)"; - } else { - languageString = "Chinese (simplified)"; - } - } - languages.push_back(std::make_pair(QString("%1").arg(languageString), exp.cap(1))); - //languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); - } - } - if (!languageBox->findText("English")) { - languages.push_back(std::make_pair(QString("English"), QString("en_US"))); - //languageBox->addItem("English", "en_US"); - } - std::sort(languages.begin(), languages.end()); - for (const auto &lang : languages) { - languageBox->addItem(lang.first, lang.second); - } -} - -void Settings::addStyles(QComboBox *styleBox) -{ - styleBox->addItem("None", ""); - styleBox->addItem("Fusion", "Fusion"); - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); - while (langIter.hasNext()) { - langIter.next(); - QString style = langIter.fileName(); - styleBox->addItem(style, style); - } -} - -void Settings::resetDialogs() -{ - QuestionBoxMemory::resetDialogs(); -} - void Settings::query(PluginContainer *pluginContainer, QWidget *parent) { SettingsDialog dialog(pluginContainer, this, parent); - connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); std::vector> tabs; @@ -787,128 +751,6 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) } -Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : m_parent(m_parent) - , m_Settings(m_parent->m_Settings) - , m_dialog(m_dialog) -{ -} - -Settings::SettingsTab::~SettingsTab() -{} - -Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) - : Settings::SettingsTab(m_parent, m_dialog) - , m_languageBox(m_dialog.findChild("languageBox")) - , m_styleBox(m_dialog.findChild("styleBox")) - , m_compactBox(m_dialog.findChild("compactBox")) - , m_showMetaBox(m_dialog.findChild("showMetaBox")) - , m_usePrereleaseBox(m_dialog.findChild("usePrereleaseBox")) - , m_overwritingBtn(m_dialog.findChild("overwritingBtn")) - , m_overwrittenBtn(m_dialog.findChild("overwrittenBtn")) - , m_overwritingArchiveBtn(m_dialog.findChild("overwritingArchiveBtn")) - , m_overwrittenArchiveBtn(m_dialog.findChild("overwrittenArchiveBtn")) - , m_containsBtn(m_dialog.findChild("containsBtn")) - , m_containedBtn(m_dialog.findChild("containedBtn")) - , m_colorSeparatorsBox(m_dialog.findChild("colorSeparatorsBox")) -{ - // FIXME I think 'addLanguages' lives in here not in parent - m_parent->addLanguages(m_languageBox); - { - QString languageCode = m_parent->language(); - int currentID = m_languageBox->findData(languageCode); - // I made a mess. :( Most languages are stored with only the iso country - // code (2 characters like "de") but chinese - // with the exact language variant (zh_TW) so I have to search for both - // variants - if (currentID == -1) { - currentID = m_languageBox->findData(languageCode.mid(0, 2)); - } - if (currentID != -1) { - m_languageBox->setCurrentIndex(currentID); - } - } - - // FIXME I think addStyles lives in here not in parent - m_parent->addStyles(m_styleBox); - { - int currentID = m_styleBox->findData( - m_Settings.value("Settings/style", "").toString()); - if (currentID != -1) { - m_styleBox->setCurrentIndex(currentID); - } - } - /* verision using palette only works with fusion theme for some stupid reason... - m_overwritingBtn->setAutoFillBackground(true); - m_overwrittenBtn->setAutoFillBackground(true); - m_containsBtn->setAutoFillBackground(true); - m_containedBtn->setAutoFillBackground(true); - m_overwritingBtn->setPalette(QPalette(m_parent->modlistOverwritingLooseColor())); - m_overwrittenBtn->setPalette(QPalette(m_parent->modlistOverwrittenLooseColor())); - m_containsBtn->setPalette(QPalette(m_parent->modlistContainsPluginColor())); - m_containedBtn->setPalette(QPalette(m_parent->pluginListContainedColor())); - QPalette palette1 = m_overwritingBtn->palette(); - QPalette palette2 = m_overwrittenBtn->palette(); - QPalette palette3 = m_containsBtn->palette(); - QPalette palette4 = m_containedBtn->palette(); - palette1.setColor(QPalette::Background, m_parent->modlistOverwritingLooseColor()); - palette2.setColor(QPalette::Background, m_parent->modlistOverwrittenLooseColor()); - palette3.setColor(QPalette::Background, m_parent->modlistContainsPluginColor()); - palette4.setColor(QPalette::Background, m_parent->pluginListContainedColor()); - m_overwritingBtn->setPalette(palette1); - m_overwrittenBtn->setPalette(palette2); - m_containsBtn->setPalette(palette3); - m_containedBtn->setPalette(palette4); - */ - - //version with stylesheet - m_dialog.setButtonColor(m_overwritingBtn, m_parent->modlistOverwritingLooseColor()); - m_dialog.setButtonColor(m_overwrittenBtn, m_parent->modlistOverwrittenLooseColor()); - m_dialog.setButtonColor(m_overwritingArchiveBtn, m_parent->modlistOverwritingArchiveColor()); - m_dialog.setButtonColor(m_overwrittenArchiveBtn, m_parent->modlistOverwrittenArchiveColor()); - m_dialog.setButtonColor(m_containsBtn, m_parent->modlistContainsPluginColor()); - m_dialog.setButtonColor(m_containedBtn, m_parent->pluginListContainedColor()); - - m_dialog.setOverwritingColor(m_parent->modlistOverwritingLooseColor()); - m_dialog.setOverwrittenColor(m_parent->modlistOverwrittenLooseColor()); - m_dialog.setOverwritingArchiveColor(m_parent->modlistOverwritingArchiveColor()); - m_dialog.setOverwrittenArchiveColor(m_parent->modlistOverwrittenArchiveColor()); - m_dialog.setContainsColor(m_parent->modlistContainsPluginColor()); - m_dialog.setContainedColor(m_parent->pluginListContainedColor()); - - m_compactBox->setChecked(m_parent->compactDownloads()); - m_showMetaBox->setChecked(m_parent->metaDownloads()); - m_usePrereleaseBox->setChecked(m_parent->usePrereleases()); - m_colorSeparatorsBox->setChecked(m_parent->colorSeparatorScrollbar()); -} - -void Settings::GeneralTab::update() -{ - QString oldLanguage = m_parent->language(); - QString newLanguage = m_languageBox->itemData(m_languageBox->currentIndex()).toString(); - if (newLanguage != oldLanguage) { - m_Settings.setValue("Settings/language", newLanguage); - emit m_parent->languageChanged(newLanguage); - } - - QString oldStyle = m_Settings.value("Settings/style", "").toString(); - QString newStyle = m_styleBox->itemData(m_styleBox->currentIndex()).toString(); - if (oldStyle != newStyle) { - m_Settings.setValue("Settings/style", newStyle); - emit m_parent->styleChanged(newStyle); - } - - m_Settings.setValue("Settings/overwritingLooseFilesColor", m_dialog.getOverwritingColor()); - m_Settings.setValue("Settings/overwrittenLooseFilesColor", m_dialog.getOverwrittenColor()); - m_Settings.setValue("Settings/overwritingArchiveFilesColor", m_dialog.getOverwritingArchiveColor()); - m_Settings.setValue("Settings/overwrittenArchiveFilesColor", m_dialog.getOverwrittenArchiveColor()); - m_Settings.setValue("Settings/containsPluginColor", m_dialog.getContainsColor()); - m_Settings.setValue("Settings/containedColor", m_dialog.getContainedColor()); - m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked()); - m_Settings.setValue("Settings/use_prereleases", m_usePrereleaseBox->isChecked()); - m_Settings.setValue("Settings/colorSeparatorScrollbars", m_colorSeparatorsBox->isChecked()); -} Settings::PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) : SettingsTab(parent, dialog) @@ -991,7 +833,7 @@ void Settings::PathsTab::update() } Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_dialog) - : Settings::SettingsTab(m_parent, m_dialog) + : SettingsTab(m_parent, m_dialog) , m_logLevelBox(m_dialog.findChild("logLevelBox")) , m_dumpsTypeBox(m_dialog.findChild("dumpsTypeBox")) , m_dumpsMaxEdit(m_dialog.findChild("dumpsMaxEdit")) @@ -1036,7 +878,7 @@ void Settings::DiagnosticsTab::setLevelsBox() } Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) - : Settings::SettingsTab(parent, dialog) + : SettingsTab(parent, dialog) , m_offlineBox(dialog.findChild("offlineBox")) , m_proxyBox(dialog.findChild("proxyBox")) , m_knownServersList(dialog.findChild("knownServersList")) @@ -1114,7 +956,7 @@ void Settings::NexusTab::update() } Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) - : Settings::SettingsTab(m_parent, m_dialog) + : SettingsTab(m_parent, m_dialog) , m_steamUserEdit(m_dialog.findChild("steamUserEdit")) , m_steamPassEdit(m_dialog.findChild("steamPassEdit")) { @@ -1134,7 +976,7 @@ void Settings::SteamTab::update() } Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) - : Settings::SettingsTab(m_parent, m_dialog) + : SettingsTab(m_parent, m_dialog) , m_pluginsList(m_dialog.findChild("pluginsList")) , m_pluginBlacklistList(m_dialog.findChild("pluginBlacklist")) { @@ -1181,7 +1023,7 @@ void Settings::PluginsTab::update() Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, SettingsDialog &m_dialog) - : Settings::SettingsTab(m_parent, m_dialog) + : SettingsTab(m_parent, m_dialog) , m_appIDEdit(m_dialog.findChild("appIDEdit")) , m_mechanismBox(m_dialog.findChild("mechanismBox")) , m_hideUncheckedBox(m_dialog.findChild("hideUncheckedBox")) diff --git a/src/settings.h b/src/settings.h index c66eb94c..e88080ba 100644 --- a/src/settings.h +++ b/src/settings.h @@ -55,8 +55,30 @@ namespace MOBase { class IPluginGame; } +namespace Ui { + class SettingsDialog; +} + class SettingsDialog; class PluginContainer; +class Settings; + +class SettingsTab +{ +public: + SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + virtual ~SettingsTab(); + + virtual void update() = 0; + +protected: + Settings *m_parent; + QSettings &m_Settings; + SettingsDialog &m_dialog; + Ui::SettingsDialog* ui; + + QWidget* parentWidget(); +}; /** * manages the settings for Mod Organizer. The settings are not cached @@ -404,6 +426,8 @@ public: */ bool colorSeparatorScrollbar() const; + QSettings& settingsRef() { return m_Settings; } + public slots: void managedGameChanged(MOBase::IPluginGame const *gamePlugin); @@ -414,49 +438,10 @@ private: static bool obfuscate(const QString key, const QString data); static QString deObfuscate(const QString key); - void addLanguages(QComboBox *languageBox); - void addStyles(QComboBox *styleBox); void readPluginBlacklist(); void writePluginBlacklist(); QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - class SettingsTab - { - public: - SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); - virtual ~SettingsTab(); - - virtual void update() = 0; - - protected: - Settings *m_parent; - QSettings &m_Settings; - SettingsDialog &m_dialog; - - }; - - /** Display/store the configuration in the 'general' tab of the settings dialogue */ - class GeneralTab : public SettingsTab - { - public: - GeneralTab(Settings *m_parent, SettingsDialog &m_dialog); - - void update(); - - private: - QComboBox *m_languageBox; - QComboBox *m_styleBox; - QCheckBox *m_compactBox; - QCheckBox *m_showMetaBox; - QCheckBox *m_usePrereleaseBox; - QPushButton *m_overwritingBtn; - QPushButton *m_overwrittenBtn; - QPushButton *m_overwritingArchiveBtn; - QPushButton *m_overwrittenArchiveBtn; - QPushButton *m_containsBtn; - QPushButton *m_containedBtn; - QCheckBox *m_colorSeparatorsBox; - }; class PathsTab : public SettingsTab { @@ -554,8 +539,6 @@ private: private slots: - void resetDialogs(); - signals: void languageChanged(const QString &newLanguage); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 0dae31ac..f922cfb9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -131,23 +131,6 @@ QString SettingsDialog::getColoredButtonStyleSheet() const "}"); } -void SettingsDialog::setButtonColor(QPushButton *button, const QColor &color) -{ - button->setStyleSheet( - QString("QPushButton {" - "background-color: rgba(%1, %2, %3, %4);" - "color: %5;" - "border: 1px solid;" - "padding: 3px;" - "}") - .arg(color.red()) - .arg(color.green()) - .arg(color.blue()) - .arg(color.alpha()) - .arg(Settings::getIdealTextColor(color).name()) - ); -}; - void SettingsDialog::accept() { QString newModPath = ui->modDirEdit->text(); @@ -181,14 +164,6 @@ bool SettingsDialog::getApiKeyChanged() return m_keyChanged; } -void SettingsDialog::on_categoriesBtn_clicked() -{ - CategoriesDialog dialog(this); - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - void SettingsDialog::on_execBlacklistBtn_clicked() { bool ok = false; @@ -299,86 +274,6 @@ void SettingsDialog::on_browseGameDirBtn_clicked() } } -void SettingsDialog::on_containsBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_ContainsColor, this, "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_ContainsColor = result; - setButtonColor(ui->containsBtn, result); - } -} - -void SettingsDialog::on_containedBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_ContainedColor, this, "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_ContainedColor = result; - setButtonColor(ui->containedBtn, result); - } -} - -void SettingsDialog::on_overwrittenBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_OverwrittenColor, this, "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_OverwrittenColor = result; - setButtonColor(ui->overwrittenBtn, result); - } -} - -void SettingsDialog::on_overwritingBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_OverwritingColor, this, "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_OverwritingColor = result; - setButtonColor(ui->overwritingBtn, result); - } -} - -void SettingsDialog::on_overwrittenArchiveBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, this, "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_OverwrittenArchiveColor = result; - setButtonColor(ui->overwrittenArchiveBtn, result); - } -} - -void SettingsDialog::on_overwritingArchiveBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, this, "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_OverwritingArchiveColor = result; - setButtonColor(ui->overwritingArchiveBtn, result); - } -} - -void SettingsDialog::on_resetColorsBtn_clicked() -{ - m_OverwritingColor = QColor(255, 0, 0, 64); - m_OverwrittenColor = QColor(0, 255, 0, 64); - m_OverwritingArchiveColor = QColor(255, 0, 255, 64); - m_OverwrittenArchiveColor = QColor(0, 255, 255, 64); - m_ContainsColor = QColor(0, 0, 255, 64); - m_ContainedColor = QColor(0, 0, 255, 64); - - setButtonColor(ui->overwritingBtn, m_OverwritingColor); - setButtonColor(ui->overwrittenBtn, m_OverwrittenColor); - setButtonColor(ui->overwritingArchiveBtn, m_OverwritingArchiveColor); - setButtonColor(ui->overwrittenArchiveBtn, m_OverwrittenArchiveColor); - setButtonColor(ui->containsBtn, m_ContainsColor); - setButtonColor(ui->containedBtn, m_ContainedColor); -} - -void SettingsDialog::on_resetDialogsButton_clicked() -{ - if (QMessageBox::question(this, tr("Confirm?"), - tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit resetDialogs(); - } -} - void SettingsDialog::on_nexusConnect_clicked() { if (m_nexusLogin && m_nexusLogin->isActive()) { diff --git a/src/settingsdialog.h b/src/settingsdialog.h index c5f487fd..01a0afa2 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -54,7 +54,7 @@ public: */ QString getColoredButtonStyleSheet() const; - void setButtonColor(QPushButton *button, const QColor &color); + Ui::SettingsDialog *ui; public slots: @@ -62,7 +62,6 @@ public slots: signals: - void resetDialogs(); void retryApiConnection(); private: @@ -71,73 +70,41 @@ private: void normalizePath(QLineEdit *lineEdit); public: - - QColor getOverwritingColor() { return m_OverwritingColor; } - QColor getOverwrittenColor() { return m_OverwrittenColor; } - QColor getOverwritingArchiveColor() { return m_OverwritingArchiveColor; } - QColor getOverwrittenArchiveColor() { return m_OverwrittenArchiveColor; } - QColor getContainsColor() { return m_ContainsColor; } - QColor getContainedColor() { return m_ContainedColor; } QString getExecutableBlacklist() { return m_ExecutableBlacklist; } + void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; } bool getResetGeometries(); bool getApiKeyChanged(); - void setOverwritingColor(QColor col) { m_OverwritingColor = col; } - void setOverwrittenColor(QColor col) { m_OverwrittenColor = col; } - void setOverwritingArchiveColor(QColor col) { m_OverwritingArchiveColor = col; } - void setOverwrittenArchiveColor(QColor col) { m_OverwrittenArchiveColor = col; } - void setContainsColor(QColor col) { m_ContainsColor = col; } - void setContainedColor(QColor col) { m_ContainedColor = col; } - void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; } - - private slots: - void on_categoriesBtn_clicked(); - void on_execBlacklistBtn_clicked(); - void on_bsaDateBtn_clicked(); - void on_browseDownloadDirBtn_clicked(); - void on_browseModDirBtn_clicked(); - void on_browseCacheDirBtn_clicked(); - void on_resetDialogsButton_clicked(); - void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_associateButton_clicked(); - void on_clearCacheButton_clicked(); - void on_nexusDisconnect_clicked(); + void on_baseDirEdit_editingFinished(); void on_browseBaseDirBtn_clicked(); + void on_browseCacheDirBtn_clicked(); + void on_browseDownloadDirBtn_clicked(); + void on_browseGameDirBtn_clicked(); + void on_browseModDirBtn_clicked(); void on_browseOverwriteDirBtn_clicked(); void on_browseProfilesDirBtn_clicked(); - void on_browseGameDirBtn_clicked(); - void on_overwritingBtn_clicked(); - void on_overwrittenBtn_clicked(); - void on_overwritingArchiveBtn_clicked(); - void on_overwrittenArchiveBtn_clicked(); - void on_containsBtn_clicked(); - void on_containedBtn_clicked(); - void on_resetColorsBtn_clicked(); - void on_baseDirEdit_editingFinished(); + void on_bsaDateBtn_clicked(); + void on_cacheDirEdit_editingFinished(); + void on_clearCacheButton_clicked(); void on_downloadDirEdit_editingFinished(); + void on_execBlacklistBtn_clicked(); void on_modDirEdit_editingFinished(); - void on_cacheDirEdit_editingFinished(); - void on_profilesDirEdit_editingFinished(); - void on_overwriteDirEdit_editingFinished(); void on_nexusConnect_clicked(); + void on_nexusDisconnect_clicked(); void on_nexusManualKey_clicked(); + void on_overwriteDirEdit_editingFinished(); + void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_profilesDirEdit_editingFinished(); void on_resetGeometryBtn_clicked(); void deleteBlacklistItem(); private: - Ui::SettingsDialog *ui; Settings* m_settings; PluginContainer *m_PluginContainer; - QColor m_OverwritingColor; - QColor m_OverwrittenColor; - QColor m_OverwritingArchiveColor; - QColor m_OverwrittenArchiveColor; - QColor m_ContainsColor; - QColor m_ContainedColor; - bool m_GeometriesReset; bool m_keyChanged; diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp new file mode 100644 index 00000000..b22b04fd --- /dev/null +++ b/src/settingsdialoggeneral.cpp @@ -0,0 +1,274 @@ +#include "settingsdialoggeneral.h" +#include "ui_settingsdialog.h" +#include "appconfig.h" +#include "categoriesdialog.h" +#include + +using MOBase::QuestionBoxMemory; + +GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) + : SettingsTab(m_parent, m_dialog) +{ + addLanguages(); + { + QString languageCode = m_parent->language(); + int currentID = ui->languageBox->findData(languageCode); + // I made a mess. :( Most languages are stored with only the iso country + // code (2 characters like "de") but chinese + // with the exact language variant (zh_TW) so I have to search for both + // variants + if (currentID == -1) { + currentID = ui->languageBox->findData(languageCode.mid(0, 2)); + } + if (currentID != -1) { + ui->languageBox->setCurrentIndex(currentID); + } + } + + addStyles(); + { + int currentID = ui->styleBox->findData( + m_Settings.value("Settings/style", "").toString()); + if (currentID != -1) { + ui->styleBox->setCurrentIndex(currentID); + } + } + /* verision using palette only works with fusion theme for some stupid reason... + m_overwritingBtn->setAutoFillBackground(true); + m_overwrittenBtn->setAutoFillBackground(true); + m_containsBtn->setAutoFillBackground(true); + m_containedBtn->setAutoFillBackground(true); + m_overwritingBtn->setPalette(QPalette(m_parent->modlistOverwritingLooseColor())); + m_overwrittenBtn->setPalette(QPalette(m_parent->modlistOverwrittenLooseColor())); + m_containsBtn->setPalette(QPalette(m_parent->modlistContainsPluginColor())); + m_containedBtn->setPalette(QPalette(m_parent->pluginListContainedColor())); + QPalette palette1 = m_overwritingBtn->palette(); + QPalette palette2 = m_overwrittenBtn->palette(); + QPalette palette3 = m_containsBtn->palette(); + QPalette palette4 = m_containedBtn->palette(); + palette1.setColor(QPalette::Background, m_parent->modlistOverwritingLooseColor()); + palette2.setColor(QPalette::Background, m_parent->modlistOverwrittenLooseColor()); + palette3.setColor(QPalette::Background, m_parent->modlistContainsPluginColor()); + palette4.setColor(QPalette::Background, m_parent->pluginListContainedColor()); + m_overwritingBtn->setPalette(palette1); + m_overwrittenBtn->setPalette(palette2); + m_containsBtn->setPalette(palette3); + m_containedBtn->setPalette(palette4); + */ + + //version with stylesheet + setButtonColor(ui->overwritingBtn, m_parent->modlistOverwritingLooseColor()); + setButtonColor(ui->overwrittenBtn, m_parent->modlistOverwrittenLooseColor()); + setButtonColor(ui->overwritingArchiveBtn, m_parent->modlistOverwritingArchiveColor()); + setButtonColor(ui->overwrittenArchiveBtn, m_parent->modlistOverwrittenArchiveColor()); + setButtonColor(ui->containsBtn, m_parent->modlistContainsPluginColor()); + setButtonColor(ui->containedBtn, m_parent->pluginListContainedColor()); + + setOverwritingColor(m_parent->modlistOverwritingLooseColor()); + setOverwrittenColor(m_parent->modlistOverwrittenLooseColor()); + setOverwritingArchiveColor(m_parent->modlistOverwritingArchiveColor()); + setOverwrittenArchiveColor(m_parent->modlistOverwrittenArchiveColor()); + setContainsColor(m_parent->modlistContainsPluginColor()); + setContainedColor(m_parent->pluginListContainedColor()); + + ui->compactBox->setChecked(m_parent->compactDownloads()); + ui->showMetaBox->setChecked(m_parent->metaDownloads()); + ui->usePrereleaseBox->setChecked(m_parent->usePrereleases()); + ui->colorSeparatorsBox->setChecked(m_parent->colorSeparatorScrollbar()); + + QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); }); + QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); }); + QObject::connect(ui->overwrittenArchiveBtn, &QPushButton::clicked, [&]{ on_overwrittenArchiveBtn_clicked(); }); + QObject::connect(ui->overwrittenBtn, &QPushButton::clicked, [&]{ on_overwrittenBtn_clicked(); }); + QObject::connect(ui->containedBtn, &QPushButton::clicked, [&]{ on_containedBtn_clicked(); }); + QObject::connect(ui->containsBtn, &QPushButton::clicked, [&]{ on_containsBtn_clicked(); }); + QObject::connect(ui->categoriesBtn, &QPushButton::clicked, [&]{ on_categoriesBtn_clicked(); }); + QObject::connect(ui->resetColorsBtn, &QPushButton::clicked, [&]{ on_resetColorsBtn_clicked(); }); + QObject::connect(ui->resetDialogsButton, &QPushButton::clicked, [&]{ on_resetDialogsButton_clicked(); }); +} + +void GeneralTab::update() +{ + QString oldLanguage = m_parent->language(); + QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); + if (newLanguage != oldLanguage) { + m_Settings.setValue("Settings/language", newLanguage); + emit m_parent->languageChanged(newLanguage); + } + + QString oldStyle = m_Settings.value("Settings/style", "").toString(); + QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); + if (oldStyle != newStyle) { + m_Settings.setValue("Settings/style", newStyle); + emit m_parent->styleChanged(newStyle); + } + + m_Settings.setValue("Settings/overwritingLooseFilesColor", getOverwritingColor()); + m_Settings.setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor()); + m_Settings.setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor()); + m_Settings.setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor()); + m_Settings.setValue("Settings/containsPluginColor", getContainsColor()); + m_Settings.setValue("Settings/containedColor", getContainedColor()); + m_Settings.setValue("Settings/compact_downloads", ui->compactBox->isChecked()); + m_Settings.setValue("Settings/meta_downloads", ui->showMetaBox->isChecked()); + m_Settings.setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked()); + m_Settings.setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); +} + +void GeneralTab::addLanguages() +{ + std::vector> languages; + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); + QString pattern = QString::fromStdWString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + QRegExp exp(pattern); + while (langIter.hasNext()) { + langIter.next(); + QString file = langIter.fileName(); + if (exp.exactMatch(file)) { + QString languageCode = exp.cap(1); + QLocale locale(languageCode); + QString languageString = QString("%1 (%2)").arg(locale.nativeLanguageName()).arg(locale.nativeCountryName()); //QLocale::languageToString(locale.language()); + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (traditional)"; + } else { + languageString = "Chinese (simplified)"; + } + } + languages.push_back(std::make_pair(QString("%1").arg(languageString), exp.cap(1))); + //languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); + } + } + if (!ui->languageBox->findText("English")) { + languages.push_back(std::make_pair(QString("English"), QString("en_US"))); + //languageBox->addItem("English", "en_US"); + } + std::sort(languages.begin(), languages.end()); + for (const auto &lang : languages) { + ui->languageBox->addItem(lang.first, lang.second); + } +} + +void GeneralTab::addStyles() +{ + ui->styleBox->addItem("None", ""); + ui->styleBox->addItem("Fusion", "Fusion"); + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + QString::fromStdWString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); + while (langIter.hasNext()) { + langIter.next(); + QString style = langIter.fileName(); + ui->styleBox->addItem(style, style); + } +} + +void GeneralTab::resetDialogs() +{ + QuestionBoxMemory::resetDialogs(); +} + +void GeneralTab::setButtonColor(QPushButton *button, const QColor &color) +{ + button->setStyleSheet( + QString("QPushButton {" + "background-color: rgba(%1, %2, %3, %4);" + "color: %5;" + "border: 1px solid;" + "padding: 3px;" + "}") + .arg(color.red()) + .arg(color.green()) + .arg(color.blue()) + .arg(color.alpha()) + .arg(Settings::getIdealTextColor(color).name()) + ); +}; + +void GeneralTab::on_containsBtn_clicked() +{ + QColor result = QColorDialog::getColor(m_ContainsColor, parentWidget(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); + if (result.isValid()) { + m_ContainsColor = result; + setButtonColor(ui->containsBtn, result); + } +} + +void GeneralTab::on_containedBtn_clicked() +{ + QColor result = QColorDialog::getColor(m_ContainedColor, parentWidget(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); + if (result.isValid()) { + m_ContainedColor = result; + setButtonColor(ui->containedBtn, result); + } +} + +void GeneralTab::on_overwrittenBtn_clicked() +{ + QColor result = QColorDialog::getColor(m_OverwrittenColor, parentWidget(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); + if (result.isValid()) { + m_OverwrittenColor = result; + setButtonColor(ui->overwrittenBtn, result); + } +} + +void GeneralTab::on_overwritingBtn_clicked() +{ + QColor result = QColorDialog::getColor(m_OverwritingColor, parentWidget(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); + if (result.isValid()) { + m_OverwritingColor = result; + setButtonColor(ui->overwritingBtn, result); + } +} + +void GeneralTab::on_overwrittenArchiveBtn_clicked() +{ + QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, parentWidget(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); + if (result.isValid()) { + m_OverwrittenArchiveColor = result; + setButtonColor(ui->overwrittenArchiveBtn, result); + } +} + +void GeneralTab::on_overwritingArchiveBtn_clicked() +{ + QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, parentWidget(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); + if (result.isValid()) { + m_OverwritingArchiveColor = result; + setButtonColor(ui->overwritingArchiveBtn, result); + } +} + +void GeneralTab::on_resetColorsBtn_clicked() +{ + m_OverwritingColor = QColor(255, 0, 0, 64); + m_OverwrittenColor = QColor(0, 255, 0, 64); + m_OverwritingArchiveColor = QColor(255, 0, 255, 64); + m_OverwrittenArchiveColor = QColor(0, 255, 255, 64); + m_ContainsColor = QColor(0, 0, 255, 64); + m_ContainedColor = QColor(0, 0, 255, 64); + + setButtonColor(ui->overwritingBtn, m_OverwritingColor); + setButtonColor(ui->overwrittenBtn, m_OverwrittenColor); + setButtonColor(ui->overwritingArchiveBtn, m_OverwritingArchiveColor); + setButtonColor(ui->overwrittenArchiveBtn, m_OverwrittenArchiveColor); + setButtonColor(ui->containsBtn, m_ContainsColor); + setButtonColor(ui->containedBtn, m_ContainedColor); +} + +void GeneralTab::on_resetDialogsButton_clicked() +{ + if (QMessageBox::question(parentWidget(), QObject::tr("Confirm?"), + QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + resetDialogs(); + } +} + +void GeneralTab::on_categoriesBtn_clicked() +{ + CategoriesDialog dialog(parentWidget()); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h new file mode 100644 index 00000000..1f1b4637 --- /dev/null +++ b/src/settingsdialoggeneral.h @@ -0,0 +1,53 @@ +#ifndef SETTINGSDIALOGGENERAL_H +#define SETTINGSDIALOGGENERAL_H + +#include "settingsdialog.h" +#include "settings.h" + +class GeneralTab : public SettingsTab +{ +public: + GeneralTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + +private: + QColor m_OverwritingColor; + QColor m_OverwrittenColor; + QColor m_OverwritingArchiveColor; + QColor m_OverwrittenArchiveColor; + QColor m_ContainsColor; + QColor m_ContainedColor; + + void addLanguages(); + void addStyles(); + void resetDialogs(); + void setButtonColor(QPushButton *button, const QColor &color); + + QColor getOverwritingColor() { return m_OverwritingColor; } + QColor getOverwrittenColor() { return m_OverwrittenColor; } + QColor getOverwritingArchiveColor() { return m_OverwritingArchiveColor; } + QColor getOverwrittenArchiveColor() { return m_OverwrittenArchiveColor; } + QColor getContainsColor() { return m_ContainsColor; } + QColor getContainedColor() { return m_ContainedColor; } + + void setOverwritingColor(QColor col) { m_OverwritingColor = col; } + void setOverwrittenColor(QColor col) { m_OverwrittenColor = col; } + void setOverwritingArchiveColor(QColor col) { m_OverwritingArchiveColor = col; } + void setOverwrittenArchiveColor(QColor col) { m_OverwrittenArchiveColor = col; } + void setContainsColor(QColor col) { m_ContainsColor = col; } + void setContainedColor(QColor col) { m_ContainedColor = col; } + + void on_overwritingArchiveBtn_clicked(); + void on_overwritingBtn_clicked(); + void on_overwrittenArchiveBtn_clicked(); + void on_overwrittenBtn_clicked(); + void on_containedBtn_clicked(); + void on_containsBtn_clicked(); + + void on_categoriesBtn_clicked(); + void on_resetColorsBtn_clicked(); + void on_resetDialogsButton_clicked(); +}; + +#endif // SETTINGSDIALOGGENERAL_H -- cgit v1.3.1 From 0a5ce34b1a80694fbfe6a4d6b4f032b9c11a5376 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 09:02:03 -0400 Subject: split paths tab --- src/CMakeLists.txt | 3 + src/settings.cpp | 81 +---------------- src/settings.h | 17 +--- src/settingsdialog.cpp | 113 ------------------------ src/settingsdialog.h | 14 --- src/settingsdialogpaths.cpp | 205 ++++++++++++++++++++++++++++++++++++++++++++ src/settingsdialogpaths.h | 33 +++++++ 7 files changed, 243 insertions(+), 223 deletions(-) create mode 100644 src/settingsdialogpaths.cpp create mode 100644 src/settingsdialogpaths.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29c419b5..98d59996 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ SET(organizer_SRCS singleinstance.cpp settingsdialog.cpp settingsdialoggeneral.cpp + settingsdialogpaths.cpp settings.cpp selfupdater.cpp selectiondialog.cpp @@ -153,6 +154,7 @@ SET(organizer_HDRS singleinstance.h settingsdialog.h settingsdialoggeneral.h + settingsdialogpaths.h settings.h selfupdater.h selectiondialog.h @@ -434,6 +436,7 @@ set(settings settings settingsdialog settingsdialoggeneral + settingsdialogpaths ) set(utilities diff --git a/src/settings.cpp b/src/settings.cpp index 0911b155..bed8e789 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "serverinfo.h" #include "settingsdialog.h" #include "settingsdialoggeneral.h" +#include "settingsdialogpaths.h" #include "versioninfo.h" #include "appconfig.h" #include "organizercore.h" @@ -752,86 +753,6 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) } -Settings::PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) - : SettingsTab(parent, dialog) - , m_baseDirEdit(m_dialog.findChild("baseDirEdit")) - , m_downloadDirEdit(m_dialog.findChild("downloadDirEdit")) - , m_modDirEdit(m_dialog.findChild("modDirEdit")) - , m_cacheDirEdit(m_dialog.findChild("cacheDirEdit")) - , m_profilesDirEdit(m_dialog.findChild("profilesDirEdit")) - , m_overwriteDirEdit(m_dialog.findChild("overwriteDirEdit")) - , m_managedGameDirEdit(m_dialog.findChild("managedGameDirEdit")) -{ - m_baseDirEdit->setText(m_parent->getBaseDirectory()); - m_managedGameDirEdit->setText(m_parent->m_GamePlugin->gameDirectory().absoluteFilePath(m_parent->m_GamePlugin->binaryName())); - QString basePath = parent->getBaseDirectory(); - QDir baseDir(basePath); - for (const auto &dir : { - std::make_pair(m_downloadDirEdit, m_parent->getDownloadDirectory(false)), - std::make_pair(m_modDirEdit, m_parent->getModDirectory(false)), - std::make_pair(m_cacheDirEdit, m_parent->getCacheDirectory(false)), - std::make_pair(m_profilesDirEdit, m_parent->getProfileDirectory(false)), - std::make_pair(m_overwriteDirEdit, m_parent->getOverwriteDirectory(false)) - }) { - QString storePath = baseDir.relativeFilePath(dir.second); - storePath = dir.second; - dir.first->setText(storePath); - } -} - -void Settings::PathsTab::update() -{ - typedef std::tuple Directory; - - QString basePath = m_parent->getBaseDirectory(); - - for (const Directory &dir :{ - Directory{m_downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, - Directory{m_cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()}, - Directory{m_modDirEdit->text(), "mod_directory", AppConfig::modsPath()}, - Directory{m_overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()}, - Directory{m_profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()} - }) { - QString path, settingsKey; - std::wstring defaultName; - std::tie(path, settingsKey, defaultName) = dir; - - settingsKey = QString("Settings/%1").arg(settingsKey); - - QString realPath = path; - realPath.replace("%BASE_DIR%", m_baseDirEdit->text()); - - if (!QDir(realPath).exists()) { - if (!QDir().mkpath(realPath)) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), - tr("Failed to create \"%1\", you may not have the " - "necessary permission. path remains unchanged.") - .arg(realPath)); - } - } - - if (QFileInfo(realPath) - != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - m_Settings.setValue(settingsKey, path); - } else { - m_Settings.remove(settingsKey); - } - } - - if (QFileInfo(m_baseDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString())) { - m_Settings.setValue("Settings/base_directory", m_baseDirEdit->text()); - } else { - m_Settings.remove("Settings/base_directory"); - } - - QFileInfo oldGameExe(m_parent->m_GamePlugin->gameDirectory().absoluteFilePath(m_parent->m_GamePlugin->binaryName())); - QFileInfo newGameExe(m_managedGameDirEdit->text()); - if (oldGameExe != newGameExe) { - m_Settings.setValue("gamePath", newGameExe.absolutePath()); - } -} - Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) , m_logLevelBox(m_dialog.findChild("logLevelBox")) diff --git a/src/settings.h b/src/settings.h index e88080ba..6f75562b 100644 --- a/src/settings.h +++ b/src/settings.h @@ -427,6 +427,7 @@ public: bool colorSeparatorScrollbar() const; QSettings& settingsRef() { return m_Settings; } + MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } public slots: @@ -443,22 +444,6 @@ private: QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - class PathsTab : public SettingsTab - { - public: - PathsTab(Settings *parent, SettingsDialog &dialog); - - void update(); - - private: - QLineEdit *m_baseDirEdit; - QLineEdit *m_downloadDirEdit; - QLineEdit *m_modDirEdit; - QLineEdit *m_cacheDirEdit; - QLineEdit *m_profilesDirEdit; - QLineEdit *m_overwriteDirEdit; - QLineEdit *m_managedGameDirEdit; - }; class DiagnosticsTab : public SettingsTab { diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index f922cfb9..bfa285bf 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -200,80 +200,6 @@ void SettingsDialog::on_bsaDateBtn_clicked() dir.absolutePath().toStdWString()); } -void SettingsDialog::on_browseBaseDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory( - this, tr("Select base directory"), ui->baseDirEdit->text()); - if (!temp.isEmpty()) { - ui->baseDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseDownloadDirBtn_clicked() -{ - QString searchPath = ui->downloadDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - - QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), searchPath); - if (!temp.isEmpty()) { - ui->downloadDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseModDirBtn_clicked() -{ - QString searchPath = ui->modDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - - QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), searchPath); - if (!temp.isEmpty()) { - ui->modDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseCacheDirBtn_clicked() -{ - QString searchPath = ui->cacheDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - - QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), searchPath); - if (!temp.isEmpty()) { - ui->cacheDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseProfilesDirBtn_clicked() -{ - QString searchPath = ui->profilesDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - - QString temp = QFileDialog::getExistingDirectory(this, tr("Select profiles directory"), searchPath); - if (!temp.isEmpty()) { - ui->profilesDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseOverwriteDirBtn_clicked() -{ - QString searchPath = ui->overwriteDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - - QString temp = QFileDialog::getExistingDirectory(this, tr("Select overwrite directory"), searchPath); - if (!temp.isEmpty()) { - ui->overwriteDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseGameDirBtn_clicked() -{ - QFileInfo oldGameExe(ui->managedGameDirEdit->text()); - - QString temp = QFileDialog::getOpenFileName(this, tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName()); - if (!temp.isEmpty()) { - ui->managedGameDirEdit->setText(temp); - } -} - void SettingsDialog::on_nexusConnect_clicked() { if (m_nexusLogin && m_nexusLogin->isActive()) { @@ -555,45 +481,6 @@ void SettingsDialog::on_clearCacheButton_clicked() NexusInterface::instance(m_PluginContainer)->clearCache(); } -void SettingsDialog::normalizePath(QLineEdit *lineEdit) -{ - QString text = lineEdit->text(); - while (text.endsWith('/') || text.endsWith('\\')) { - text.chop(1); - } - lineEdit->setText(text); -} - -void SettingsDialog::on_baseDirEdit_editingFinished() -{ - normalizePath(ui->baseDirEdit); -} - -void SettingsDialog::on_downloadDirEdit_editingFinished() -{ - normalizePath(ui->downloadDirEdit); -} - -void SettingsDialog::on_modDirEdit_editingFinished() -{ - normalizePath(ui->modDirEdit); -} - -void SettingsDialog::on_cacheDirEdit_editingFinished() -{ - normalizePath(ui->cacheDirEdit); -} - -void SettingsDialog::on_profilesDirEdit_editingFinished() -{ - normalizePath(ui->profilesDirEdit); -} - -void SettingsDialog::on_overwriteDirEdit_editingFinished() -{ - normalizePath(ui->overwriteDirEdit); -} - void SettingsDialog::on_resetGeometryBtn_clicked() { m_GeometriesReset = true; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 01a0afa2..68e72529 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -67,7 +67,6 @@ signals: private: void storeSettings(QListWidgetItem *pluginItem); - void normalizePath(QLineEdit *lineEdit); public: QString getExecutableBlacklist() { return m_ExecutableBlacklist; } @@ -77,26 +76,13 @@ public: private slots: void on_associateButton_clicked(); - void on_baseDirEdit_editingFinished(); - void on_browseBaseDirBtn_clicked(); - void on_browseCacheDirBtn_clicked(); - void on_browseDownloadDirBtn_clicked(); - void on_browseGameDirBtn_clicked(); - void on_browseModDirBtn_clicked(); - void on_browseOverwriteDirBtn_clicked(); - void on_browseProfilesDirBtn_clicked(); void on_bsaDateBtn_clicked(); - void on_cacheDirEdit_editingFinished(); void on_clearCacheButton_clicked(); - void on_downloadDirEdit_editingFinished(); void on_execBlacklistBtn_clicked(); - void on_modDirEdit_editingFinished(); void on_nexusConnect_clicked(); void on_nexusDisconnect_clicked(); void on_nexusManualKey_clicked(); - void on_overwriteDirEdit_editingFinished(); void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_profilesDirEdit_editingFinished(); void on_resetGeometryBtn_clicked(); void deleteBlacklistItem(); diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp new file mode 100644 index 00000000..303d1562 --- /dev/null +++ b/src/settingsdialogpaths.cpp @@ -0,0 +1,205 @@ +#include "settingsdialogpaths.h" +#include "ui_settingsdialog.h" +#include "appconfig.h" +#include + +PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) + : SettingsTab(parent, dialog) +{ + ui->baseDirEdit->setText(m_parent->getBaseDirectory()); + ui->managedGameDirEdit->setText(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName())); + QString basePath = parent->getBaseDirectory(); + QDir baseDir(basePath); + for (const auto &dir : { + std::make_pair(ui->downloadDirEdit, m_parent->getDownloadDirectory(false)), + std::make_pair(ui->modDirEdit, m_parent->getModDirectory(false)), + std::make_pair(ui->cacheDirEdit, m_parent->getCacheDirectory(false)), + std::make_pair(ui->profilesDirEdit, m_parent->getProfileDirectory(false)), + std::make_pair(ui->overwriteDirEdit, m_parent->getOverwriteDirectory(false)) + }) { + QString storePath = baseDir.relativeFilePath(dir.second); + storePath = dir.second; + dir.first->setText(storePath); + } + + QObject::connect(ui->browseBaseDirBtn, &QPushButton::clicked, [&]{ on_browseBaseDirBtn_clicked(); }); + QObject::connect(ui->browseCacheDirBtn, &QPushButton::clicked, [&]{ on_browseCacheDirBtn_clicked(); }); + QObject::connect(ui->browseDownloadDirBtn, &QPushButton::clicked, [&]{ on_browseDownloadDirBtn_clicked(); }); + QObject::connect(ui->browseGameDirBtn, &QPushButton::clicked, [&]{ on_browseGameDirBtn_clicked(); }); + QObject::connect(ui->browseModDirBtn, &QPushButton::clicked, [&]{ on_browseModDirBtn_clicked(); }); + QObject::connect(ui->browseOverwriteDirBtn, &QPushButton::clicked, [&]{ on_browseOverwriteDirBtn_clicked(); }); + QObject::connect(ui->browseProfilesDirBtn, &QPushButton::clicked, [&]{ on_browseProfilesDirBtn_clicked(); }); + + QObject::connect(ui->baseDirEdit, &QLineEdit::editingFinished, [&]{ on_baseDirEdit_editingFinished(); }); + QObject::connect(ui->cacheDirEdit, &QLineEdit::editingFinished, [&]{ on_cacheDirEdit_editingFinished(); }); + QObject::connect(ui->downloadDirEdit, &QLineEdit::editingFinished, [&]{ on_downloadDirEdit_editingFinished(); }); + QObject::connect(ui->modDirEdit, &QLineEdit::editingFinished, [&]{ on_modDirEdit_editingFinished(); }); + QObject::connect(ui->overwriteDirEdit, &QLineEdit::editingFinished, [&]{ on_overwriteDirEdit_editingFinished(); }); + QObject::connect(ui->profilesDirEdit, &QLineEdit::editingFinished, [&]{ on_profilesDirEdit_editingFinished(); }); +} + +void PathsTab::update() +{ + typedef std::tuple Directory; + + QString basePath = m_parent->getBaseDirectory(); + + for (const Directory &dir :{ + Directory{ui->downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, + Directory{ui->cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()}, + Directory{ui->modDirEdit->text(), "mod_directory", AppConfig::modsPath()}, + Directory{ui->overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()}, + Directory{ui->profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()} + }) { + QString path, settingsKey; + std::wstring defaultName; + std::tie(path, settingsKey, defaultName) = dir; + + settingsKey = QString("Settings/%1").arg(settingsKey); + + QString realPath = path; + realPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + if (!QDir(realPath).exists()) { + if (!QDir().mkpath(realPath)) { + QMessageBox::warning(qApp->activeWindow(), QObject::tr("Error"), + QObject::tr("Failed to create \"%1\", you may not have the " + "necessary permission. path remains unchanged.") + .arg(realPath)); + } + } + + if (QFileInfo(realPath) + != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { + m_Settings.setValue(settingsKey, path); + } else { + m_Settings.remove(settingsKey); + } + } + + if (QFileInfo(ui->baseDirEdit->text()) != + QFileInfo(qApp->property("dataPath").toString())) { + m_Settings.setValue("Settings/base_directory", ui->baseDirEdit->text()); + } else { + m_Settings.remove("Settings/base_directory"); + } + + QFileInfo oldGameExe(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName())); + QFileInfo newGameExe(ui->managedGameDirEdit->text()); + if (oldGameExe != newGameExe) { + m_Settings.setValue("gamePath", newGameExe.absolutePath()); + } +} + +void PathsTab::on_browseBaseDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory( + parentWidget(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); + if (!temp.isEmpty()) { + ui->baseDirEdit->setText(temp); + } +} + +void PathsTab::on_browseDownloadDirBtn_clicked() +{ + QString searchPath = ui->downloadDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select download directory"), searchPath); + if (!temp.isEmpty()) { + ui->downloadDirEdit->setText(temp); + } +} + +void PathsTab::on_browseModDirBtn_clicked() +{ + QString searchPath = ui->modDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select mod directory"), searchPath); + if (!temp.isEmpty()) { + ui->modDirEdit->setText(temp); + } +} + +void PathsTab::on_browseCacheDirBtn_clicked() +{ + QString searchPath = ui->cacheDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select cache directory"), searchPath); + if (!temp.isEmpty()) { + ui->cacheDirEdit->setText(temp); + } +} + +void PathsTab::on_browseProfilesDirBtn_clicked() +{ + QString searchPath = ui->profilesDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select profiles directory"), searchPath); + if (!temp.isEmpty()) { + ui->profilesDirEdit->setText(temp); + } +} + +void PathsTab::on_browseOverwriteDirBtn_clicked() +{ + QString searchPath = ui->overwriteDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select overwrite directory"), searchPath); + if (!temp.isEmpty()) { + ui->overwriteDirEdit->setText(temp); + } +} + +void PathsTab::on_browseGameDirBtn_clicked() +{ + QFileInfo oldGameExe(ui->managedGameDirEdit->text()); + + QString temp = QFileDialog::getOpenFileName(parentWidget(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName()); + if (!temp.isEmpty()) { + ui->managedGameDirEdit->setText(temp); + } +} + +void PathsTab::on_baseDirEdit_editingFinished() +{ + normalizePath(ui->baseDirEdit); +} + +void PathsTab::on_downloadDirEdit_editingFinished() +{ + normalizePath(ui->downloadDirEdit); +} + +void PathsTab::on_modDirEdit_editingFinished() +{ + normalizePath(ui->modDirEdit); +} + +void PathsTab::on_cacheDirEdit_editingFinished() +{ + normalizePath(ui->cacheDirEdit); +} + +void PathsTab::on_profilesDirEdit_editingFinished() +{ + normalizePath(ui->profilesDirEdit); +} + +void PathsTab::on_overwriteDirEdit_editingFinished() +{ + normalizePath(ui->overwriteDirEdit); +} + +void PathsTab::normalizePath(QLineEdit *lineEdit) +{ + QString text = lineEdit->text(); + while (text.endsWith('/') || text.endsWith('\\')) { + text.chop(1); + } + lineEdit->setText(text); +} diff --git a/src/settingsdialogpaths.h b/src/settingsdialogpaths.h new file mode 100644 index 00000000..dac402b1 --- /dev/null +++ b/src/settingsdialogpaths.h @@ -0,0 +1,33 @@ +#ifndef SETTINGSDIALOGPATHS_H +#define SETTINGSDIALOGPATHS_H + +#include "settings.h" +#include "settingsdialog.h" + +class PathsTab : public SettingsTab +{ +public: + PathsTab(Settings *parent, SettingsDialog &dialog); + + void update(); + +private: + void on_browseBaseDirBtn_clicked(); + void on_browseCacheDirBtn_clicked(); + void on_browseDownloadDirBtn_clicked(); + void on_browseGameDirBtn_clicked(); + void on_browseModDirBtn_clicked(); + void on_browseOverwriteDirBtn_clicked(); + void on_browseProfilesDirBtn_clicked(); + + void on_baseDirEdit_editingFinished(); + void on_cacheDirEdit_editingFinished(); + void on_downloadDirEdit_editingFinished(); + void on_modDirEdit_editingFinished(); + void on_overwriteDirEdit_editingFinished(); + void on_profilesDirEdit_editingFinished(); + + void normalizePath(QLineEdit *lineEdit); +}; + +#endif // SETTINGSDIALOGPATHS_H -- cgit v1.3.1 From af95b3b8637d28517f69a70f13b901cc7f43d121 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 09:31:38 -0400 Subject: renamed tab classes, clashing with mod info dialog split nexus tab --- src/CMakeLists.txt | 3 + src/settings.cpp | 114 +------------ src/settings.h | 16 -- src/settingsdialog.cpp | 278 +------------------------------ src/settingsdialog.h | 34 +--- src/settingsdialoggeneral.cpp | 30 ++-- src/settingsdialoggeneral.h | 4 +- src/settingsdialognexus.cpp | 374 ++++++++++++++++++++++++++++++++++++++++++ src/settingsdialognexus.h | 40 +++++ src/settingsdialogpaths.cpp | 32 ++-- src/settingsdialogpaths.h | 4 +- 11 files changed, 460 insertions(+), 469 deletions(-) create mode 100644 src/settingsdialognexus.cpp create mode 100644 src/settingsdialognexus.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 98d59996..b2407e17 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ SET(organizer_SRCS singleinstance.cpp settingsdialog.cpp settingsdialoggeneral.cpp + settingsdialognexus.cpp settingsdialogpaths.cpp settings.cpp selfupdater.cpp @@ -154,6 +155,7 @@ SET(organizer_HDRS singleinstance.h settingsdialog.h settingsdialoggeneral.h + settingsdialognexus.h settingsdialogpaths.h settings.h selfupdater.h @@ -436,6 +438,7 @@ set(settings settings settingsdialog settingsdialoggeneral + settingsdialognexus settingsdialogpaths ) diff --git a/src/settings.cpp b/src/settings.cpp index bed8e789..bded470c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "serverinfo.h" #include "settingsdialog.h" #include "settingsdialoggeneral.h" +#include "settingsdialognexus.h" #include "settingsdialogpaths.h" #include "versioninfo.h" #include "appconfig.h" @@ -69,19 +70,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; -template -class QListWidgetItemEx : public QListWidgetItem { -public: - QListWidgetItemEx(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) - : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} - - virtual bool operator< ( const QListWidgetItem & other ) const { - return this->data(m_SortRole).value() < other.data(m_SortRole).value(); - } -private: - int m_SortRole; -}; - SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : m_parent(m_parent) @@ -688,10 +676,10 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) std::vector> tabs; - tabs.push_back(std::unique_ptr(new GeneralTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PathsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new GeneralSettingsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new PathsSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new DiagnosticsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new NexusTab(this, dialog))); + tabs.push_back(std::unique_ptr(new NexusSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new SteamTab(this, dialog))); tabs.push_back(std::unique_ptr(new PluginsTab(this, dialog))); tabs.push_back(std::unique_ptr(new WorkaroundsTab(this, dialog))); @@ -781,100 +769,6 @@ void Settings::DiagnosticsTab::update() m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); } -void Settings::DiagnosticsTab::setLevelsBox() -{ - m_logLevelBox->clear(); - - m_logLevelBox->addItem(tr("Debug"), log::Debug); - m_logLevelBox->addItem(tr("Info (recommended)"), log::Info); - m_logLevelBox->addItem(tr("Warning"), log::Warning); - m_logLevelBox->addItem(tr("Error"), log::Error); - - for (int i=0; icount(); ++i) { - if (m_logLevelBox->itemData(i) == m_parent->logLevel()) { - m_logLevelBox->setCurrentIndex(i); - break; - } - } -} - -Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) - : SettingsTab(parent, dialog) - , m_offlineBox(dialog.findChild("offlineBox")) - , m_proxyBox(dialog.findChild("proxyBox")) - , m_knownServersList(dialog.findChild("knownServersList")) - , m_preferredServersList( - dialog.findChild("preferredServersList")) - , m_endorsementBox(dialog.findChild("endorsementBox")) - , m_hideAPICounterBox(dialog.findChild("hideAPICounterBox")) -{ - m_offlineBox->setChecked(parent->offlineMode()); - m_proxyBox->setChecked(parent->useProxy()); - m_endorsementBox->setChecked(parent->endorsementIntegration()); - m_hideAPICounterBox->setChecked(parent->hideAPICounter()); - - // display server preferences - m_Settings.beginGroup("Servers"); - for (const QString &key : m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); - QString descriptor = key; - if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { - descriptor += QStringLiteral(" (automatic)"); - } - if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { - int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); - descriptor += QString(" (%1 kbps)").arg(bps / 1024); - } - - QListWidgetItem *newItem = new QListWidgetItemEx(descriptor, Qt::UserRole + 1); - - newItem->setData(Qt::UserRole, key); - newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); - if (val["preferred"].toInt() > 0) { - m_preferredServersList->addItem(newItem); - } else { - m_knownServersList->addItem(newItem); - } - m_preferredServersList->sortItems(Qt::DescendingOrder); - } - m_Settings.endGroup(); -} - -void Settings::NexusTab::update() -{ - /* - if (m_loginCheckBox->isChecked()) { - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", m_usernameEdit->text()); - m_Settings.setValue("Settings/nexus_password", obfuscate(m_passwordEdit->text())); - } else { - m_Settings.setValue("Settings/nexus_login", false); - m_Settings.remove("Settings/nexus_username"); - m_Settings.remove("Settings/nexus_password"); - } - */ - m_Settings.setValue("Settings/offline_mode", m_offlineBox->isChecked()); - m_Settings.setValue("Settings/use_proxy", m_proxyBox->isChecked()); - m_Settings.setValue("Settings/endorsement_integration", m_endorsementBox->isChecked()); - m_Settings.setValue("Settings/hide_api_counter", m_hideAPICounterBox->isChecked()); - - // store server preference - m_Settings.beginGroup("Servers"); - for (int i = 0; i < m_knownServersList->count(); ++i) { - QString key = m_knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = 0; - m_Settings.setValue(key, val); - } - int count = m_preferredServersList->count(); - for (int i = 0; i < count; ++i) { - QString key = m_preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = count - i; - m_Settings.setValue(key, val); - } - m_Settings.endGroup(); -} Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) diff --git a/src/settings.h b/src/settings.h index 6f75562b..b9383ce4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -461,22 +461,6 @@ private: void setLevelsBox(); }; - /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ - class NexusTab : public SettingsTab - { - public: - NexusTab(Settings *m_parent, SettingsDialog &m_dialog); - void update(); - - private: - QCheckBox *m_offlineBox; - QCheckBox *m_proxyBox; - QListWidget *m_knownServersList; - QListWidget *m_preferredServersList; - QCheckBox *m_endorsementBox; - QCheckBox *m_hideAPICounterBox; - }; - /** Display/store the configuration in the 'steam' tab of the settings dialogue */ class SteamTab : public SettingsTab { diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index bfa285bf..6d5a8cc0 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -20,7 +20,6 @@ along with Mod Organizer. If not, see . #include "settingsdialog.h" #include "ui_settingsdialog.h" -#include "ui_nexusmanualkey.h" #include "categoriesdialog.h" #include "helper.h" #include "noeditdelegate.h" @@ -48,62 +47,14 @@ along with Mod Organizer. If not, see . using namespace MOBase; -class NexusManualKeyDialog : public QDialog -{ -public: - NexusManualKeyDialog(QWidget* parent) - : QDialog(parent), ui(new Ui::NexusManualKeyDialog) - { - ui->setupUi(this); - ui->key->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - - connect(ui->openBrowser, &QPushButton::clicked, [&]{ openBrowser(); }); - connect(ui->paste, &QPushButton::clicked, [&]{ paste(); }); - connect(ui->clear, &QPushButton::clicked, [&]{ clear(); }); - } - - void accept() override - { - m_key = ui->key->toPlainText(); - QDialog::accept(); - } - - const QString& key() const - { - return m_key; - } - - void openBrowser() - { - shell::OpenLink(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); - } - - void paste() - { - const auto text = QApplication::clipboard()->text(); - if (!text.isEmpty()) { - ui->key->setPlainText(text); - } - } - - void clear() - { - ui->key->clear(); - } - -private: - std::unique_ptr ui; - QString m_key; -}; - SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) , m_PluginContainer(pluginContainer) - , m_keyChanged(false) , m_GeometriesReset(false) + , m_keyChanged(false) { ui->setupUi(this); ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); @@ -111,8 +62,6 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti QShortcut *delShortcut = new QShortcut( QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); - - updateNexusState(); } SettingsDialog::~SettingsDialog() @@ -200,220 +149,6 @@ void SettingsDialog::on_bsaDateBtn_clicked() dir.absolutePath().toStdWString()); } -void SettingsDialog::on_nexusConnect_clicked() -{ - if (m_nexusLogin && m_nexusLogin->isActive()) { - m_nexusLogin->cancel(); - return; - } - - if (!m_nexusLogin) { - m_nexusLogin.reset(new NexusSSOLogin); - - m_nexusLogin->keyChanged = [&](auto&& s){ - onSSOKeyChanged(s); - }; - - m_nexusLogin->stateChanged = [&](auto&& s, auto&& e){ - onSSOStateChanged(s, e); - }; - } - - ui->nexusLog->clear(); - m_nexusLogin->start(); - updateNexusState(); -} - -void SettingsDialog::on_nexusManualKey_clicked() -{ - if (m_nexusValidator && m_nexusValidator->isActive()) { - m_nexusValidator->cancel(); - return; - } - - NexusManualKeyDialog dialog(this); - if (dialog.exec() != QDialog::Accepted) { - return; - } - - const auto key = dialog.key(); - if (key.isEmpty()) { - clearKey(); - return; - } - - ui->nexusLog->clear(); - validateKey(key); -} - -void SettingsDialog::on_nexusDisconnect_clicked() -{ - clearKey(); - ui->nexusLog->clear(); - addNexusLog(tr("Disconnected.")); -} - -void SettingsDialog::validateKey(const QString& key) -{ - if (!m_nexusValidator) { - m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance(m_PluginContainer)->getAccessManager())); - - m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ - onValidatorStateChanged(s, e); - }; - - m_nexusValidator->finished = [&](auto&& user) { - onValidatorFinished(user); - }; - } - - addNexusLog(tr("Checking API key...")); - m_nexusValidator->start(key); -} - -void SettingsDialog::onSSOKeyChanged(const QString& key) -{ - if (key.isEmpty()) { - clearKey(); - } else { - addNexusLog(tr("Received API key.")); - validateKey(key); - } -} - -void SettingsDialog::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) -{ - if (s != NexusSSOLogin::Finished) { - // finished state is handled in onSSOKeyChanged() - const auto log = NexusSSOLogin::stateToString(s, e); - - for (auto&& line : log.split("\n")) { - addNexusLog(line); - } - } - - updateNexusState(); -} - -void SettingsDialog::onValidatorStateChanged( - NexusKeyValidator::States s, const QString& e) -{ - if (s != NexusKeyValidator::Finished) { - // finished state is handled in onValidatorFinished() - const auto log = NexusKeyValidator::stateToString(s, e); - - for (auto&& line : log.split("\n")) { - addNexusLog(line); - } - } - - updateNexusState(); -} - -void SettingsDialog::onValidatorFinished(const APIUserAccount& user) -{ - NexusInterface::instance(m_PluginContainer)->setUserAccount(user); - - if (!user.apiKey().isEmpty()) { - if (setKey(user.apiKey())) { - addNexusLog(tr("Linked with Nexus successfully.")); - } - } -} - -void SettingsDialog::addNexusLog(const QString& s) -{ - ui->nexusLog->addItem(s); - ui->nexusLog->scrollToBottom(); -} - -bool SettingsDialog::setKey(const QString& key) -{ - m_keyChanged = true; - const bool ret = m_settings->setNexusApiKey(key); - updateNexusState(); - return ret; -} - -bool SettingsDialog::clearKey() -{ - m_keyChanged = true; - const auto ret = m_settings->clearNexusApiKey(); - - NexusInterface::instance(m_PluginContainer)->getAccessManager()->clearApiKey(); - updateNexusState(); - - return ret; -} - -void SettingsDialog::updateNexusState() -{ - updateNexusButtons(); - updateNexusData(); -} - -void SettingsDialog::updateNexusButtons() -{ - if (m_nexusLogin && m_nexusLogin->isActive()) { - // api key is in the process of being retrieved - ui->nexusConnect->setText(tr("Cancel")); - ui->nexusConnect->setEnabled(true); - ui->nexusDisconnect->setEnabled(false); - ui->nexusManualKey->setText(tr("Enter API Key Manually")); - ui->nexusManualKey->setEnabled(false); - } - else if (m_nexusValidator && m_nexusValidator->isActive()) { - // api key is in the process of being tested - ui->nexusConnect->setText(tr("Connect to Nexus")); - ui->nexusConnect->setEnabled(false); - ui->nexusDisconnect->setEnabled(false); - ui->nexusManualKey->setText(tr("Cancel")); - ui->nexusManualKey->setEnabled(true); - } - else if (m_settings->hasNexusApiKey()) { - // api key is present - ui->nexusConnect->setText(tr("Connect to Nexus")); - ui->nexusConnect->setEnabled(false); - ui->nexusDisconnect->setEnabled(true); - ui->nexusManualKey->setText(tr("Enter API Key Manually")); - ui->nexusManualKey->setEnabled(false); - } else { - // api key not present - ui->nexusConnect->setText(tr("Connect to Nexus")); - ui->nexusConnect->setEnabled(true); - ui->nexusDisconnect->setEnabled(false); - ui->nexusManualKey->setText(tr("Enter API Key Manually")); - ui->nexusManualKey->setEnabled(true); - } -} - -void SettingsDialog::updateNexusData() -{ - const auto user = NexusInterface::instance(m_PluginContainer) - ->getAPIUserAccount(); - - if (user.isValid()) { - ui->nexusUserID->setText(user.id()); - ui->nexusName->setText(user.name()); - ui->nexusAccount->setText(localizedUserAccountType(user.type())); - - ui->nexusDailyRequests->setText(QString("%1/%2") - .arg(user.limits().remainingDailyRequests) - .arg(user.limits().maxDailyRequests)); - - ui->nexusHourlyRequests->setText(QString("%1/%2") - .arg(user.limits().remainingHourlyRequests) - .arg(user.limits().maxHourlyRequests)); - } else { - ui->nexusUserID->setText(tr("N/A")); - ui->nexusName->setText(tr("N/A")); - ui->nexusAccount->setText(tr("N/A")); - ui->nexusDailyRequests->setText(tr("N/A")); - ui->nexusHourlyRequests->setText(tr("N/A")); - } -} - void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) { if (pluginItem != nullptr) { @@ -470,17 +205,6 @@ void SettingsDialog::deleteBlacklistItem() ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); } -void SettingsDialog::on_associateButton_clicked() -{ - Settings::instance().registerAsNXMHandler(true); -} - -void SettingsDialog::on_clearCacheButton_clicked() -{ - QDir(Settings::instance().getCacheDirectory()).removeRecursively(); - NexusInterface::instance(m_PluginContainer)->clearCache(); -} - void SettingsDialog::on_resetGeometryBtn_clicked() { m_GeometriesReset = true; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 68e72529..df5d0ad8 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -54,18 +54,15 @@ public: */ QString getColoredButtonStyleSheet() const; + // temp Ui::SettingsDialog *ui; + bool m_keyChanged; + PluginContainer *m_PluginContainer; public slots: - virtual void accept(); -signals: - - void retryApiConnection(); - private: - void storeSettings(QListWidgetItem *pluginItem); public: @@ -75,13 +72,8 @@ public: bool getApiKeyChanged(); private slots: - void on_associateButton_clicked(); void on_bsaDateBtn_clicked(); - void on_clearCacheButton_clicked(); void on_execBlacklistBtn_clicked(); - void on_nexusConnect_clicked(); - void on_nexusDisconnect_clicked(); - void on_nexusManualKey_clicked(); void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_resetGeometryBtn_clicked(); @@ -89,30 +81,10 @@ private slots: private: Settings* m_settings; - PluginContainer *m_PluginContainer; bool m_GeometriesReset; - bool m_keyChanged; QString m_ExecutableBlacklist; - std::unique_ptr m_nexusLogin; - std::unique_ptr m_nexusValidator; - - void validateKey(const QString& key); - bool setKey(const QString& key); - bool clearKey(); - - void updateNexusState(); - void updateNexusButtons(); - void updateNexusData(); - - void onSSOKeyChanged(const QString& key); - void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); - - void onValidatorStateChanged(NexusKeyValidator::States s, const QString& e); - void onValidatorFinished(const APIUserAccount& user); - - void addNexusLog(const QString& s); }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index b22b04fd..cd98dfdc 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -6,7 +6,7 @@ using MOBase::QuestionBoxMemory; -GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) +GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) { addLanguages(); @@ -87,7 +87,7 @@ GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) QObject::connect(ui->resetDialogsButton, &QPushButton::clicked, [&]{ on_resetDialogsButton_clicked(); }); } -void GeneralTab::update() +void GeneralSettingsTab::update() { QString oldLanguage = m_parent->language(); QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); @@ -115,7 +115,7 @@ void GeneralTab::update() m_Settings.setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); } -void GeneralTab::addLanguages() +void GeneralSettingsTab::addLanguages() { std::vector> languages; @@ -150,7 +150,7 @@ void GeneralTab::addLanguages() } } -void GeneralTab::addStyles() +void GeneralSettingsTab::addStyles() { ui->styleBox->addItem("None", ""); ui->styleBox->addItem("Fusion", "Fusion"); @@ -163,12 +163,12 @@ void GeneralTab::addStyles() } } -void GeneralTab::resetDialogs() +void GeneralSettingsTab::resetDialogs() { QuestionBoxMemory::resetDialogs(); } -void GeneralTab::setButtonColor(QPushButton *button, const QColor &color) +void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) { button->setStyleSheet( QString("QPushButton {" @@ -185,7 +185,7 @@ void GeneralTab::setButtonColor(QPushButton *button, const QColor &color) ); }; -void GeneralTab::on_containsBtn_clicked() +void GeneralSettingsTab::on_containsBtn_clicked() { QColor result = QColorDialog::getColor(m_ContainsColor, parentWidget(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); if (result.isValid()) { @@ -194,7 +194,7 @@ void GeneralTab::on_containsBtn_clicked() } } -void GeneralTab::on_containedBtn_clicked() +void GeneralSettingsTab::on_containedBtn_clicked() { QColor result = QColorDialog::getColor(m_ContainedColor, parentWidget(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); if (result.isValid()) { @@ -203,7 +203,7 @@ void GeneralTab::on_containedBtn_clicked() } } -void GeneralTab::on_overwrittenBtn_clicked() +void GeneralSettingsTab::on_overwrittenBtn_clicked() { QColor result = QColorDialog::getColor(m_OverwrittenColor, parentWidget(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { @@ -212,7 +212,7 @@ void GeneralTab::on_overwrittenBtn_clicked() } } -void GeneralTab::on_overwritingBtn_clicked() +void GeneralSettingsTab::on_overwritingBtn_clicked() { QColor result = QColorDialog::getColor(m_OverwritingColor, parentWidget(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { @@ -221,7 +221,7 @@ void GeneralTab::on_overwritingBtn_clicked() } } -void GeneralTab::on_overwrittenArchiveBtn_clicked() +void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked() { QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, parentWidget(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { @@ -230,7 +230,7 @@ void GeneralTab::on_overwrittenArchiveBtn_clicked() } } -void GeneralTab::on_overwritingArchiveBtn_clicked() +void GeneralSettingsTab::on_overwritingArchiveBtn_clicked() { QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, parentWidget(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { @@ -239,7 +239,7 @@ void GeneralTab::on_overwritingArchiveBtn_clicked() } } -void GeneralTab::on_resetColorsBtn_clicked() +void GeneralSettingsTab::on_resetColorsBtn_clicked() { m_OverwritingColor = QColor(255, 0, 0, 64); m_OverwrittenColor = QColor(0, 255, 0, 64); @@ -256,7 +256,7 @@ void GeneralTab::on_resetColorsBtn_clicked() setButtonColor(ui->containedBtn, m_ContainedColor); } -void GeneralTab::on_resetDialogsButton_clicked() +void GeneralSettingsTab::on_resetDialogsButton_clicked() { if (QMessageBox::question(parentWidget(), QObject::tr("Confirm?"), QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), @@ -265,7 +265,7 @@ void GeneralTab::on_resetDialogsButton_clicked() } } -void GeneralTab::on_categoriesBtn_clicked() +void GeneralSettingsTab::on_categoriesBtn_clicked() { CategoriesDialog dialog(parentWidget()); if (dialog.exec() == QDialog::Accepted) { diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 1f1b4637..c7fcae36 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -4,10 +4,10 @@ #include "settingsdialog.h" #include "settings.h" -class GeneralTab : public SettingsTab +class GeneralSettingsTab : public SettingsTab { public: - GeneralTab(Settings *m_parent, SettingsDialog &m_dialog); + GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); void update(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp new file mode 100644 index 00000000..7d4414fd --- /dev/null +++ b/src/settingsdialognexus.cpp @@ -0,0 +1,374 @@ +#include "settingsdialognexus.h" +#include "ui_settingsdialog.h" +#include "ui_nexusmanualkey.h" +#include "nexusinterface.h" +#include + +namespace shell = MOBase::shell; + +template +class ServerItem : public QListWidgetItem { +public: + ServerItem(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) + : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} + + virtual bool operator< ( const QListWidgetItem & other ) const { + return this->data(m_SortRole).value() < other.data(m_SortRole).value(); + } +private: + int m_SortRole; +}; + + +class NexusManualKeyDialog : public QDialog +{ +public: + NexusManualKeyDialog(QWidget* parent) + : QDialog(parent), ui(new Ui::NexusManualKeyDialog) + { + ui->setupUi(this); + ui->key->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + + connect(ui->openBrowser, &QPushButton::clicked, [&]{ openBrowser(); }); + connect(ui->paste, &QPushButton::clicked, [&]{ paste(); }); + connect(ui->clear, &QPushButton::clicked, [&]{ clear(); }); + } + + void accept() override + { + m_key = ui->key->toPlainText(); + QDialog::accept(); + } + + const QString& key() const + { + return m_key; + } + + void openBrowser() + { + shell::OpenLink(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); + } + + void paste() + { + const auto text = QApplication::clipboard()->text(); + if (!text.isEmpty()) { + ui->key->setPlainText(text); + } + } + + void clear() + { + ui->key->clear(); + } + +private: + std::unique_ptr ui; + QString m_key; +}; + + +NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) + : SettingsTab(parent, dialog) +{ + ui->offlineBox->setChecked(parent->offlineMode()); + ui->proxyBox->setChecked(parent->useProxy()); + ui->endorsementBox->setChecked(parent->endorsementIntegration()); + ui->hideAPICounterBox->setChecked(parent->hideAPICounter()); + + // display server preferences + m_Settings.beginGroup("Servers"); + for (const QString &key : m_Settings.childKeys()) { + QVariantMap val = m_Settings.value(key).toMap(); + QString descriptor = key; + if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { + descriptor += QStringLiteral(" (automatic)"); + } + if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { + int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); + descriptor += QString(" (%1 kbps)").arg(bps / 1024); + } + + QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); + + newItem->setData(Qt::UserRole, key); + newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); + if (val["preferred"].toInt() > 0) { + ui->preferredServersList->addItem(newItem); + } else { + ui->knownServersList->addItem(newItem); + } + ui->preferredServersList->sortItems(Qt::DescendingOrder); + } + m_Settings.endGroup(); + + QObject::connect(ui->nexusConnect, &QPushButton::clicked, [&]{ on_nexusConnect_clicked(); }); + QObject::connect(ui->nexusManualKey, &QPushButton::clicked, [&]{ on_nexusManualKey_clicked(); }); + QObject::connect(ui->nexusDisconnect, &QPushButton::clicked, [&]{ on_nexusDisconnect_clicked(); }); + QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ on_clearCacheButton_clicked(); }); + QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ on_associateButton_clicked(); }); + + updateNexusState(); +} + +void NexusSettingsTab::update() +{ + /* + if (m_loginCheckBox->isChecked()) { + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", m_usernameEdit->text()); + m_Settings.setValue("Settings/nexus_password", obfuscate(m_passwordEdit->text())); + } else { + m_Settings.setValue("Settings/nexus_login", false); + m_Settings.remove("Settings/nexus_username"); + m_Settings.remove("Settings/nexus_password"); + } + */ + m_Settings.setValue("Settings/offline_mode", ui->offlineBox->isChecked()); + m_Settings.setValue("Settings/use_proxy", ui->proxyBox->isChecked()); + m_Settings.setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked()); + m_Settings.setValue("Settings/hide_api_counter", ui->hideAPICounterBox->isChecked()); + + // store server preference + m_Settings.beginGroup("Servers"); + for (int i = 0; i < ui->knownServersList->count(); ++i) { + QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = 0; + m_Settings.setValue(key, val); + } + int count = ui->preferredServersList->count(); + for (int i = 0; i < count; ++i) { + QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = count - i; + m_Settings.setValue(key, val); + } + m_Settings.endGroup(); +} + +void NexusSettingsTab::on_nexusConnect_clicked() +{ + if (m_nexusLogin && m_nexusLogin->isActive()) { + m_nexusLogin->cancel(); + return; + } + + if (!m_nexusLogin) { + m_nexusLogin.reset(new NexusSSOLogin); + + m_nexusLogin->keyChanged = [&](auto&& s){ + onSSOKeyChanged(s); + }; + + m_nexusLogin->stateChanged = [&](auto&& s, auto&& e){ + onSSOStateChanged(s, e); + }; + } + + ui->nexusLog->clear(); + m_nexusLogin->start(); + updateNexusState(); +} + +void NexusSettingsTab::on_nexusManualKey_clicked() +{ + if (m_nexusValidator && m_nexusValidator->isActive()) { + m_nexusValidator->cancel(); + return; + } + + NexusManualKeyDialog dialog(parentWidget()); + if (dialog.exec() != QDialog::Accepted) { + return; + } + + const auto key = dialog.key(); + if (key.isEmpty()) { + clearKey(); + return; + } + + ui->nexusLog->clear(); + validateKey(key); +} + +void NexusSettingsTab::on_nexusDisconnect_clicked() +{ + clearKey(); + ui->nexusLog->clear(); + addNexusLog(QObject::tr("Disconnected.")); +} + +void NexusSettingsTab::on_clearCacheButton_clicked() +{ + QDir(Settings::instance().getCacheDirectory()).removeRecursively(); + NexusInterface::instance(m_dialog.m_PluginContainer)->clearCache(); +} + +void NexusSettingsTab::on_associateButton_clicked() +{ + Settings::instance().registerAsNXMHandler(true); +} + +void NexusSettingsTab::validateKey(const QString& key) +{ + if (!m_nexusValidator) { + m_nexusValidator.reset(new NexusKeyValidator( + *NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager())); + + m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ + onValidatorStateChanged(s, e); + }; + + m_nexusValidator->finished = [&](auto&& user) { + onValidatorFinished(user); + }; + } + + addNexusLog(QObject::tr("Checking API key...")); + m_nexusValidator->start(key); +} + +void NexusSettingsTab::onSSOKeyChanged(const QString& key) +{ + if (key.isEmpty()) { + clearKey(); + } else { + addNexusLog(QObject::tr("Received API key.")); + validateKey(key); + } +} + +void NexusSettingsTab::onSSOStateChanged(NexusSSOLogin::States s, const QString& e) +{ + if (s != NexusSSOLogin::Finished) { + // finished state is handled in onSSOKeyChanged() + const auto log = NexusSSOLogin::stateToString(s, e); + + for (auto&& line : log.split("\n")) { + addNexusLog(line); + } + } + + updateNexusState(); +} + +void NexusSettingsTab::onValidatorStateChanged( + NexusKeyValidator::States s, const QString& e) +{ + if (s != NexusKeyValidator::Finished) { + // finished state is handled in onValidatorFinished() + const auto log = NexusKeyValidator::stateToString(s, e); + + for (auto&& line : log.split("\n")) { + addNexusLog(line); + } + } + + updateNexusState(); +} + +void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user) +{ + NexusInterface::instance(m_dialog.m_PluginContainer)->setUserAccount(user); + + if (!user.apiKey().isEmpty()) { + if (setKey(user.apiKey())) { + addNexusLog(QObject::tr("Linked with Nexus successfully.")); + } + } +} + +void NexusSettingsTab::addNexusLog(const QString& s) +{ + ui->nexusLog->addItem(s); + ui->nexusLog->scrollToBottom(); +} + +bool NexusSettingsTab::setKey(const QString& key) +{ + m_dialog.m_keyChanged = true; + const bool ret = m_parent->setNexusApiKey(key); + updateNexusState(); + return ret; +} + +bool NexusSettingsTab::clearKey() +{ + m_dialog.m_keyChanged = true; + const auto ret = m_parent->clearNexusApiKey(); + + NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager()->clearApiKey(); + updateNexusState(); + + return ret; +} + +void NexusSettingsTab::updateNexusState() +{ + updateNexusButtons(); + updateNexusData(); +} + +void NexusSettingsTab::updateNexusButtons() +{ + if (m_nexusLogin && m_nexusLogin->isActive()) { + // api key is in the process of being retrieved + ui->nexusConnect->setText(QObject::tr("Cancel")); + ui->nexusConnect->setEnabled(true); + ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setText(QObject::tr("Enter API Key Manually")); + ui->nexusManualKey->setEnabled(false); + } + else if (m_nexusValidator && m_nexusValidator->isActive()) { + // api key is in the process of being tested + ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); + ui->nexusConnect->setEnabled(false); + ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setText(QObject::tr("Cancel")); + ui->nexusManualKey->setEnabled(true); + } + else if (m_parent->hasNexusApiKey()) { + // api key is present + ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); + ui->nexusConnect->setEnabled(false); + ui->nexusDisconnect->setEnabled(true); + ui->nexusManualKey->setText(QObject::tr("Enter API Key Manually")); + ui->nexusManualKey->setEnabled(false); + } else { + // api key not present + ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); + ui->nexusConnect->setEnabled(true); + ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setText(QObject::tr("Enter API Key Manually")); + ui->nexusManualKey->setEnabled(true); + } +} + +void NexusSettingsTab::updateNexusData() +{ + const auto user = NexusInterface::instance(m_dialog.m_PluginContainer) + ->getAPIUserAccount(); + + if (user.isValid()) { + ui->nexusUserID->setText(user.id()); + ui->nexusName->setText(user.name()); + ui->nexusAccount->setText(localizedUserAccountType(user.type())); + + ui->nexusDailyRequests->setText(QString("%1/%2") + .arg(user.limits().remainingDailyRequests) + .arg(user.limits().maxDailyRequests)); + + ui->nexusHourlyRequests->setText(QString("%1/%2") + .arg(user.limits().remainingHourlyRequests) + .arg(user.limits().maxHourlyRequests)); + } else { + ui->nexusUserID->setText(QObject::tr("N/A")); + ui->nexusName->setText(QObject::tr("N/A")); + ui->nexusAccount->setText(QObject::tr("N/A")); + ui->nexusDailyRequests->setText(QObject::tr("N/A")); + ui->nexusHourlyRequests->setText(QObject::tr("N/A")); + } +} diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h new file mode 100644 index 00000000..5c01f61f --- /dev/null +++ b/src/settingsdialognexus.h @@ -0,0 +1,40 @@ +#ifndef SETTINGSDIALOGNEXUS_H +#define SETTINGSDIALOGNEXUS_H + +#include "settings.h" +#include "settingsdialog.h" + +class NexusSettingsTab : public SettingsTab +{ +public: + NexusSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + void update(); + +private: + std::unique_ptr m_nexusLogin; + std::unique_ptr m_nexusValidator; + + void on_nexusConnect_clicked(); + void on_nexusManualKey_clicked(); + void on_nexusDisconnect_clicked(); + void on_clearCacheButton_clicked(); + void on_associateButton_clicked(); + + void validateKey(const QString& key); + bool setKey(const QString& key); + bool clearKey(); + + void updateNexusState(); + void updateNexusButtons(); + void updateNexusData(); + + void onSSOKeyChanged(const QString& key); + void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); + + void onValidatorStateChanged(NexusKeyValidator::States s, const QString& e); + void onValidatorFinished(const APIUserAccount& user); + + void addNexusLog(const QString& s); +}; + +#endif // SETTINGSDIALOGNEXUS_H diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 303d1562..6e8fe994 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -3,7 +3,7 @@ #include "appconfig.h" #include -PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) +PathsSettingsTab::PathsSettingsTab(Settings *parent, SettingsDialog &dialog) : SettingsTab(parent, dialog) { ui->baseDirEdit->setText(m_parent->getBaseDirectory()); @@ -38,7 +38,7 @@ PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) QObject::connect(ui->profilesDirEdit, &QLineEdit::editingFinished, [&]{ on_profilesDirEdit_editingFinished(); }); } -void PathsTab::update() +void PathsSettingsTab::update() { typedef std::tuple Directory; @@ -91,7 +91,7 @@ void PathsTab::update() } } -void PathsTab::on_browseBaseDirBtn_clicked() +void PathsSettingsTab::on_browseBaseDirBtn_clicked() { QString temp = QFileDialog::getExistingDirectory( parentWidget(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); @@ -100,7 +100,7 @@ void PathsTab::on_browseBaseDirBtn_clicked() } } -void PathsTab::on_browseDownloadDirBtn_clicked() +void PathsSettingsTab::on_browseDownloadDirBtn_clicked() { QString searchPath = ui->downloadDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); @@ -111,7 +111,7 @@ void PathsTab::on_browseDownloadDirBtn_clicked() } } -void PathsTab::on_browseModDirBtn_clicked() +void PathsSettingsTab::on_browseModDirBtn_clicked() { QString searchPath = ui->modDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); @@ -122,7 +122,7 @@ void PathsTab::on_browseModDirBtn_clicked() } } -void PathsTab::on_browseCacheDirBtn_clicked() +void PathsSettingsTab::on_browseCacheDirBtn_clicked() { QString searchPath = ui->cacheDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); @@ -133,7 +133,7 @@ void PathsTab::on_browseCacheDirBtn_clicked() } } -void PathsTab::on_browseProfilesDirBtn_clicked() +void PathsSettingsTab::on_browseProfilesDirBtn_clicked() { QString searchPath = ui->profilesDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); @@ -144,7 +144,7 @@ void PathsTab::on_browseProfilesDirBtn_clicked() } } -void PathsTab::on_browseOverwriteDirBtn_clicked() +void PathsSettingsTab::on_browseOverwriteDirBtn_clicked() { QString searchPath = ui->overwriteDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); @@ -155,7 +155,7 @@ void PathsTab::on_browseOverwriteDirBtn_clicked() } } -void PathsTab::on_browseGameDirBtn_clicked() +void PathsSettingsTab::on_browseGameDirBtn_clicked() { QFileInfo oldGameExe(ui->managedGameDirEdit->text()); @@ -165,37 +165,37 @@ void PathsTab::on_browseGameDirBtn_clicked() } } -void PathsTab::on_baseDirEdit_editingFinished() +void PathsSettingsTab::on_baseDirEdit_editingFinished() { normalizePath(ui->baseDirEdit); } -void PathsTab::on_downloadDirEdit_editingFinished() +void PathsSettingsTab::on_downloadDirEdit_editingFinished() { normalizePath(ui->downloadDirEdit); } -void PathsTab::on_modDirEdit_editingFinished() +void PathsSettingsTab::on_modDirEdit_editingFinished() { normalizePath(ui->modDirEdit); } -void PathsTab::on_cacheDirEdit_editingFinished() +void PathsSettingsTab::on_cacheDirEdit_editingFinished() { normalizePath(ui->cacheDirEdit); } -void PathsTab::on_profilesDirEdit_editingFinished() +void PathsSettingsTab::on_profilesDirEdit_editingFinished() { normalizePath(ui->profilesDirEdit); } -void PathsTab::on_overwriteDirEdit_editingFinished() +void PathsSettingsTab::on_overwriteDirEdit_editingFinished() { normalizePath(ui->overwriteDirEdit); } -void PathsTab::normalizePath(QLineEdit *lineEdit) +void PathsSettingsTab::normalizePath(QLineEdit *lineEdit) { QString text = lineEdit->text(); while (text.endsWith('/') || text.endsWith('\\')) { diff --git a/src/settingsdialogpaths.h b/src/settingsdialogpaths.h index dac402b1..f661b624 100644 --- a/src/settingsdialogpaths.h +++ b/src/settingsdialogpaths.h @@ -4,10 +4,10 @@ #include "settings.h" #include "settingsdialog.h" -class PathsTab : public SettingsTab +class PathsSettingsTab : public SettingsTab { public: - PathsTab(Settings *parent, SettingsDialog &dialog); + PathsSettingsTab(Settings *parent, SettingsDialog &dialog); void update(); -- cgit v1.3.1 From d91d0caba5fac3b2b27698a5e6ab4a9b60efbf53 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 09:47:09 -0400 Subject: removed incorrect warning that steam password is unencrypted split steam tab --- src/CMakeLists.txt | 3 +++ src/settings.cpp | 36 +++++----------------------- src/settings.h | 13 ---------- src/settingsdialog.ui | 58 ++++++++++++++++----------------------------- src/settingsdialogsteam.cpp | 17 +++++++++++++ src/settingsdialogsteam.h | 17 +++++++++++++ 6 files changed, 64 insertions(+), 80 deletions(-) create mode 100644 src/settingsdialogsteam.cpp create mode 100644 src/settingsdialogsteam.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b2407e17..a1adf2db 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,6 +40,7 @@ SET(organizer_SRCS settingsdialoggeneral.cpp settingsdialognexus.cpp settingsdialogpaths.cpp + settingsdialogsteam.cpp settings.cpp selfupdater.cpp selectiondialog.cpp @@ -157,6 +158,7 @@ SET(organizer_HDRS settingsdialoggeneral.h settingsdialognexus.h settingsdialogpaths.h + settingsdialogsteam.h settings.h selfupdater.h selectiondialog.h @@ -440,6 +442,7 @@ set(settings settingsdialoggeneral settingsdialognexus settingsdialogpaths + settingsdialogsteam ) set(utilities diff --git a/src/settings.cpp b/src/settings.cpp index bded470c..26c9720a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "settingsdialoggeneral.h" #include "settingsdialognexus.h" #include "settingsdialogpaths.h" +#include "settingsdialogsteam.h" #include "versioninfo.h" #include "appconfig.h" #include "organizercore.h" @@ -391,15 +392,10 @@ bool Settings::hasNexusApiKey() const bool Settings::getSteamLogin(QString &username, QString &password) const { - if (m_Settings.contains("Settings/steam_username")) { - QString tempPass = deObfuscate("steam_password"); - if (!tempPass.isEmpty()) { - username = m_Settings.value("Settings/steam_username").toString(); - password = tempPass; - return true; - } - } - return false; + username = m_Settings.value("Settings/steam_username", "").toString(); + password = deObfuscate("steam_password"); + + return !username.isEmpty() && !password.isEmpty(); } bool Settings::compactDownloads() const { @@ -680,7 +676,7 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) tabs.push_back(std::unique_ptr(new PathsSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new DiagnosticsTab(this, dialog))); tabs.push_back(std::unique_ptr(new NexusSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new SteamTab(this, dialog))); + tabs.push_back(std::unique_ptr(new SteamSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new PluginsTab(this, dialog))); tabs.push_back(std::unique_ptr(new WorkaroundsTab(this, dialog))); @@ -770,26 +766,6 @@ void Settings::DiagnosticsTab::update() } -Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) - , m_steamUserEdit(m_dialog.findChild("steamUserEdit")) - , m_steamPassEdit(m_dialog.findChild("steamPassEdit")) -{ - if (m_Settings.contains("Settings/steam_username")) { - m_steamUserEdit->setText(m_Settings.value("Settings/steam_username", "").toString()); - QString password = deObfuscate("steam_password"); - if (!password.isEmpty()) { - m_steamPassEdit->setText(password); - } - } -} - -void Settings::SteamTab::update() -{ - //FIXME this should be inlined here? - m_parent->setSteamLogin(m_steamUserEdit->text(), m_steamPassEdit->text()); -} - Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) , m_pluginsList(m_dialog.findChild("pluginsList")) diff --git a/src/settings.h b/src/settings.h index b9383ce4..5bf705d1 100644 --- a/src/settings.h +++ b/src/settings.h @@ -461,19 +461,6 @@ private: void setLevelsBox(); }; - /** Display/store the configuration in the 'steam' tab of the settings dialogue */ - class SteamTab : public SettingsTab - { - public: - SteamTab(Settings *m_parent, SettingsDialog &m_dialog); - - void update(); - - private: - QLineEdit *m_steamUserEdit; - QLineEdit *m_steamPassEdit; - }; - /** Display/store the configuration in the 'plugins' tab of the settings dialogue */ class PluginsTab : public SettingsTab { diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 1e94bcde..3ad525e1 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -828,71 +828,55 @@ If you use pre-releases, never contact me directly by e-mail or via private mess Steam - + Username - - - - - - - Password - - - - - - - QLineEdit::Password - - - - - + + Qt::Vertical - QSizePolicy::Minimum + QSizePolicy::Expanding 20 - 40 + 232 - + + + + - If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. + <html><head/><body><p>If you save your steam user ID and password here, they will be used when logging into steam.</p></body></html> true - - - - Qt::Vertical - - - QSizePolicy::Expanding + + + + Password - - - 20 - 232 - + + + + + + QLineEdit::Password - + diff --git a/src/settingsdialogsteam.cpp b/src/settingsdialogsteam.cpp new file mode 100644 index 00000000..34c2d76b --- /dev/null +++ b/src/settingsdialogsteam.cpp @@ -0,0 +1,17 @@ +#include "settingsdialogsteam.h" +#include "ui_settingsdialog.h" + +SteamSettingsTab::SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : SettingsTab(m_parent, m_dialog) +{ + QString username, password; + m_parent->getSteamLogin(username, password); + + ui->steamUserEdit->setText(username); + ui->steamPassEdit->setText(password); +} + +void SteamSettingsTab::update() +{ + m_parent->setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); +} diff --git a/src/settingsdialogsteam.h b/src/settingsdialogsteam.h new file mode 100644 index 00000000..dbd85151 --- /dev/null +++ b/src/settingsdialogsteam.h @@ -0,0 +1,17 @@ +#ifndef SETTINGSDIALOGSTEAM_H +#define SETTINGSDIALOGSTEAM_H + +#include "settings.h" +#include "settingsdialog.h" + +class SteamSettingsTab : public SettingsTab +{ +public: + SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + +private: +}; + +#endif // SETTINGSDIALOGSTEAM_H -- cgit v1.3.1 From 55eafd62dd3c96f363cde4537061e7f03ae8fd0a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 10:10:49 -0400 Subject: split plugins tab --- src/CMakeLists.txt | 3 ++ src/settings.cpp | 54 +++------------------ src/settings.h | 25 +++------- src/settingsdialog.cpp | 52 -------------------- src/settingsdialog.h | 4 -- src/settingsdialogplugins.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++ src/settingsdialogplugins.h | 20 ++++++++ 7 files changed, 146 insertions(+), 122 deletions(-) create mode 100644 src/settingsdialogplugins.cpp create mode 100644 src/settingsdialogplugins.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1adf2db..a8ded510 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -40,6 +40,7 @@ SET(organizer_SRCS settingsdialoggeneral.cpp settingsdialognexus.cpp settingsdialogpaths.cpp + settingsdialogplugins.cpp settingsdialogsteam.cpp settings.cpp selfupdater.cpp @@ -158,6 +159,7 @@ SET(organizer_HDRS settingsdialoggeneral.h settingsdialognexus.h settingsdialogpaths.h + settingsdialogplugins.h settingsdialogsteam.h settings.h selfupdater.h @@ -442,6 +444,7 @@ set(settings settingsdialoggeneral settingsdialognexus settingsdialogpaths + settingsdialogplugins settingsdialogsteam ) diff --git a/src/settings.cpp b/src/settings.cpp index 26c9720a..bc45b720 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "settingsdialoggeneral.h" #include "settingsdialognexus.h" #include "settingsdialogpaths.h" +#include "settingsdialogplugins.h" #include "settingsdialogsteam.h" #include "versioninfo.h" #include "appconfig.h" @@ -677,7 +678,7 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) tabs.push_back(std::unique_ptr(new DiagnosticsTab(this, dialog))); tabs.push_back(std::unique_ptr(new NexusSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new SteamSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PluginsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new PluginsSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new WorkaroundsTab(this, dialog))); @@ -687,6 +688,11 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) } if (dialog.exec() == QDialog::Accepted) { + + for (auto&& tab : tabs) { + tab->closing(); + } + // remember settings before change QMap before; m_Settings.beginGroup("Settings"); @@ -766,52 +772,6 @@ void Settings::DiagnosticsTab::update() } -Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) - , m_pluginsList(m_dialog.findChild("pluginsList")) - , m_pluginBlacklistList(m_dialog.findChild("pluginBlacklist")) -{ - // display plugin settings - QSet handledNames; - for (IPlugin *plugin : m_parent->m_Plugins) { - if (handledNames.contains(plugin->name())) - continue; - QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), m_pluginsList); - listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, m_parent->m_PluginSettings[plugin->name()]); - listItem->setData(Qt::UserRole + 2, m_parent->m_PluginDescriptions[plugin->name()]); - m_pluginsList->addItem(listItem); - handledNames.insert(plugin->name()); - } - - // display plugin blacklist - for (const QString &pluginName : m_parent->m_PluginBlacklist) { - m_pluginBlacklistList->addItem(pluginName); - } -} - -void Settings::PluginsTab::update() -{ - // transfer plugin settings to in-memory structure - for (int i = 0; i < m_pluginsList->count(); ++i) { - QListWidgetItem *item = m_pluginsList->item(i); - m_parent->m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); - } - // store plugin settings on disc - for (auto iterPlugins = m_parent->m_PluginSettings.begin(); iterPlugins != m_parent->m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { - m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); - } - } - - // store plugin blacklist - m_parent->m_PluginBlacklist.clear(); - for (QListWidgetItem *item : m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { - m_parent->m_PluginBlacklist.insert(item->text()); - } - m_parent->writePluginBlacklist(); -} - Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) diff --git a/src/settings.h b/src/settings.h index 5bf705d1..5298103a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -70,6 +70,7 @@ public: virtual ~SettingsTab(); virtual void update() = 0; + virtual void closing() {} protected: Settings *m_parent; @@ -426,8 +427,13 @@ public: */ bool colorSeparatorScrollbar() const; + // temp QSettings& settingsRef() { return m_Settings; } MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } + QMap m_PluginSettings; + QMap m_PluginDescriptions; + QSet m_PluginBlacklist; + void writePluginBlacklist(); public slots: @@ -440,7 +446,6 @@ private: static QString deObfuscate(const QString key); void readPluginBlacklist(); - void writePluginBlacklist(); QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; @@ -461,19 +466,6 @@ private: void setLevelsBox(); }; - /** Display/store the configuration in the 'plugins' tab of the settings dialogue */ - class PluginsTab : public SettingsTab - { - public: - PluginsTab(Settings *m_parent, SettingsDialog &m_dialog); - - void update(); - - private: - QListWidget *m_pluginsList; - QListWidget *m_pluginBlacklistList; - }; - /** Display/store the configuration in the 'workarounds' tab of the settings dialogue */ class WorkaroundsTab : public SettingsTab { @@ -512,11 +504,6 @@ private: std::vector m_Plugins; - QMap m_PluginSettings; - QMap m_PluginDescriptions; - - QSet m_PluginBlacklist; - }; #endif // SETTINGS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 6d5a8cc0..f43f7ae8 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -99,7 +99,6 @@ void SettingsDialog::accept() return; } - storeSettings(ui->pluginsList->currentItem()); TutorableDialog::accept(); } @@ -149,57 +148,6 @@ void SettingsDialog::on_bsaDateBtn_clicked() dir.absolutePath().toStdWString()); } -void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) -{ - if (pluginItem != nullptr) { - QVariantMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); - - for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { - const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); - settings[item->text(0)] = item->data(1, Qt::DisplayRole); - } - - pluginItem->setData(Qt::UserRole + 1, settings); - } -} - -void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - storeSettings(previous); - - ui->pluginSettingsList->clear(); - IPlugin *plugin = static_cast(current->data(Qt::UserRole).value()); - ui->authorLabel->setText(plugin->author()); - ui->versionLabel->setText(plugin->version().canonicalString()); - ui->descriptionLabel->setText(plugin->description()); - - QVariantMap settings = current->data(Qt::UserRole + 1).toMap(); - QVariantMap descriptions = current->data(Qt::UserRole + 2).toMap(); - ui->pluginSettingsList->setEnabled(settings.count() != 0); - for (auto iter = settings.begin(); iter != settings.end(); ++iter) { - QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); - QVariant value = *iter; - QString description; - { - auto descriptionIter = descriptions.find(iter.key()); - if (descriptionIter != descriptions.end()) { - description = descriptionIter->toString(); - } - } - - ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); - newItem->setData(1, Qt::DisplayRole, value); - newItem->setData(1, Qt::EditRole, value); - newItem->setToolTip(1, description); - - newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); - ui->pluginSettingsList->addTopLevelItem(newItem); - } - - ui->pluginSettingsList->resizeColumnToContents(0); - ui->pluginSettingsList->resizeColumnToContents(1); -} - void SettingsDialog::deleteBlacklistItem() { ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); diff --git a/src/settingsdialog.h b/src/settingsdialog.h index df5d0ad8..319e6ed8 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -62,9 +62,6 @@ public: public slots: virtual void accept(); -private: - void storeSettings(QListWidgetItem *pluginItem); - public: QString getExecutableBlacklist() { return m_ExecutableBlacklist; } void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; } @@ -74,7 +71,6 @@ public: private slots: void on_bsaDateBtn_clicked(); void on_execBlacklistBtn_clicked(); - void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_resetGeometryBtn_clicked(); void deleteBlacklistItem(); diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp new file mode 100644 index 00000000..32269344 --- /dev/null +++ b/src/settingsdialogplugins.cpp @@ -0,0 +1,110 @@ +#include "settingsdialogplugins.h" +#include "ui_settingsdialog.h" +#include "noeditdelegate.h" +#include + +using MOBase::IPlugin; + +PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : SettingsTab(m_parent, m_dialog) +{ + // display plugin settings + QSet handledNames; + for (IPlugin *plugin : m_parent->plugins()) { + if (handledNames.contains(plugin->name())) + continue; + QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), ui->pluginsList); + listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); + listItem->setData(Qt::UserRole + 1, m_parent->m_PluginSettings[plugin->name()]); + listItem->setData(Qt::UserRole + 2, m_parent->m_PluginDescriptions[plugin->name()]); + ui->pluginsList->addItem(listItem); + handledNames.insert(plugin->name()); + } + + // display plugin blacklist + for (const QString &pluginName : m_parent->m_PluginBlacklist) { + ui->pluginBlacklist->addItem(pluginName); + } + + QObject::connect( + ui->pluginsList, &QListWidget::currentItemChanged, + [&](auto* current, auto* previous) { on_pluginsList_currentItemChanged(current, previous); }); +} + +void PluginsSettingsTab::update() +{ + // transfer plugin settings to in-memory structure + for (int i = 0; i < ui->pluginsList->count(); ++i) { + QListWidgetItem *item = ui->pluginsList->item(i); + m_parent->m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); + } + // store plugin settings on disc + for (auto iterPlugins = m_parent->m_PluginSettings.begin(); iterPlugins != m_parent->m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { + m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); + } + } + + // store plugin blacklist + m_parent->m_PluginBlacklist.clear(); + for (QListWidgetItem *item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { + m_parent->m_PluginBlacklist.insert(item->text()); + } + m_parent->writePluginBlacklist(); +} + +void PluginsSettingsTab::closing() +{ + storeSettings(ui->pluginsList->currentItem()); +} + +void PluginsSettingsTab::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + storeSettings(previous); + + ui->pluginSettingsList->clear(); + IPlugin *plugin = static_cast(current->data(Qt::UserRole).value()); + ui->authorLabel->setText(plugin->author()); + ui->versionLabel->setText(plugin->version().canonicalString()); + ui->descriptionLabel->setText(plugin->description()); + + QVariantMap settings = current->data(Qt::UserRole + 1).toMap(); + QVariantMap descriptions = current->data(Qt::UserRole + 2).toMap(); + ui->pluginSettingsList->setEnabled(settings.count() != 0); + for (auto iter = settings.begin(); iter != settings.end(); ++iter) { + QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); + QVariant value = *iter; + QString description; + { + auto descriptionIter = descriptions.find(iter.key()); + if (descriptionIter != descriptions.end()) { + description = descriptionIter->toString(); + } + } + + ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); + newItem->setData(1, Qt::DisplayRole, value); + newItem->setData(1, Qt::EditRole, value); + newItem->setToolTip(1, description); + + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui->pluginSettingsList->addTopLevelItem(newItem); + } + + ui->pluginSettingsList->resizeColumnToContents(0); + ui->pluginSettingsList->resizeColumnToContents(1); +} + +void PluginsSettingsTab::storeSettings(QListWidgetItem *pluginItem) +{ + if (pluginItem != nullptr) { + QVariantMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); + + for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { + const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); + settings[item->text(0)] = item->data(1, Qt::DisplayRole); + } + + pluginItem->setData(Qt::UserRole + 1, settings); + } +} diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h new file mode 100644 index 00000000..48d61858 --- /dev/null +++ b/src/settingsdialogplugins.h @@ -0,0 +1,20 @@ +#ifndef SETTINGSDIALOGPLUGINS_H +#define SETTINGSDIALOGPLUGINS_H + +#include "settings.h" +#include "settingsdialog.h" + +class PluginsSettingsTab : public SettingsTab +{ +public: + PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + void closing() override; + +private: + void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void storeSettings(QListWidgetItem *pluginItem); +}; + +#endif // SETTINGSDIALOGPLUGINS_H -- cgit v1.3.1 From e4dcdb01ac2e3f99fea76b21e1acfd21d0de89c7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 10:45:59 -0400 Subject: split workarounds tab --- src/CMakeLists.txt | 3 ++ src/loadmechanism.cpp | 6 +-- src/loadmechanism.h | 6 +-- src/settings.cpp | 73 +------------------------- src/settings.h | 20 +------ src/settingsdialog.cpp | 51 ------------------ src/settingsdialog.h | 14 +---- src/settingsdialogplugins.cpp | 9 ++++ src/settingsdialogplugins.h | 1 + src/settingsdialogworkarounds.cpp | 108 ++++++++++++++++++++++++++++++++++++++ src/settingsdialogworkarounds.h | 25 +++++++++ 11 files changed, 157 insertions(+), 159 deletions(-) create mode 100644 src/settingsdialogworkarounds.cpp create mode 100644 src/settingsdialogworkarounds.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a8ded510..86ef9721 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,7 @@ SET(organizer_SRCS settingsdialogpaths.cpp settingsdialogplugins.cpp settingsdialogsteam.cpp + settingsdialogworkarounds.cpp settings.cpp selfupdater.cpp selectiondialog.cpp @@ -161,6 +162,7 @@ SET(organizer_HDRS settingsdialogpaths.h settingsdialogplugins.h settingsdialogsteam.h + settingsdialogworkarounds.h settings.h selfupdater.h selectiondialog.h @@ -446,6 +448,7 @@ set(settings settingsdialogpaths settingsdialogplugins settingsdialogsteam + settingsdialogworkarounds ) set(utilities diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 4d6cebd4..2d01562d 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -63,7 +63,7 @@ void LoadMechanism::removeHintFile(QDir targetDirectory) } -bool LoadMechanism::isDirectLoadingSupported() +bool LoadMechanism::isDirectLoadingSupported() const { //FIXME: Seriously? isn't there a 'do i need steam' thing? IPluginGame const *game = qApp->property("managed_game").value(); @@ -76,7 +76,7 @@ bool LoadMechanism::isDirectLoadingSupported() } } -bool LoadMechanism::isScriptExtenderSupported() +bool LoadMechanism::isScriptExtenderSupported() const { IPluginGame const *game = qApp->property("managed_game").value(); ScriptExtender *extender = game->feature(); @@ -85,7 +85,7 @@ bool LoadMechanism::isScriptExtenderSupported() return extender != nullptr && extender->isInstalled(); } -bool LoadMechanism::isProxyDLLSupported() +bool LoadMechanism::isProxyDLLSupported() const { // using steam_api.dll as the proxy is way too game specific as many games will have different // versions of that dll. diff --git a/src/loadmechanism.h b/src/loadmechanism.h index c04473ab..51fefaf9 100644 --- a/src/loadmechanism.h +++ b/src/loadmechanism.h @@ -68,21 +68,21 @@ public: * * @return true if the load mechanism is supported **/ - bool isDirectLoadingSupported(); + bool isDirectLoadingSupported() const; /** * @brief test whether the "Script Extender" load mechanism is supported for the current game * * @return true if the load mechanism is supported **/ - bool isScriptExtenderSupported(); + bool isScriptExtenderSupported() const; /** * @brief test whether the "Proxy DLL" load mechanism is supported for the current game * * @return true if the load mechanism is supported **/ - bool isProxyDLLSupported(); + bool isProxyDLLSupported() const; private: diff --git a/src/settings.cpp b/src/settings.cpp index bc45b720..515ff907 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "settingsdialogpaths.h" #include "settingsdialogplugins.h" #include "settingsdialogsteam.h" +#include "settingsdialogworkarounds.h" #include "versioninfo.h" #include "appconfig.h" #include "organizercore.h" @@ -679,7 +680,7 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) tabs.push_back(std::unique_ptr(new NexusSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new SteamSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new PluginsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new WorkaroundsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(this, dialog))); QString key = QString("geometry/%1").arg(dialog.objectName()); @@ -770,73 +771,3 @@ void Settings::DiagnosticsTab::update() m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); } - - -Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, - SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) - , m_appIDEdit(m_dialog.findChild("appIDEdit")) - , m_mechanismBox(m_dialog.findChild("mechanismBox")) - , m_hideUncheckedBox(m_dialog.findChild("hideUncheckedBox")) - , m_forceEnableBox(m_dialog.findChild("forceEnableBox")) - , m_displayForeignBox(m_dialog.findChild("displayForeignBox")) - , m_lockGUIBox(m_dialog.findChild("lockGUIBox")) - , m_enableArchiveParsingBox(m_dialog.findChild("enableArchiveParsingBox")) - , m_resetGeometriesBtn(m_dialog.findChild("resetGeometryBtn")) -{ - m_appIDEdit->setText(m_parent->getSteamAppID()); - - LoadMechanism::EMechanism mechanismID = m_parent->getLoadMechanism(); - int index = 0; - - if (m_parent->m_LoadMechanism.isDirectLoadingSupported()) { - m_mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); - if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { - index = m_mechanismBox->count() - 1; - } - } - - if (m_parent->m_LoadMechanism.isScriptExtenderSupported()) { - m_mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); - if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { - index = m_mechanismBox->count() - 1; - } - } - - if (m_parent->m_LoadMechanism.isProxyDLLSupported()) { - m_mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); - if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { - index = m_mechanismBox->count() - 1; - } - } - - m_mechanismBox->setCurrentIndex(index); - - m_hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins()); - m_forceEnableBox->setChecked(m_parent->forceEnableCoreFiles()); - m_displayForeignBox->setChecked(m_parent->displayForeign()); - m_lockGUIBox->setChecked(m_parent->lockGUI()); - m_enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); - - m_resetGeometriesBtn->setChecked(m_parent->directInterface().value("reset_geometry", false).toBool()); - - m_dialog.setExecutableBlacklist(m_parent->executablesBlacklist()); - -} - -void Settings::WorkaroundsTab::update() -{ - if (m_appIDEdit->text() != m_parent->m_GamePlugin->steamAPPId()) { - m_Settings.setValue("Settings/app_id", m_appIDEdit->text()); - } else { - m_Settings.remove("Settings/app_id"); - } - m_Settings.setValue("Settings/load_mechanism", m_mechanismBox->itemData(m_mechanismBox->currentIndex()).toInt()); - m_Settings.setValue("Settings/hide_unchecked_plugins", m_hideUncheckedBox->isChecked()); - m_Settings.setValue("Settings/force_enable_core_files", m_forceEnableBox->isChecked()); - m_Settings.setValue("Settings/display_foreign", m_displayForeignBox->isChecked()); - m_Settings.setValue("Settings/lock_gui", m_lockGUIBox->isChecked()); - m_Settings.setValue("Settings/archive_parsing_experimental", m_enableArchiveParsingBox->isChecked()); - - m_Settings.setValue("Settings/executable_blacklist", m_dialog.getExecutableBlacklist()); -} diff --git a/src/settings.h b/src/settings.h index 5298103a..64068173 100644 --- a/src/settings.h +++ b/src/settings.h @@ -434,6 +434,7 @@ public: QMap m_PluginDescriptions; QSet m_PluginBlacklist; void writePluginBlacklist(); + const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } public slots: @@ -466,25 +467,6 @@ private: void setLevelsBox(); }; - /** Display/store the configuration in the 'workarounds' tab of the settings dialogue */ - class WorkaroundsTab : public SettingsTab - { - public: - WorkaroundsTab(Settings *m_parent, SettingsDialog &m_dialog); - - void update(); - - private: - QLineEdit *m_appIDEdit; - QComboBox *m_mechanismBox; - QCheckBox *m_hideUncheckedBox; - QCheckBox *m_forceEnableBox; - QCheckBox *m_displayForeignBox; - QCheckBox *m_lockGUIBox; - QCheckBox *m_enableArchiveParsingBox; - QPushButton *m_resetGeometriesBtn; - }; - private slots: signals: diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index f43f7ae8..76b0a146 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -58,10 +58,6 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti { ui->setupUi(this); ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); - - QShortcut *delShortcut = new QShortcut( - QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); } SettingsDialog::~SettingsDialog() @@ -111,50 +107,3 @@ bool SettingsDialog::getApiKeyChanged() { return m_keyChanged; } - -void SettingsDialog::on_execBlacklistBtn_clicked() -{ - bool ok = false; - QString result = QInputDialog::getMultiLineText( - this, - tr("Executables Blacklist"), - tr("Enter one executable per line to be blacklisted from the virtual file system.\n" - "Mods and other virtualized files will not be visible to these executables and\n" - "any executables launched by them.\n\n" - "Example:\n" - " Chrome.exe\n" - " Firefox.exe"), - m_ExecutableBlacklist.split(";").join("\n"), - &ok - ); - if (ok) { - QStringList blacklist; - for (auto exec : result.split("\n")) { - if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) { - blacklist << exec.trimmed(); - } - } - m_ExecutableBlacklist = blacklist.join(";"); - } -} - -void SettingsDialog::on_bsaDateBtn_clicked() -{ - IPluginGame const *game - = qApp->property("managed_game").value(); - QDir dir = game->dataDirectory(); - - Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), - dir.absolutePath().toStdWString()); -} - -void SettingsDialog::deleteBlacklistItem() -{ - ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); -} - -void SettingsDialog::on_resetGeometryBtn_clicked() -{ - m_GeometriesReset = true; - ui->resetGeometryBtn->setChecked(true); -} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 319e6ed8..81c17f44 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -57,30 +57,20 @@ public: // temp Ui::SettingsDialog *ui; bool m_keyChanged; + bool m_GeometriesReset; PluginContainer *m_PluginContainer; public slots: virtual void accept(); public: - QString getExecutableBlacklist() { return m_ExecutableBlacklist; } - void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; } - bool getResetGeometries(); bool getApiKeyChanged(); - -private slots: - void on_bsaDateBtn_clicked(); - void on_execBlacklistBtn_clicked(); - void on_resetGeometryBtn_clicked(); - - void deleteBlacklistItem(); + bool getResetGeometries(); private: Settings* m_settings; - bool m_GeometriesReset; - QString m_ExecutableBlacklist; }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 32269344..33bc1563 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -29,6 +29,10 @@ PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dia QObject::connect( ui->pluginsList, &QListWidget::currentItemChanged, [&](auto* current, auto* previous) { on_pluginsList_currentItemChanged(current, previous); }); + + QShortcut *delShortcut = new QShortcut( + QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + QObject::connect(delShortcut, &QShortcut::activated, parentWidget(), [&]{ deleteBlacklistItem(); }); } void PluginsSettingsTab::update() @@ -95,6 +99,11 @@ void PluginsSettingsTab::on_pluginsList_currentItemChanged(QListWidgetItem *curr ui->pluginSettingsList->resizeColumnToContents(1); } +void PluginsSettingsTab::deleteBlacklistItem() +{ + ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); +} + void PluginsSettingsTab::storeSettings(QListWidgetItem *pluginItem) { if (pluginItem != nullptr) { diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index 48d61858..9d21daa6 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -14,6 +14,7 @@ public: private: void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void deleteBlacklistItem(); void storeSettings(QListWidgetItem *pluginItem); }; diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp new file mode 100644 index 00000000..4cca5fd4 --- /dev/null +++ b/src/settingsdialogworkarounds.cpp @@ -0,0 +1,108 @@ +#include "settingsdialogworkarounds.h" +#include "ui_settingsdialog.h" +#include "helper.h" +#include + +WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : SettingsTab(m_parent, m_dialog) +{ + ui->appIDEdit->setText(m_parent->getSteamAppID()); + + LoadMechanism::EMechanism mechanismID = m_parent->getLoadMechanism(); + int index = 0; + + if (m_parent->loadMechanism().isDirectLoadingSupported()) { + ui->mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); + if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { + index = ui->mechanismBox->count() - 1; + } + } + + if (m_parent->loadMechanism().isScriptExtenderSupported()) { + ui->mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); + if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { + index = ui->mechanismBox->count() - 1; + } + } + + if (m_parent->loadMechanism().isProxyDLLSupported()) { + ui->mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); + if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { + index = ui->mechanismBox->count() - 1; + } + } + + ui->mechanismBox->setCurrentIndex(index); + + ui->hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins()); + ui->forceEnableBox->setChecked(m_parent->forceEnableCoreFiles()); + ui->displayForeignBox->setChecked(m_parent->displayForeign()); + ui->lockGUIBox->setChecked(m_parent->lockGUI()); + ui->enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); + + ui->resetGeometryBtn->setChecked(m_parent->directInterface().value("reset_geometry", false).toBool()); + + setExecutableBlacklist(m_parent->executablesBlacklist()); + + QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); + QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); }); + QObject::connect(ui->resetGeometryBtn, &QPushButton::clicked, [&]{ on_resetGeometryBtn_clicked(); }); +} + +void WorkaroundsSettingsTab::update() +{ + if (ui->appIDEdit->text() != m_parent->gamePlugin()->steamAPPId()) { + m_Settings.setValue("Settings/app_id", ui->appIDEdit->text()); + } else { + m_Settings.remove("Settings/app_id"); + } + m_Settings.setValue("Settings/load_mechanism", ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt()); + m_Settings.setValue("Settings/hide_unchecked_plugins", ui->hideUncheckedBox->isChecked()); + m_Settings.setValue("Settings/force_enable_core_files", ui->forceEnableBox->isChecked()); + m_Settings.setValue("Settings/display_foreign", ui->displayForeignBox->isChecked()); + m_Settings.setValue("Settings/lock_gui", ui->lockGUIBox->isChecked()); + m_Settings.setValue("Settings/archive_parsing_experimental", ui->enableArchiveParsingBox->isChecked()); + + m_Settings.setValue("Settings/executable_blacklist", getExecutableBlacklist()); +} + +void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() +{ + bool ok = false; + QString result = QInputDialog::getMultiLineText( + parentWidget(), + QObject::tr("Executables Blacklist"), + QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n" + "Mods and other virtualized files will not be visible to these executables and\n" + "any executables launched by them.\n\n" + "Example:\n" + " Chrome.exe\n" + " Firefox.exe"), + m_ExecutableBlacklist.split(";").join("\n"), + &ok + ); + if (ok) { + QStringList blacklist; + for (auto exec : result.split("\n")) { + if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) { + blacklist << exec.trimmed(); + } + } + m_ExecutableBlacklist = blacklist.join(";"); + } +} + +void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() +{ + const auto* game = qApp->property("managed_game").value(); + QDir dir = game->dataDirectory(); + + Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), + dir.absolutePath().toStdWString()); +} + +void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() +{ + m_dialog.m_GeometriesReset = true; + ui->resetGeometryBtn->setChecked(true); +} diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h new file mode 100644 index 00000000..1687624b --- /dev/null +++ b/src/settingsdialogworkarounds.h @@ -0,0 +1,25 @@ +#ifndef SETTINGSDIALOGWORKAROUNDS_H +#define SETTINGSDIALOGWORKAROUNDS_H + +#include "settings.h" +#include "settingsdialog.h" + +class WorkaroundsSettingsTab : public SettingsTab +{ +public: + WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + +private: + QString m_ExecutableBlacklist; + + void on_bsaDateBtn_clicked(); + void on_execBlacklistBtn_clicked(); + void on_resetGeometryBtn_clicked(); + + QString getExecutableBlacklist() { return m_ExecutableBlacklist; } + void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; } +}; + +#endif // SETTINGSDIALOGWORKAROUNDS_H -- cgit v1.3.1 From e8d7930edacdc04a4607ecd59fc402f2f04ea39d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 10:59:07 -0400 Subject: split diagnostics tab fixed log and crash dump directory links in label not working --- src/CMakeLists.txt | 3 +++ src/settings.cpp | 32 ++------------------------------ src/settings.h | 18 ------------------ src/settingsdialog.ui | 3 +++ src/settingsdialogdiagnostics.cpp | 28 ++++++++++++++++++++++++++++ src/settingsdialogdiagnostics.h | 17 +++++++++++++++++ 6 files changed, 53 insertions(+), 48 deletions(-) create mode 100644 src/settingsdialogdiagnostics.cpp create mode 100644 src/settingsdialogdiagnostics.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86ef9721..d8316e7e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,6 +37,7 @@ SET(organizer_SRCS spawn.cpp singleinstance.cpp settingsdialog.cpp + settingsdialogdiagnostics.cpp settingsdialoggeneral.cpp settingsdialognexus.cpp settingsdialogpaths.cpp @@ -157,6 +158,7 @@ SET(organizer_HDRS spawn.h singleinstance.h settingsdialog.h + settingsdialogdiagnostics.h settingsdialoggeneral.h settingsdialognexus.h settingsdialogpaths.h @@ -443,6 +445,7 @@ set(profiles set(settings settings settingsdialog + settingsdialogdiagnostics settingsdialoggeneral settingsdialognexus settingsdialogpaths diff --git a/src/settings.cpp b/src/settings.cpp index 515ff907..725b7e06 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see . #include "pluginsetting.h" #include "serverinfo.h" #include "settingsdialog.h" +#include "settingsdialogdiagnostics.h" #include "settingsdialoggeneral.h" #include "settingsdialognexus.h" #include "settingsdialogpaths.h" @@ -676,7 +677,7 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) tabs.push_back(std::unique_ptr(new GeneralSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new PathsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new DiagnosticsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new DiagnosticsSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new NexusSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new SteamSettingsTab(this, dialog))); tabs.push_back(std::unique_ptr(new PluginsSettingsTab(this, dialog))); @@ -742,32 +743,3 @@ void Settings::query(PluginContainer *pluginContainer, QWidget *parent) } } - - -Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) - , m_logLevelBox(m_dialog.findChild("logLevelBox")) - , m_dumpsTypeBox(m_dialog.findChild("dumpsTypeBox")) - , m_dumpsMaxEdit(m_dialog.findChild("dumpsMaxEdit")) - , m_diagnosticsExplainedLabel(m_dialog.findChild("diagnosticsExplainedLabel")) -{ - setLevelsBox(); - m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); - m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); - QString logsPath = qApp->property("dataPath").toString() - + "/" + QString::fromStdWString(AppConfig::logPath()); - m_diagnosticsExplainedLabel->setText( - m_diagnosticsExplainedLabel->text() - .replace("LOGS_FULL_PATH", logsPath) - .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) - .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::crashDumpsPath())) - .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir())) - ); -} - -void Settings::DiagnosticsTab::update() -{ - m_Settings.setValue("Settings/log_level", m_logLevelBox->currentData().toInt()); - m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); - m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); -} diff --git a/src/settings.h b/src/settings.h index 64068173..71fbcbc1 100644 --- a/src/settings.h +++ b/src/settings.h @@ -449,24 +449,6 @@ private: void readPluginBlacklist(); QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - - - class DiagnosticsTab : public SettingsTab - { - public: - DiagnosticsTab(Settings *parent, SettingsDialog &dialog); - - void update(); - - private: - QComboBox *m_logLevelBox; - QComboBox *m_dumpsTypeBox; - QSpinBox *m_dumpsMaxEdit; - QLabel *m_diagnosticsExplainedLabel; - - void setLevelsBox(); - }; - private slots: signals: diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 3ad525e1..1deac400 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1325,6 +1325,9 @@ programs you are intentionally running. true + + true + diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp new file mode 100644 index 00000000..2ea4d478 --- /dev/null +++ b/src/settingsdialogdiagnostics.cpp @@ -0,0 +1,28 @@ +#include "settingsdialogdiagnostics.h" +#include "ui_settingsdialog.h" +#include "appconfig.h" +#include "organizercore.h" + +DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : SettingsTab(m_parent, m_dialog) +{ + ui->logLevelBox->setCurrentIndex(m_parent->logLevel()); + ui->dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); + ui->dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); + QString logsPath = qApp->property("dataPath").toString() + + "/" + QString::fromStdWString(AppConfig::logPath()); + ui->diagnosticsExplainedLabel->setText( + ui->diagnosticsExplainedLabel->text() + .replace("LOGS_FULL_PATH", logsPath) + .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) + .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::crashDumpsPath())) + .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir())) + ); +} + +void DiagnosticsSettingsTab::update() +{ + m_Settings.setValue("Settings/log_level", ui->logLevelBox->currentIndex()); + m_Settings.setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); + m_Settings.setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); +} diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h new file mode 100644 index 00000000..2341c253 --- /dev/null +++ b/src/settingsdialogdiagnostics.h @@ -0,0 +1,17 @@ +#ifndef SETTINGSDIALOGDIAGNOSTICS_H +#define SETTINGSDIALOGDIAGNOSTICS_H + +#include "settings.h" +#include "settingsdialog.h" + +class DiagnosticsSettingsTab : public SettingsTab +{ +public: + DiagnosticsSettingsTab(Settings *parent, SettingsDialog &dialog); + + void update(); + +private: +}; + +#endif // SETTINGSDIALOGDIAGNOSTICS_H -- cgit v1.3.1 From 107b396902be52f8ae305f58d4e7d85a86779051 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 11:12:28 -0400 Subject: moved tabs to SettingsDialog removed Settings::query(), main window now deals with SettingsDialog directly --- src/mainwindow.cpp | 5 +- src/settings.cpp | 100 ---------------------------------------- src/settings.h | 55 +--------------------- src/settingsdialog.cpp | 105 +++++++++++++++++++++++++++++++++++++++++- src/settingsdialog.h | 26 +++++++++-- src/settingsdialogplugins.cpp | 2 + 6 files changed, 133 insertions(+), 160 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7c73bc8a..28405819 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -72,6 +72,7 @@ along with Mod Organizer. If not, see . #include "previewdialog.h" #include "browserdialog.h" #include "aboutdialog.h" +#include "settingsdialog.h" #include #include "nxmaccessmanager.h" #include "appconfig.h" @@ -5217,7 +5218,9 @@ void MainWindow::on_actionSettings_triggered() bool proxy = settings.useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); - settings.query(&m_PluginContainer, this); + + SettingsDialog dialog(&m_PluginContainer, &settings, this); + dialog.exec(); if (oldManagedGameDirectory != settings.getManagedGameDirectory()) { QMessageBox::about(this, tr("Restarting MO"), diff --git a/src/settings.cpp b/src/settings.cpp index 725b7e06..dc07e107 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,14 +21,6 @@ along with Mod Organizer. If not, see . #include "pluginsetting.h" #include "serverinfo.h" -#include "settingsdialog.h" -#include "settingsdialogdiagnostics.h" -#include "settingsdialoggeneral.h" -#include "settingsdialognexus.h" -#include "settingsdialogpaths.h" -#include "settingsdialogplugins.h" -#include "settingsdialogsteam.h" -#include "settingsdialogworkarounds.h" #include "versioninfo.h" #include "appconfig.h" #include "organizercore.h" @@ -75,23 +67,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : m_parent(m_parent) - , m_Settings(m_parent->settingsRef()) - , m_dialog(m_dialog) - , ui(m_dialog.ui) -{ -} - -SettingsTab::~SettingsTab() -{} - -QWidget* SettingsTab::parentWidget() -{ - return &m_dialog; -} - - Settings *Settings::s_Instance = nullptr; @@ -668,78 +643,3 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } - -void Settings::query(PluginContainer *pluginContainer, QWidget *parent) -{ - SettingsDialog dialog(pluginContainer, this, parent); - - std::vector> tabs; - - tabs.push_back(std::unique_ptr(new GeneralSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PathsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new DiagnosticsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new NexusSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new SteamSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PluginsSettingsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(this, dialog))); - - - QString key = QString("geometry/%1").arg(dialog.objectName()); - if (m_Settings.contains(key)) { - dialog.restoreGeometry(m_Settings.value(key).toByteArray()); - } - - if (dialog.exec() == QDialog::Accepted) { - - for (auto&& tab : tabs) { - tab->closing(); - } - - // remember settings before change - QMap before; - m_Settings.beginGroup("Settings"); - for (auto k : m_Settings.allKeys()) - before[k] = m_Settings.value(k).toString(); - m_Settings.endGroup(); - - // transfer modified settings to configuration file - for (std::unique_ptr const &tab: tabs) { - tab->update(); - } - - // print "changed" settings - m_Settings.beginGroup("Settings"); - bool first_update = true; - for (auto k : m_Settings.allKeys()) - if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) - { - if (first_update) { - log::debug("Changed settings:"); - first_update = false; - } - log::debug(" {}={}", k, m_Settings.value(k).toString()); - } - m_Settings.endGroup(); - } - m_Settings.setValue(key, dialog.saveGeometry()); - - // These changes happen regardless of accepted or rejected - bool restartNeeded = false; - if (dialog.getApiKeyChanged()) { - restartNeeded = true; - } - if (dialog.getResetGeometries()) { - restartNeeded = true; - m_Settings.setValue("reset_geometry", true); - } - if (restartNeeded) { - if (QMessageBox::question(nullptr, - tr("Restart Mod Organizer?"), - tr("In order to finish configuration changes, MO must be restarted.\n" - "Restart it now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - qApp->exit(INT_MAX); - } - } - -} diff --git a/src/settings.h b/src/settings.h index 71fbcbc1..899baaa3 100644 --- a/src/settings.h +++ b/src/settings.h @@ -26,60 +26,21 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include #include #include -#include - -#include //for uint - #include #include -class QCheckBox; -class QComboBox; -class QLineEdit; -class QSpinBox; -class QListWidget; -class QWidget; -class QLabel; -class QPushButton; - -struct ServerInfo; - namespace MOBase { class IPlugin; class IPluginGame; } -namespace Ui { - class SettingsDialog; -} - -class SettingsDialog; class PluginContainer; -class Settings; - -class SettingsTab -{ -public: - SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); - virtual ~SettingsTab(); - - virtual void update() = 0; - virtual void closing() {} - -protected: - Settings *m_parent; - QSettings &m_Settings; - SettingsDialog &m_dialog; - Ui::SettingsDialog* ui; - - QWidget* parentWidget(); -}; +struct ServerInfo; /** * manages the settings for Mod Organizer. The settings are not cached @@ -87,17 +48,11 @@ protected: **/ class Settings : public QObject { - Q_OBJECT public: - - /** - * @brief constructor - **/ Settings(const QSettings &settingsSource); - - virtual ~Settings(); + ~Settings(); static Settings &instance(); @@ -113,12 +68,6 @@ public: */ void registerPlugin(MOBase::IPlugin *plugin); - /** - * displays a SettingsDialog that allows the user to change settings. If the - * user accepts the changes, the settings are immediately written - **/ - void query(PluginContainer *pluginContainer, QWidget *parent); - /** * set up the settings for the specified plugins **/ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 76b0a146..8c5b2678 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -29,6 +29,14 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "plugincontainer.h" +#include "settingsdialogdiagnostics.h" +#include "settingsdialoggeneral.h" +#include "settingsdialognexus.h" +#include "settingsdialogpaths.h" +#include "settingsdialogplugins.h" +#include "settingsdialogsteam.h" +#include "settingsdialogworkarounds.h" + #include #include @@ -47,7 +55,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; - SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) @@ -57,7 +64,84 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti , m_keyChanged(false) { ui->setupUi(this); - ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); + + m_tabs.push_back(std::unique_ptr(new GeneralSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new PathsSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new DiagnosticsSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new NexusSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new SteamSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new PluginsSettingsTab(settings, *this))); + m_tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); + + auto& qsettings = settings->directInterface(); + + QString key = QString("geometry/%1").arg(objectName()); + if (qsettings.contains(key)) { + restoreGeometry(qsettings.value(key).toByteArray()); + } +} + +int SettingsDialog::exec() +{ + auto& qsettings = m_settings->directInterface(); + auto ret = TutorableDialog::exec(); + + if (ret == QDialog::Accepted) { + + for (auto&& tab : m_tabs) { + tab->closing(); + } + + // remember settings before change + QMap before; + qsettings.beginGroup("Settings"); + for (auto k : qsettings.allKeys()) + before[k] = qsettings.value(k).toString(); + qsettings.endGroup(); + + // transfer modified settings to configuration file + for (std::unique_ptr const &tab: m_tabs) { + tab->update(); + } + + // print "changed" settings + qsettings.beginGroup("Settings"); + bool first_update = true; + for (auto k : qsettings.allKeys()) + if (qsettings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) + { + if (first_update) { + qDebug("Changed settings:"); + first_update = false; + } + qDebug(" %s=%s", k.toUtf8().data(), qsettings.value(k).toString().toUtf8().data()); + } + qsettings.endGroup(); + } + + QString key = QString("geometry/%1").arg(objectName()); + qsettings.setValue(key, saveGeometry()); + + // These changes happen regardless of accepted or rejected + bool restartNeeded = false; + if (getApiKeyChanged()) { + restartNeeded = true; + } + if (getResetGeometries()) { + restartNeeded = true; + qsettings.setValue("reset_geometry", true); + } + if (restartNeeded) { + if (QMessageBox::question(nullptr, + tr("Restart Mod Organizer?"), + tr("In order to finish configuration changes, MO must be restarted.\n" + "Restart it now?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + qApp->exit(INT_MAX); + } + } + + return ret; } SettingsDialog::~SettingsDialog() @@ -107,3 +191,20 @@ bool SettingsDialog::getApiKeyChanged() { return m_keyChanged; } + + +SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : m_parent(m_parent) + , m_Settings(m_parent->settingsRef()) + , m_dialog(m_dialog) + , ui(m_dialog.ui) +{ +} + +SettingsTab::~SettingsTab() +{} + +QWidget* SettingsTab::parentWidget() +{ + return &m_dialog; +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 81c17f44..f2367315 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -27,10 +27,26 @@ along with Mod Organizer. If not, see . class PluginContainer; class Settings; +class SettingsDialog; +namespace Ui { class SettingsDialog; } -namespace Ui { - class SettingsDialog; -} +class SettingsTab +{ +public: + SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + virtual ~SettingsTab(); + + virtual void update() = 0; + virtual void closing() {} + +protected: + Settings *m_parent; + QSettings &m_Settings; + SettingsDialog &m_dialog; + Ui::SettingsDialog* ui; + + QWidget* parentWidget(); +}; /** @@ -60,6 +76,8 @@ public: bool m_GeometriesReset; PluginContainer *m_PluginContainer; + int exec() override; + public slots: virtual void accept(); @@ -69,7 +87,7 @@ public: private: Settings* m_settings; - + std::vector> m_tabs; }; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 33bc1563..53b28fcc 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -8,6 +8,8 @@ using MOBase::IPlugin; PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) { + ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); + // display plugin settings QSet handledNames; for (IPlugin *plugin : m_parent->plugins()) { -- cgit v1.3.1 From a05862aaa13b028e2f250347daa7a2e0f64c2380 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 11:20:27 -0400 Subject: cleaned up includes removed commented out code reordered member functions in Settings --- src/settings.cpp | 44 ------------------------------------------- src/settings.h | 36 +++++++---------------------------- src/settingsdialog.cpp | 26 ------------------------- src/settingsdialog.h | 4 +--- src/settingsdialoggeneral.cpp | 24 ----------------------- src/settingsdialognexus.cpp | 11 ----------- src/settingsdialognexus.h | 1 + 7 files changed, 9 insertions(+), 137 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index dc07e107..e7a853a2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -18,58 +18,16 @@ along with Mod Organizer. If not, see . */ #include "settings.h" - -#include "pluginsetting.h" #include "serverinfo.h" -#include "versioninfo.h" #include "appconfig.h" -#include "organizercore.h" #include -#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include // for Qt::UserRole, etc - -#include // For ShellExecuteW, HINSTANCE, etc -#include // For storage - -#include // for sort -#include -#include // for runtime_error -#include -#include // for pair, make_pair - - using namespace MOBase; - Settings *Settings::s_Instance = nullptr; - Settings::Settings(const QSettings &settingsSource) : m_Settings(settingsSource.fileName(), settingsSource.format()) { @@ -80,13 +38,11 @@ Settings::Settings(const QSettings &settingsSource) } } - Settings::~Settings() { s_Instance = nullptr; } - Settings &Settings::instance() { if (s_Instance == nullptr) { diff --git a/src/settings.h b/src/settings.h index 899baaa3..b20e78d0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -23,17 +23,6 @@ along with Mod Organizer. If not, see . #include "loadmechanism.h" #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - namespace MOBase { class IPlugin; class IPluginGame; @@ -376,6 +365,8 @@ public: */ bool colorSeparatorScrollbar() const; + static QColor getIdealTextColor(const QColor& rBackgroundColor); + // temp QSettings& settingsRef() { return m_Settings; } MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } @@ -386,37 +377,24 @@ public: const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } public slots: - void managedGameChanged(MOBase::IPluginGame const *gamePlugin); -public: - static QColor getIdealTextColor(const QColor& rBackgroundColor); -private: - - static bool obfuscate(const QString key, const QString data); - static QString deObfuscate(const QString key); - - void readPluginBlacklist(); - QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - -private slots: signals: - void languageChanged(const QString &newLanguage); void styleChanged(const QString &newStyle); private: - static Settings *s_Instance; - MOBase::IPluginGame const *m_GamePlugin; - QSettings m_Settings; - LoadMechanism m_LoadMechanism; - std::vector m_Plugins; + static bool obfuscate(const QString key, const QString data); + static QString deObfuscate(const QString key); + + void readPluginBlacklist(); + QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; }; #endif // SETTINGS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 8c5b2678..e008086a 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -18,17 +18,7 @@ along with Mod Organizer. If not, see . */ #include "settingsdialog.h" - #include "ui_settingsdialog.h" -#include "categoriesdialog.h" -#include "helper.h" -#include "noeditdelegate.h" -#include "iplugingame.h" -#include "settings.h" -#include "instancemanager.h" -#include "nexusinterface.h" -#include "plugincontainer.h" - #include "settingsdialogdiagnostics.h" #include "settingsdialoggeneral.h" #include "settingsdialognexus.h" @@ -37,22 +27,6 @@ along with Mod Organizer. If not, see . #include "settingsdialogsteam.h" #include "settingsdialogworkarounds.h" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include - - using namespace MOBase; SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) diff --git a/src/settingsdialog.h b/src/settingsdialog.h index f2367315..03bba7cf 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,15 +21,13 @@ along with Mod Organizer. If not, see . #define SETTINGSDIALOG_H #include "tutorabledialog.h" -#include "nxmaccessmanager.h" -#include -#include class PluginContainer; class Settings; class SettingsDialog; namespace Ui { class SettingsDialog; } + class SettingsTab { public: diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index cd98dfdc..324dc4f4 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -33,28 +33,6 @@ GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dia ui->styleBox->setCurrentIndex(currentID); } } - /* verision using palette only works with fusion theme for some stupid reason... - m_overwritingBtn->setAutoFillBackground(true); - m_overwrittenBtn->setAutoFillBackground(true); - m_containsBtn->setAutoFillBackground(true); - m_containedBtn->setAutoFillBackground(true); - m_overwritingBtn->setPalette(QPalette(m_parent->modlistOverwritingLooseColor())); - m_overwrittenBtn->setPalette(QPalette(m_parent->modlistOverwrittenLooseColor())); - m_containsBtn->setPalette(QPalette(m_parent->modlistContainsPluginColor())); - m_containedBtn->setPalette(QPalette(m_parent->pluginListContainedColor())); - QPalette palette1 = m_overwritingBtn->palette(); - QPalette palette2 = m_overwrittenBtn->palette(); - QPalette palette3 = m_containsBtn->palette(); - QPalette palette4 = m_containedBtn->palette(); - palette1.setColor(QPalette::Background, m_parent->modlistOverwritingLooseColor()); - palette2.setColor(QPalette::Background, m_parent->modlistOverwrittenLooseColor()); - palette3.setColor(QPalette::Background, m_parent->modlistContainsPluginColor()); - palette4.setColor(QPalette::Background, m_parent->pluginListContainedColor()); - m_overwritingBtn->setPalette(palette1); - m_overwrittenBtn->setPalette(palette2); - m_containsBtn->setPalette(palette3); - m_containedBtn->setPalette(palette4); - */ //version with stylesheet setButtonColor(ui->overwritingBtn, m_parent->modlistOverwritingLooseColor()); @@ -137,12 +115,10 @@ void GeneralSettingsTab::addLanguages() } } languages.push_back(std::make_pair(QString("%1").arg(languageString), exp.cap(1))); - //languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); } } if (!ui->languageBox->findText("English")) { languages.push_back(std::make_pair(QString("English"), QString("en_US"))); - //languageBox->addItem("English", "en_US"); } std::sort(languages.begin(), languages.end()); for (const auto &lang : languages) { diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 7d4414fd..575f54d0 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -114,17 +114,6 @@ NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) void NexusSettingsTab::update() { - /* - if (m_loginCheckBox->isChecked()) { - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", m_usernameEdit->text()); - m_Settings.setValue("Settings/nexus_password", obfuscate(m_passwordEdit->text())); - } else { - m_Settings.setValue("Settings/nexus_login", false); - m_Settings.remove("Settings/nexus_username"); - m_Settings.remove("Settings/nexus_password"); - } - */ m_Settings.setValue("Settings/offline_mode", ui->offlineBox->isChecked()); m_Settings.setValue("Settings/use_proxy", ui->proxyBox->isChecked()); m_Settings.setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked()); diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index 5c01f61f..cca2e1b5 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -3,6 +3,7 @@ #include "settings.h" #include "settingsdialog.h" +#include "nxmaccessmanager.h" class NexusSettingsTab : public SettingsTab { -- cgit v1.3.1 From ee43c405987d646fd15ad17cf1f1ffe2db45bc51 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 20 Jul 2019 12:03:05 -0400 Subject: removed obsolete load mechanisms --- src/loadmechanism.cpp | 272 +------------------------------------- src/loadmechanism.h | 63 +-------- src/settings.cpp | 19 ++- src/settings.h | 8 +- src/settingsdialog.cpp | 2 +- src/settingsdialogworkarounds.cpp | 14 -- 6 files changed, 23 insertions(+), 355 deletions(-) (limited to 'src') diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 2d01562d..06e9f201 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -32,286 +32,20 @@ along with Mod Organizer. If not, see . #include #include - using namespace MOBase; using namespace MOShared; - LoadMechanism::LoadMechanism() : m_SelectedMechanism(LOAD_MODORGANIZER) { } -void LoadMechanism::writeHintFile(const QDir &targetDirectory) -{ - QString hintFilePath = targetDirectory.absoluteFilePath("mo_path.txt"); - QFile hintFile(hintFilePath); - if (hintFile.exists()) { - hintFile.remove(); - } - if (!hintFile.open(QIODevice::WriteOnly)) { - throw MyException(QObject::tr("failed to open %1: %2").arg(hintFilePath).arg(hintFile.errorString())); - } - hintFile.write(qApp->applicationDirPath().toUtf8().constData()); - hintFile.close(); -} - - -void LoadMechanism::removeHintFile(QDir targetDirectory) -{ - targetDirectory.remove("mo_path.txt"); -} - - bool LoadMechanism::isDirectLoadingSupported() const { - //FIXME: Seriously? isn't there a 'do i need steam' thing? - IPluginGame const *game = qApp->property("managed_game").value(); - if (game->gameName().compare("oblivion", Qt::CaseInsensitive) == 0) { - // oblivion can be loaded directly if it's not the steam variant - return !game->gameDirectory().exists("steam_api.dll"); - } else { - // all other games work afaik - return true; - } -} - -bool LoadMechanism::isScriptExtenderSupported() const -{ - IPluginGame const *game = qApp->property("managed_game").value(); - ScriptExtender *extender = game->feature(); - - // test if there even is an extender for the managed game and if so whether it's installed - return extender != nullptr && extender->isInstalled(); -} - -bool LoadMechanism::isProxyDLLSupported() const -{ - // using steam_api.dll as the proxy is way too game specific as many games will have different - // versions of that dll. - // plus: the proxy dll hasn't been working for at least the whole 1.12.x versions of MO and - // noone reported it so why maintain an unused feature? - return false; -/* IPluginGame const *game = qApp->property("managed_game").value(); - return game->gameDirectory().exists(QString::fromStdWString(AppConfig::proxyDLLTarget()));*/ -} - - -bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS) -{ - QFile fileLHS(fileNameLHS); - if (!fileLHS.open(QIODevice::ReadOnly)) { - throw MyException(QObject::tr("file not found: %1").arg(qUtf8Printable(fileNameLHS))); - } - QByteArray dataLHS = fileLHS.readAll(); - QByteArray hashLHS = QCryptographicHash::hash(dataLHS, QCryptographicHash::Md5); - - fileLHS.close(); - - QFile fileRHS(fileNameRHS); - if (!fileRHS.open(QIODevice::ReadOnly)) { - throw MyException(QObject::tr("file not found: %1").arg(qUtf8Printable(fileNameRHS))); - } - QByteArray dataRHS = fileRHS.readAll(); - QByteArray hashRHS = QCryptographicHash::hash(dataRHS, QCryptographicHash::Md5); - - fileRHS.close(); - - return hashLHS == hashRHS; + return true; } - -void LoadMechanism::deactivateScriptExtender() +void LoadMechanism::activate(EMechanism) { - try { - IPluginGame const *game = qApp->property("managed_game").value(); - ScriptExtender *extender = game->feature(); - if (extender == nullptr) { - return; - } - - QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath()); - -#pragma message("implement this for usvfs") - - QString vfsDLLName = ""; - if (extender->getArch() == IMAGE_FILE_MACHINE_I386) { - vfsDLLName = ToQString(AppConfig::vfs32DLLName()); - } - else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64) - { - vfsDLLName = ToQString(AppConfig::vfs64DLLName()); - } - log::debug("USVFS DLL Name: {}", vfsDLLName); - if (vfsDLLName != "") { - if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) { - // remove dll from SE plugins directory - if (!pluginsDir.remove(vfsDLLName)) { - throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(vfsDLLName))); - } - } - } - - removeHintFile(pluginsDir); - } catch (const std::exception &e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to deactivate script extender loading"), e.what()); - } + // no-op } - - -void LoadMechanism::deactivateProxyDLL() -{ - try { - IPluginGame const *game = qApp->property("managed_game").value(); - - QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget())); - - QFile targetDLL(targetPath); - if (targetDLL.exists()) { - QString origFile = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLOrig())); - // determine if a proxy-dll is installed - // this is a very crude way of making this decision but it should be good enough - if ((targetDLL.size() < 24576) && (QFile(origFile).exists())) { - // remove proxy-dll - if (!targetDLL.remove()) { - throw MyException(QObject::tr("Failed to remove %1: %2").arg(targetPath).arg(targetDLL.errorString())); - } else if (!QFile::rename(origFile, targetPath)) { - throw MyException(QObject::tr("Failed to rename %1 to %2").arg(origFile, targetPath)); - } - } - } - - removeHintFile(game->gameDirectory()); - } catch (const std::exception &e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to deactivate proxy-dll loading"), e.what()); - } -} - - -void LoadMechanism::activateScriptExtender() -{ - try { - IPluginGame const *game = qApp->property("managed_game").value(); - ScriptExtender *extender = game->feature(); - if (extender == nullptr) { - return; - } - - QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath()); - - if (!pluginsDir.exists()) { - pluginsDir.mkpath("."); - } - -#pragma message("implement this for usvfs") - std::wstring vfsDLL = L""; - if (extender->getArch() == IMAGE_FILE_MACHINE_I386) { - vfsDLL = AppConfig::vfs32DLLName(); - } - else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64) - { - vfsDLL = AppConfig::vfs64DLLName(); - } - if (vfsDLL != L"") { - QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL)); - QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL); - - log::debug("DLL USVFS Target Path: {}", targetPath); - log::debug("DLL USVFS VFS DLL Path: {}", vfsDLLPath); - - QFile dllFile(targetPath); - - if (dllFile.exists()) { - // may be outdated - if (!hashIdentical(targetPath, vfsDLLPath)) { - dllFile.remove(); - } - } - - if (!dllFile.exists()) { - // install dll to SE plugins - if (!QFile::copy(vfsDLLPath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(vfsDLLPath, targetPath)); - } - } - } - writeHintFile(pluginsDir); - } catch (const std::exception &e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to set up script extender loading"), e.what()); - } -} - - -void LoadMechanism::activateProxyDLL() -{ - try { - IPluginGame const *game = qApp->property("managed_game").value(); - - QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget())); - - QFile targetDLL(targetPath); - if (!targetDLL.exists()) { - return; - } - - QString sourcePath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::proxyDLLSource()); - - // this is a very crude way of making this decision but it should be good enough - if (targetDLL.size() < 24576) { - // determine if a proxy-dll is already installed and if so, if it's the right one - if (!hashIdentical(targetPath, sourcePath)) { - // wrong proxy dll, probably outdated. delete and install the new one - if (!QFile::remove(targetPath)) { - throw MyException(QObject::tr("Failed to delete old proxy-dll %1").arg(targetPath)); - } - if (!QFile::copy(sourcePath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); - } - } // otherwise the proxy-dll is already the right one - } else { - // no proxy dll installed yet. move the original and insert proxy-dll - - QString origFile = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLOrig())); - - if (QFile(origFile).exists()) { - // orig-file exists. this may happen if the steam-api was updated or the user messed with the - // dlls. - if (!QFile::remove(origFile)) { - throw MyException(QObject::tr("Failed to overwrite %1").arg(origFile)); - } - } - if (!QFile::rename(targetPath, origFile)) { - throw MyException(QObject::tr("Failed to rename %1 to %2").arg(targetPath).arg(origFile)); - } - if (!QFile::copy(sourcePath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); - } - } - writeHintFile(game->gameDirectory()); - } catch (const std::exception &e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to set up proxy-dll loading"), e.what()); - } -} - - -void LoadMechanism::activate(EMechanism mechanism) -{ - switch (mechanism) { - case LOAD_MODORGANIZER: { - log::debug("Load Mechanism: Mod Organizer"); - deactivateProxyDLL(); - deactivateScriptExtender(); - } break; - case LOAD_SCRIPTEXTENDER: { - log::debug("Load Mechanism: ScriptExtender"); - deactivateProxyDLL(); - activateScriptExtender(); - } break; - case LOAD_PROXYDLL: { - log::debug("Load Mechanism: Proxy DLL"); - deactivateScriptExtender(); - activateProxyDLL(); - } break; - } -} - diff --git a/src/loadmechanism.h b/src/loadmechanism.h index 51fefaf9..49eb0c52 100644 --- a/src/loadmechanism.h +++ b/src/loadmechanism.h @@ -27,33 +27,14 @@ along with Mod Organizer. If not, see . /** * @brief manages the various load mechanisms supported by Mod Organizer - * the load mechanisms is the means by which the mo-dll is injected into the target - * process. The default mode "mod organizer" requires the target process to be started - * from inside mod organizer. In certain cases (oblivion steam edition) this is not - * possible since the game can then only be started from steam. - * "Script Extender" is an alternative load mechanism that uses a script extender (obse, - * fose, nvse or skse) to load MO. This is reliable but prevents se plugins installed - * through MO from working. - * "Proxy DLL" replaces a dll belonging to the game by a proxy that will load MO and then - * chain-load the original dll. This currently only works with steam-versions of games and - * is intended as a last resort solution. **/ class LoadMechanism { public: - enum EMechanism { LOAD_MODORGANIZER = 0, - LOAD_SCRIPTEXTENDER, - LOAD_PROXYDLL }; -public: - - /** - * @brief constructor - * - **/ LoadMechanism(); /** @@ -66,54 +47,12 @@ public: /** * @brief test whether the "Mod Organizer" load mechanism is supported for the current game * - * @return true if the load mechanism is supported + * @return true **/ bool isDirectLoadingSupported() const; - /** - * @brief test whether the "Script Extender" load mechanism is supported for the current game - * - * @return true if the load mechanism is supported - **/ - bool isScriptExtenderSupported() const; - - /** - * @brief test whether the "Proxy DLL" load mechanism is supported for the current game - * - * @return true if the load mechanism is supported - **/ - bool isProxyDLLSupported() const; - -private: - - // write a hint file that is required for certain loading mechanisms for the dll to find - // the mod organizer installation - void writeHintFile(const QDir &targetDirectory); - - // remove the hint file if it exists. does nothing if the file doesn't exist - void removeHintFile(QDir targetDirectory); - - // compare the two files by md5-hash, returns true if they are identical - bool hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS); - - // deactivate loading through script extender. does nothing if se-loading wasn't active - void deactivateScriptExtender(); - - // deactivate loading through proxy-dll. does nothing if se-loading wasn't active - void deactivateProxyDLL(); - - // activate loading through script extender. does nothing if already active. updates - // the dll if necessary - void activateScriptExtender(); - - // activate loading through proxy-dll. does nothing if already active. updates - // the dll if necessary - void activateProxyDLL(); - private: - EMechanism m_SelectedMechanism; - }; diff --git a/src/settings.cpp b/src/settings.cpp index e7a853a2..77db6918 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -429,12 +429,21 @@ void Settings::setSteamLogin(QString username, QString password) LoadMechanism::EMechanism Settings::getLoadMechanism() const { - switch (m_Settings.value("Settings/load_mechanism").toInt()) { - case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER; - case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER; - case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL; + const auto i = m_Settings.value("Settings/load_mechanism").toInt(); + + switch (i) + { + case LoadMechanism::LOAD_MODORGANIZER: + return LoadMechanism::LOAD_MODORGANIZER; + + default: + qCritical().nospace().noquote() + << "invalid load mechanism " << i << ", reverting to modorganizer"; + + m_Settings.setValue("Settings/load_mechanism", LoadMechanism::LOAD_MODORGANIZER); + + return LoadMechanism::LOAD_MODORGANIZER; } - throw std::runtime_error("invalid load mechanism"); } diff --git a/src/settings.h b/src/settings.h index b20e78d0..63718089 100644 --- a/src/settings.h +++ b/src/settings.h @@ -367,14 +367,14 @@ public: static QColor getIdealTextColor(const QColor& rBackgroundColor); - // temp - QSettings& settingsRef() { return m_Settings; } MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } + const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + + // temp QMap m_PluginSettings; QMap m_PluginDescriptions; QSet m_PluginBlacklist; void writePluginBlacklist(); - const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } public slots: void managedGameChanged(MOBase::IPluginGame const *gamePlugin); @@ -386,7 +386,7 @@ signals: private: static Settings *s_Instance; MOBase::IPluginGame const *m_GamePlugin; - QSettings m_Settings; + mutable QSettings m_Settings; LoadMechanism m_LoadMechanism; std::vector m_Plugins; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index e008086a..d870c192 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -169,7 +169,7 @@ bool SettingsDialog::getApiKeyChanged() SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : m_parent(m_parent) - , m_Settings(m_parent->settingsRef()) + , m_Settings(m_parent->directInterface()) , m_dialog(m_dialog) , ui(m_dialog.ui) { diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 4cca5fd4..9ac46ac1 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -18,20 +18,6 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo } } - if (m_parent->loadMechanism().isScriptExtenderSupported()) { - ui->mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); - if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { - index = ui->mechanismBox->count() - 1; - } - } - - if (m_parent->loadMechanism().isProxyDLLSupported()) { - ui->mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); - if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { - index = ui->mechanismBox->count() - 1; - } - } - ui->mechanismBox->setCurrentIndex(index); ui->hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins()); -- cgit v1.3.1 From 4099efcb5bfd6932ed2a510aa0e4318fffff121b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 2 Aug 2019 02:46:19 -0400 Subject: rebased to new logging --- src/settingsdialog.cpp | 4 ++-- src/settingsdialogdiagnostics.cpp | 24 ++++++++++++++++++++++-- src/settingsdialogdiagnostics.h | 1 + 3 files changed, 25 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index d870c192..fbd9ecd1 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -85,10 +85,10 @@ int SettingsDialog::exec() if (qsettings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) { if (first_update) { - qDebug("Changed settings:"); + log::debug("Changed settings:"); first_update = false; } - qDebug(" %s=%s", k.toUtf8().data(), qsettings.value(k).toString().toUtf8().data()); + log::debug(" {}={}", k, qsettings.value(k).toString()); } qsettings.endGroup(); } diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 2ea4d478..daf81d5c 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -2,11 +2,14 @@ #include "ui_settingsdialog.h" #include "appconfig.h" #include "organizercore.h" +#include + +using namespace MOBase; DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : SettingsTab(m_parent, m_dialog) { - ui->logLevelBox->setCurrentIndex(m_parent->logLevel()); + setLevelsBox(); ui->dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); ui->dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() @@ -20,9 +23,26 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings *m_parent, SettingsDialo ); } +void DiagnosticsSettingsTab::setLevelsBox() +{ + ui->logLevelBox->clear(); + + ui->logLevelBox->addItem(QObject::tr("Debug"), log::Debug); + ui->logLevelBox->addItem(QObject::tr("Info (recommended)"), log::Info); + ui->logLevelBox->addItem(QObject::tr("Warning"), log::Warning); + ui->logLevelBox->addItem(QObject::tr("Error"), log::Error); + + for (int i=0; ilogLevelBox->count(); ++i) { + if (ui->logLevelBox->itemData(i) == m_parent->logLevel()) { + ui->logLevelBox->setCurrentIndex(i); + break; + } + } +} + void DiagnosticsSettingsTab::update() { - m_Settings.setValue("Settings/log_level", ui->logLevelBox->currentIndex()); + m_Settings.setValue("Settings/log_level", ui->logLevelBox->currentData().toInt()); m_Settings.setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); m_Settings.setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h index 2341c253..4c1805e2 100644 --- a/src/settingsdialogdiagnostics.h +++ b/src/settingsdialogdiagnostics.h @@ -12,6 +12,7 @@ public: void update(); private: + void setLevelsBox(); }; #endif // SETTINGSDIALOGDIAGNOSTICS_H -- cgit v1.3.1 From 712c8687c208629e22ef7b4d8015c899c2ea053a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 2 Aug 2019 05:33:57 -0400 Subject: changed old whatsthis for prerelease apparently, QFontMetrics::width() is deprecated --- src/loglist.cpp | 3 ++- src/settingsdialog.ui | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/loglist.cpp b/src/loglist.cpp index 192913b6..c884be49 100644 --- a/src/loglist.cpp +++ b/src/loglist.cpp @@ -162,7 +162,8 @@ LogList::LogList(QWidget* parent) { setModel(&LogModel::instance()); - const int timestampWidth = QFontMetrics(font()).width("00:00:00.000"); + const QFontMetrics fm(font()); + const int timestampWidth = fm.horizontalAdvance("00:00:00.000"); header()->setMinimumSectionSize(0); header()->resizeSection(0, 20); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 1deac400..e011542e 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -76,11 +76,7 @@ p, li { white-space: pre-wrap; } Update to non-stable releases. - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). - -Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. - -If you use pre-releases, never contact me directly by e-mail or via private messages! + If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. Install Pre-releases (Betas) -- cgit v1.3.1 From 5e98855847590cfff4e04aa7c3d593f0d30e7202 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 2 Aug 2019 07:01:06 -0400 Subject: create log directory sooner to handle creating new instances reset the log file in case MO is restarted and the previously selected instance is deleted switched back to single `mo_interface.log` file --- src/main.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 0e61a781..911f11c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -120,10 +120,6 @@ bool bootstrap() removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), "usvfs*.log", 5, QDir::Name); - if (!createAndMakeWritable(AppConfig::logPath())) { - return false; - } - return true; } @@ -906,6 +902,10 @@ int main(int argc, char *argv[]) } // we continue for the primary instance OR if MO was called with parameters do { + // make sure the log file isn't locked in case MO was restarted and + // the previous instance gets deleted + log::getDefault().setFile({}); + QString dataPath; try { @@ -926,8 +926,12 @@ int main(int argc, char *argv[]) const auto logFile = qApp->property("dataPath").toString() + "/logs/mo_interface.log"; - log::getDefault().setFile(MOBase::log::File::rotating( - logFile.toStdWString(), 5*1024*1024, 5)); + if (!createAndMakeWritable(AppConfig::logPath())) { + reportError("Failed to create log folder"); + return 1; + } + + log::getDefault().setFile(MOBase::log::File::single(logFile.toStdWString())); QString splash = dataPath + "/splash.png"; if (!QFile::exists(dataPath + "/splash.png")) { -- cgit v1.3.1 From b6b01a52db1877b16531137289641fb9be9833aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 2 Aug 2019 23:24:27 -0400 Subject: removed mentions of QSettings from main.cpp added necessary member functions in Settings --- src/filedialogmemory.cpp | 1 + src/filedialogmemory.h | 1 + src/main.cpp | 185 +++++++++++++++++++++-------------------------- src/mainwindow.cpp | 6 +- src/mainwindow.h | 3 +- src/organizercore.cpp | 4 +- src/organizercore.h | 2 +- src/settings.cpp | 77 +++++++++++++++++++- src/settings.h | 22 +++++- 9 files changed, 188 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 0e3e9793..308a175e 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see . */ #include "filedialogmemory.h" +#include "settings.h" #include diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index 81d7ba40..1a72b289 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include #include +class Settings; class FileDialogMemory { diff --git a/src/main.cpp b/src/main.cpp index 911f11c3..720ecbf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -245,9 +245,10 @@ static bool HaveWriteAccess(const std::wstring &path) } -QString determineProfile(QStringList &arguments, const QSettings &settings) +QString determineProfile(QStringList &arguments, const Settings &settings) { - QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); + QString selectedProfileName = settings.getSelectedProfileName(); + { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { @@ -257,6 +258,7 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) arguments.removeAt(profileIndex); arguments.removeAt(profileIndex); } + if (selectedProfileName.isEmpty()) { log::debug("no configured profile"); selectedProfileName = "Default"; @@ -267,46 +269,50 @@ QString determineProfile(QStringList &arguments, const QSettings &settings) return selectedProfileName; } -MOBase::IPluginGame *selectGame(QSettings &settings, QDir const &gamePath, MOBase::IPluginGame *game) +MOBase::IPluginGame *selectGame( + Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) { - settings.setValue("gameName", game->gameName()); - //Sadly, hookdll needs gamePath in order to run. So following code block is - //commented out - /*if (gamePath == game->gameDirectory()) { - settings.remove("gamePath"); - } else*/ { - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData()); - } - return game; //Woot + settings.setManagedGameName(game->gameName()); + + QString gameDir = gamePath.absolutePath(); + game->setGamePath(gameDir); + + settings.setManagedGameDirectory(gameDir); + + return game; } -MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &settings, PluginContainer const &plugins) +MOBase::IPluginGame *determineCurrentGame( + QString const &moPath, Settings &settings, PluginContainer const &plugins) { //Determine what game we are running where. Be very paranoid in case the //user has done something odd. //If the game name has been set up, try to use that. - QString gameName = settings.value("gameName", "").toString(); + const QString gameName = settings.getManagedGameName(); bool gameConfigured = !gameName.isEmpty(); + if (gameConfigured) { MOBase::IPluginGame *game = plugins.managedGame(gameName); if (game == nullptr) { reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); return nullptr; } - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + + QString gamePath = settings.getManagedGameDirectory(); if (gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } + QDir gameDir(gamePath); QFileInfo directoryInfo(gameDir.path()); + if (directoryInfo.isSymLink()) { reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); } + if (game->looksValid(gameDir)) { return selectGame(settings, gameDir, game); } @@ -315,7 +321,7 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + const QString gamePath = settings.getManagedGameDirectory(); reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). arg(gameName).arg(gamePath)); } @@ -480,27 +486,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void dumpSettings(QSettings& settings) -{ - static const QStringList ignore({ - "username", "password", "nexus_api_key" - }); - - log::debug("settings:"); - - settings.beginGroup("Settings"); - - for (auto k : settings.allKeys()) { - if (ignore.contains(k, Qt::CaseInsensitive)) { - continue; - } - - log::debug(" . {}={}", k, settings.value(k).toString()); - } - - settings.endGroup(); -} - void checkMissingFiles() { // files that are likely to be eaten @@ -557,7 +542,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::warn("no ssl support"); } - QString dataPath = application.property("dataPath").toString(); + const QString dataPath = application.property("dataPath").toString(); log::info("data path: {}", dataPath); if (!bootstrap()) { @@ -573,11 +558,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, try { log::info("working directory: {}", QDir::currentPath()); - QSettings initSettings( - dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); - - Settings settings(initSettings); + Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); log::getDefault().setLevel(settings.logLevel()); // global crashDumpType sits in OrganizerCore to make a bit less ugly to @@ -587,7 +568,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, env::Environment env; env.dump(); - dumpSettings(initSettings); + settings.dump(); sanityChecks(env); log::debug("initializing core"); @@ -602,7 +583,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, pluginContainer.loadPlugins(); MOBase::IPluginGame *game = determineCurrentGame( - application.applicationDirPath(), initSettings, pluginContainer); + application.applicationDirPath(), settings, pluginContainer); + if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); @@ -612,6 +594,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, } return 1; } + if (splashPath.startsWith(':')) { // currently using MO splash, see if the plugin contains one QString pluginSplash @@ -625,7 +608,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (!initSettings.contains("game_edition")) { + if (settings.getManagedGameEdition() == "") { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -641,78 +624,76 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - initSettings.setValue("game_edition", selection.getChoiceString()); + settings.setManagedGameEdition(selection.getChoiceString()); } } } - game->setGameVariant(initSettings.value("game_edition").toString()); + + game->setGameVariant(settings.getManagedGameEdition()); log::info("managing game at {}", game->gameDirectory().absolutePath()); - organizer.updateExecutablesList(initSettings); + organizer.updateExecutablesList(); - QString selectedProfileName = determineProfile(arguments, initSettings); + QString selectedProfileName = determineProfile(arguments, settings); organizer.setCurrentProfile(selectedProfileName); // if we have a command line parameter, it is either a nxm link or // a binary to start - if (arguments.size() > 1) { - if (MOShortcut shortcut{ arguments.at(1) }) { - if (shortcut.hasExecutable()) { - try { - organizer.runShortcut(shortcut); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } - } - else if (OrganizerCore::isNxmLink(arguments.at(1))) { - log::debug("starting download from command line: {}", arguments.at(1)); - organizer.externalMessage(arguments.at(1)); - } - else { - QString exeName = arguments.at(1); - log::debug("starting {} from command line", exeName); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.startApplication(exeName, arguments, QString(), QString()); - return 0; - } - catch (const std::exception &e) { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } - } + if (arguments.size() > 1) { + if (MOShortcut shortcut{ arguments.at(1) }) { + if (shortcut.hasExecutable()) { + try { + organizer.runShortcut(shortcut); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } + else if (OrganizerCore::isNxmLink(arguments.at(1))) { + log::debug("starting download from command line: {}", arguments.at(1)); + organizer.externalMessage(arguments.at(1)); + } + else { + QString exeName = arguments.at(1); + log::debug("starting {} from command line", exeName); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) + arguments.removeFirst(); // remove binary name + // pass the remaining parameters to the binary + try { + organizer.startApplication(exeName, arguments, QString(), QString()); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } + } QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - if (initSettings.contains("window_monitor")) { - const int monitor = initSettings.value("window_monitor").toInt(); - - if (monitor != -1 && QGuiApplication::screens().size() > monitor) { - QGuiApplication::screens().at(monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); - splash.move(center - splash.rect().center()); - } else { - const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); - splash.move(center - splash.rect().center()); - } + const int monitor = settings.getMainWindowMonitor(); + if (monitor != -1 && QGuiApplication::screens().size() > monitor) { + QGuiApplication::screens().at(monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); + splash.move(center - splash.rect().center()); + } else { + const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); + splash.move(center - splash.rect().center()); } splash.show(); splash.activateWindow(); QString apiKey; - if (organizer.settings().getNexusApiKey(apiKey)) { + if (settings.getNexusApiKey(apiKey)) { NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } @@ -722,15 +703,15 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(initSettings.value("Settings/style", "").toString())) { + if (!application.setStyleFile(settings.getStyleName())) { // disable invalid stylesheet - initSettings.setValue("Settings/style", ""); + settings.setStyleName(""); } int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(initSettings, organizer, pluginContainer); + MainWindow mainWindow(settings, organizer, pluginContainer); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28405819..7f7ded80 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -291,7 +291,7 @@ public: }; -MainWindow::MainWindow(QSettings &initSettings +MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer , QWidget *parent) @@ -540,8 +540,8 @@ MainWindow::MainWindow(QSettings &initSettings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); - FileDialogMemory::restore(initSettings); + setCategoryListVisible(settings.isCategoryListVisible()); + FileDialogMemory::restore(settings.directInterface()); fixCategories(); diff --git a/src/mainwindow.h b/src/mainwindow.h index aa49205d..7326425a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -103,6 +103,7 @@ namespace Ui { class MainWindow; } +class Settings; class MainWindow : public QMainWindow, public IUserInterface @@ -113,7 +114,7 @@ class MainWindow : public QMainWindow, public IUserInterface public: - explicit MainWindow(QSettings &initSettings, + explicit MainWindow(Settings &settings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); ~MainWindow(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1e164525..72c8dab5 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -480,14 +480,14 @@ return true; } -void OrganizerCore::updateExecutablesList(QSettings &settings) +void OrganizerCore::updateExecutablesList() { if (m_PluginContainer == nullptr) { log::error("can't update executables list now"); return; } - m_ExecutablesList.load(managedGame(), settings); + m_ExecutablesList.load(managedGame(), m_Settings.directInterface()); // TODO this has nothing to do with executables list move to an appropriate // function! diff --git a/src/organizercore.h b/src/organizercore.h index 2aa7e707..926a21f0 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -107,7 +107,7 @@ public: void setManagedGame(MOBase::IPluginGame *game); - void updateExecutablesList(QSettings &settings); + void updateExecutablesList(); void startMOUpdate(); diff --git a/src/settings.cpp b/src/settings.cpp index 77db6918..5d103267 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -28,8 +28,8 @@ using namespace MOBase; Settings *Settings::s_Instance = nullptr; -Settings::Settings(const QSettings &settingsSource) - : m_Settings(settingsSource.fileName(), settingsSource.format()) +Settings::Settings(const QString& path) + : m_Settings(path, QSettings::IniFormat) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -280,7 +280,57 @@ QString Settings::getModDirectory(bool resolve) const QString Settings::getManagedGameDirectory() const { - return m_Settings.value("gamePath", "").toString(); + return QString::fromUtf8(m_Settings.value("gamePath", "").toByteArray()); +} + +void Settings::setManagedGameDirectory(const QString& path) +{ + m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); +} + +QString Settings::getManagedGameName() const +{ + return m_Settings.value("gameName", "").toString(); +} + +void Settings::setManagedGameName(const QString& name) +{ + m_Settings.setValue("gameName", name); +} + +QString Settings::getManagedGameEdition() const +{ + return m_Settings.value("game_edition", "").toString(); +} + +void Settings::setManagedGameEdition(const QString& name) +{ + m_Settings.setValue("game_edition", name); +} + +QString Settings::getSelectedProfileName() const +{ + return QString::fromUtf8(m_Settings.value("selected_profile", "").toByteArray()); +} + +int Settings::getMainWindowMonitor() const +{ + return m_Settings.value("window_monitor", -1).toInt(); +} + +QString Settings::getStyleName() const +{ + return m_Settings.value("Settings/style", "").toString(); +} + +void Settings::setStyleName(const QString& name) +{ + m_Settings.setValue("Settings/style", name); +} + +bool Settings::isCategoryListVisible() const +{ + return m_Settings.value("categorylist_visible", true).toBool(); } QString Settings::getProfileDirectory(bool resolve) const @@ -608,3 +658,24 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } + +void Settings::dump() const +{ + static const QStringList ignore({ + "username", "password", "nexus_api_key" + }); + + log::debug("settings:"); + + m_Settings.beginGroup("Settings"); + + for (auto k : m_Settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } + + log::debug(" . {}={}", k, m_Settings.value(k).toString()); + } + + m_Settings.endGroup(); +} diff --git a/src/settings.h b/src/settings.h index 63718089..f06aece9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -40,7 +40,7 @@ class Settings : public QObject Q_OBJECT public: - Settings(const QSettings &settingsSource); + Settings(const QString& path); ~Settings(); static Settings &instance(); @@ -123,6 +123,24 @@ public: * retrieve the directory where the managed game is stored (with native separators) **/ QString getManagedGameDirectory() const; + void setManagedGameDirectory(const QString& path); + + QString getManagedGameName() const; + void setManagedGameName(const QString& name); + + QString getManagedGameEdition() const; + void setManagedGameEdition(const QString& name); + + QString getSelectedProfileName() const; + + // returns -1 if not set + // + int getMainWindowMonitor() const; + + QString getStyleName() const; + void setStyleName(const QString& name); + + bool isCategoryListVisible() const; /** * retrieve the directory where profiles stored (with native separators) @@ -370,6 +388,8 @@ public: MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + void dump() const; + // temp QMap m_PluginSettings; QMap m_PluginDescriptions; -- cgit v1.3.1 From 07f1ac7a96dcf4c91a24bb1d30af92851ecda78f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 01:55:21 -0400 Subject: split into GeometrySettings removed most of storeSettings() from OrganizerCore: QSettings handles saving by itself, no need for that removed topLevelSplitter from ui, unused since the log widget is in a dock removed QSettings from MainWindow::readSettings() replaced return values for some of the new getters in Settings to std::optional --- src/executableslist.cpp | 9 ++- src/executableslist.h | 5 +- src/filedialogmemory.cpp | 8 ++- src/filedialogmemory.h | 5 +- src/iuserinterface.h | 4 +- src/main.cpp | 96 +++++++++++++++---------- src/mainwindow.cpp | 116 ++++++++++++++---------------- src/mainwindow.h | 4 +- src/mainwindow.ui | 5 -- src/organizercore.cpp | 90 +++++------------------- src/organizercore.h | 4 -- src/settings.cpp | 180 +++++++++++++++++++++++++++++++++++++++++++---- src/settings.h | 55 ++++++++++++--- 13 files changed, 359 insertions(+), 222 deletions(-) (limited to 'src') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 3f76bb6f..2b3219df 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" #include "utility.h" +#include "settings.h" #include #include @@ -64,7 +65,7 @@ bool ExecutablesList::empty() const return m_Executables.empty(); } -void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) +void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) { log::debug("loading executables"); @@ -74,6 +75,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; + auto& settings = const_cast(s.directInterface()); + int numCustomExecutables = settings.beginReadArray("customExecutables"); for (int i = 0; i < numCustomExecutables; ++i) { settings.setArrayIndex(i); @@ -108,8 +111,10 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) dump(); } -void ExecutablesList::store(QSettings& settings) +void ExecutablesList::store(Settings& s) { + auto& settings = s.directInterface(); + settings.remove("customExecutables"); settings.beginWriteArray("customExecutables"); diff --git a/src/executableslist.h b/src/executableslist.h index eda2034e..23cf3cfe 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include namespace MOBase { class IPluginGame; class ExecutableInfo; } +class Settings; /*! * @brief Information about an executable @@ -103,7 +104,7 @@ public: /** * @brief initializes the list from the settings and the given plugin **/ - void load(const MOBase::IPluginGame* game, QSettings& settings); + void load(const MOBase::IPluginGame* game, const Settings& settings); /** * @brief re-adds all the executables from the plugin and renames existing @@ -114,7 +115,7 @@ public: /** * @brief writes the current list to the settings */ - void store(QSettings& settings); + void store(Settings& settings); /** * @brief get an executable by name diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 308a175e..48828563 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -27,8 +27,10 @@ FileDialogMemory::FileDialogMemory() } -void FileDialogMemory::save(QSettings &settings) +void FileDialogMemory::save(Settings& s) { + auto& settings = s.directInterface(); + settings.remove("recentDirectories"); settings.beginWriteArray("recentDirectories"); int index = 0; @@ -42,8 +44,10 @@ void FileDialogMemory::save(QSettings &settings) } -void FileDialogMemory::restore(QSettings &settings) +void FileDialogMemory::restore(const Settings& s) { + auto& settings = const_cast(s.directInterface()); + int size = settings.beginReadArray("recentDirectories"); for (int i = 0; i < size; ++i) { settings.setArrayIndex(i); diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index 1a72b289..d214a8e6 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -23,7 +23,6 @@ along with Mod Organizer. If not, see . #include #include -#include #include class Settings; @@ -31,8 +30,8 @@ class Settings; class FileDialogMemory { public: - static void save(QSettings &settings); - static void restore(QSettings &settings); + static void save(Settings& settings); + static void restore(const Settings& settings); static QString getOpenFileName( const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), diff --git a/src/iuserinterface.h b/src/iuserinterface.h index bba8de2b..7205f982 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -10,13 +10,13 @@ #include -class QSettings; +class Settings; class IUserInterface { public: - virtual void storeSettings(QSettings &settings) = 0; + virtual void storeSettings(Settings &settings) = 0; virtual void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr) = 0; virtual void registerPluginTools(std::vector toolPlugins) = 0; diff --git a/src/main.cpp b/src/main.cpp index 720ecbf9..3e26ea17 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,7 +62,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -116,7 +115,7 @@ bool bootstrap() shellDelete(QStringList(backupDirectory)); } - // cycle logfile + // cycle log file removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), "usvfs*.log", 5, QDir::Name); @@ -247,7 +246,7 @@ static bool HaveWriteAccess(const std::wstring &path) QString determineProfile(QStringList &arguments, const Settings &settings) { - QString selectedProfileName = settings.getSelectedProfileName(); + auto selectedProfileName = settings.getSelectedProfileName(); { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); @@ -259,14 +258,14 @@ QString determineProfile(QStringList &arguments, const Settings &settings) arguments.removeAt(profileIndex); } - if (selectedProfileName.isEmpty()) { + if (!selectedProfileName) { log::debug("no configured profile"); selectedProfileName = "Default"; } else { - log::debug("configured profile: {}", selectedProfileName); + log::debug("configured profile: {}", *selectedProfileName); } - return selectedProfileName; + return *selectedProfileName; } MOBase::IPluginGame *selectGame( @@ -290,27 +289,27 @@ MOBase::IPluginGame *determineCurrentGame( //user has done something odd. //If the game name has been set up, try to use that. - const QString gameName = settings.getManagedGameName(); - bool gameConfigured = !gameName.isEmpty(); + const auto gameName = settings.getManagedGameName(); + const bool gameConfigured = (gameName.has_value() && *gameName != ""); if (gameConfigured) { - MOBase::IPluginGame *game = plugins.managedGame(gameName); + MOBase::IPluginGame *game = plugins.managedGame(*gameName); if (game == nullptr) { - reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); + reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(*gameName)); return nullptr; } - QString gamePath = settings.getManagedGameDirectory(); - if (gamePath == "") { + auto gamePath = settings.getManagedGameDirectory(); + if (!gamePath || *gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } - QDir gameDir(gamePath); + QDir gameDir(*gamePath); QFileInfo directoryInfo(gameDir.path()); if (directoryInfo.isSymLink()) { reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " - "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(*gamePath)); } if (game->looksValid(gameDir)) { @@ -321,17 +320,20 @@ MOBase::IPluginGame *determineCurrentGame( //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - const QString gamePath = settings.getManagedGameDirectory(); - reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). - arg(gameName).arg(gamePath)); + const auto gamePath = settings.getManagedGameDirectory(); + + reportError( + QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") + .arg(*gameName).arg(gamePath ? *gamePath : "")); } - SelectionDialog selection(gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + SelectionDialog selection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); for (IPluginGame *game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0) + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) continue; //Only add games that are installed @@ -355,9 +357,11 @@ MOBase::IPluginGame *determineCurrentGame( return selectGame(settings, game->gameDirectory(), game); } - gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), - QString(), QFileDialog::ShowDirsOnly); + gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + QString(), QFileDialog::ShowDirsOnly); + if (!gamePath.isEmpty()) { QDir gameDir(gamePath); QFileInfo directoryInfo(gamePath); @@ -368,7 +372,7 @@ MOBase::IPluginGame *determineCurrentGame( QList possibleGames; for (IPluginGame * const game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game - if (gameConfigured && gameName.compare(game->gameName(), Qt::CaseInsensitive) != 0) + if (gameConfigured && gameName->compare(game->gameName(), Qt::CaseInsensitive) != 0) continue; //Only try plugins that look valid for this directory @@ -376,24 +380,31 @@ MOBase::IPluginGame *determineCurrentGame( possibleGames.append(game); } } + if (possibleGames.count() > 1) { - SelectionDialog browseSelection(gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) - : QObject::tr("Please select the game to manage"), - nullptr, QSize(32, 32)); + SelectionDialog browseSelection(gameConfigured ? + QObject::tr("Please select the installation of %1 to manage").arg(*gameName) : + QObject::tr("Please select the game to manage"), + nullptr, QSize(32, 32)); + for (IPluginGame *game : possibleGames) { browseSelection.addChoice(game->gameIcon(), game->gameName(), gamePath, QVariant::fromValue(game)); } + if (browseSelection.exec() == QDialog::Accepted) { return selectGame(settings, gameDir, browseSelection.getChoiceData().value()); } else { - reportError(gameConfigured ? QObject::tr("Canceled finding %1 in \"%2\".").arg(gameName).arg(gamePath) - : QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); + reportError(gameConfigured ? + QObject::tr("Canceled finding %1 in \"%2\".").arg(*gameName).arg(gamePath) : + QObject::tr("Canceled finding game in \"%1\".").arg(gamePath)); } } else if(possibleGames.count() == 1) { return selectGame(settings, gameDir, possibleGames[0]); } else { if (gameConfigured) { - reportError(QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.").arg(gameName).arg(gamePath)); + reportError( + QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.") + .arg(*gameName).arg(gamePath)); } else { QString supportedGames; @@ -608,7 +619,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, organizer.setManagedGame(game); organizer.createDefaultProfile(); - if (settings.getManagedGameEdition() == "") { + QString edition; + + if (auto v=settings.getManagedGameEdition()) { + edition = *v; + } else { QStringList editions = game->gameVariants(); if (editions.size() > 1) { SelectionDialog selection( @@ -624,12 +639,15 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (selection.exec() == QDialog::Rejected) { return 1; } else { - settings.setManagedGameEdition(selection.getChoiceString()); + edition = selection.getChoiceString(); + settings.setManagedGameEdition(edition); } } } - game->setGameVariant(settings.getManagedGameEdition()); + Q_ASSERT(!edition.isEmpty()); + + game->setGameVariant(edition); log::info("managing game at {}", game->gameDirectory().absolutePath()); @@ -679,10 +697,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - const int monitor = settings.getMainWindowMonitor(); - if (monitor != -1 && QGuiApplication::screens().size() > monitor) { - QGuiApplication::screens().at(monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(monitor)->geometry().center(); + const auto monitor = settings.geometry().getMainWindowMonitor(); + if (monitor && QGuiApplication::screens().size() > *monitor) { + QGuiApplication::screens().at(*monitor)->geometry().center(); + const QPoint center = QGuiApplication::screens().at(*monitor)->geometry().center(); splash.move(center - splash.rect().center()); } else { const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); @@ -703,7 +721,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.getStyleName())) { + if (!application.setStyleFile(settings.getStyleName().value_or(""))) { // disable invalid stylesheet settings.setStyleName(""); } @@ -726,7 +744,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); - mainWindow.readSettings(); + mainWindow.readSettings(settings); log::debug("displaying main window"); mainWindow.show(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7f7ded80..e77d08b1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -216,26 +216,24 @@ const QSize LargeToolbarSize(42, 36); class DockFixer { public: - static void save(MainWindow* mw, QSettings& settings) + static void save(MainWindow* mw, Settings& settings) { - const auto docks = mw->findChildren(); - // saves the size of each dock - for (int i=0; ifindChildren()) { int size = 0; // save the width for horizontal docks, or the height for vertical - if (orientation(mw, docks[i]) == Qt::Horizontal) { - size = docks[i]->size().width(); + if (orientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); } else { - size = docks[i]->size().height(); + size = dock->size().height(); } - settings.setValue(settingName(docks[i]), size); + settings.geometry().setDockSize(dock->objectName(), size); } } - static void restore(MainWindow* mw, const QSettings& settings) + static void restore(MainWindow* mw, const Settings& settings) { struct DockInfo { @@ -246,16 +244,11 @@ public: std::vector dockInfos; - const auto docks = mw->findChildren(); - // for each dock - for (int i=0; ifindChildren()) { + if (auto size=settings.geometry().getDockSize(dock->objectName())) { // remember this dock, its size and orientation - const auto size = settings.value(name).toInt(); - dockInfos.push_back({docks[i], size, orientation(mw, docks[i])}); + dockInfos.push_back({dock, *size, orientation(mw, dock)}); } } @@ -264,30 +257,25 @@ public: // // some people said a single processEvents() call is enough, but it doesn't // look like it - QTimer::singleShot(1, [=] { + QTimer::singleShot(5, [=] { for (const auto& info : dockInfos) { mw->resizeDocks({info.d}, {info.size}, info.ori); } - }); + }); } - static Qt::Orientation orientation(QMainWindow* mw, QDockWidget* d) + static Qt::Orientation orientation(QMainWindow* mw, const QDockWidget* d) { // docks in these areas are horizontal const auto horizontalAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; - if (mw->dockWidgetArea(d) & horizontalAreas) { + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { return Qt::Horizontal; } else { return Qt::Vertical; } } - - static QString settingName(QDockWidget* d) - { - return "geometry/" + d->objectName() + "_size"; - } }; @@ -359,9 +347,6 @@ MainWindow::MainWindow(Settings &settings ui->logList->setCore(m_OrganizerCore); - int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value - ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); - updateProblemsButton(); setupToolbar(); @@ -540,8 +525,7 @@ MainWindow::MainWindow(Settings &settings connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - setCategoryListVisible(settings.isCategoryListVisible()); - FileDialogMemory::restore(settings.directInterface()); + FileDialogMemory::restore(settings); fixCategories(); @@ -2247,52 +2231,50 @@ void MainWindow::activateProxy(bool activate) busyDialog.hide(); } -void MainWindow::readSettings() +void MainWindow::readSettings(const Settings& settings) { - QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - - if (settings.contains("window_geometry")) { - restoreGeometry(settings.value("window_geometry").toByteArray()); + if (auto v=settings.geometry().getMainWindow()) { + restoreGeometry(*v); } - if (settings.contains("window_state")) { - restoreState(settings.value("window_state").toByteArray()); + if (auto v=settings.geometry().getMainWindowState()) { + restoreState(*v); } - if (settings.contains("toolbar_size")) { - setToolbarSize(settings.value("toolbar_size").toSize()); + if (auto v=settings.geometry().getToolbarSize()) { + setToolbarSize(*v); } - if (settings.contains("toolbar_button_style")) { - setToolbarButtonStyle(static_cast( - settings.value("toolbar_button_style").toInt())); + if (auto v=settings.geometry().getToolbarButtonStyle()) { + setToolbarButtonStyle(*v); } - if (settings.contains("menubar_visible")) { - showMenuBar(settings.value("menubar_visible").toBool()); + if (auto v=settings.geometry().getMenubarVisible()) { + showMenuBar(*v); } - if (settings.contains("statusbar_visible")) { - showStatusBar(settings.value("statusbar_visible").toBool()); + if (auto v=settings.geometry().getStatusbarVisible()) { + showStatusBar(*v); } - if (settings.contains("window_split")) { - ui->splitter->restoreState(settings.value("window_split").toByteArray()); + if (auto v=settings.geometry().getMainSplitterState()) { + ui->splitter->restoreState(*v); } - if (settings.contains("log_split")) { - ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); + { + auto v = settings.geometry().getFiltersVisible().value_or(false); + setCategoryListVisible(v); + ui->displayCategoriesBtn->setChecked(v); } - bool filtersVisible = settings.value("filters_visible", false).toBool(); - setCategoryListVisible(filtersVisible); - ui->displayCategoriesBtn->setChecked(filtersVisible); - - int selectedExecutable = settings.value("selected_executable").toInt(); - setExecutableIndex(selectedExecutable); + if (auto v=settings.getSelectedExecutable()) { + setExecutableIndex(*v); + } - if (settings.value("Settings/use_proxy", false).toBool()) { - activateProxy(true); + if (auto v=settings.getUseProxy()) { + if (*v) { + activateProxy(true); + } } DockFixer::restore(this, settings); @@ -2335,6 +2317,12 @@ void MainWindow::processUpdates() { ui->downloadView->header()->hideSection(i); } } + if (lastVersion < QVersionNumber(2, 2, 2)) { + QSettings &instance = Settings::instance().directInterface(); + + // log splitter is gone, it's a dock now + instance.remove("log_split"); + } } if (currentVersion > lastVersion) { @@ -2354,7 +2342,9 @@ void MainWindow::processUpdates() { settings.setValue("version", currentVersion.toString()); } -void MainWindow::storeSettings(QSettings &settings) { +void MainWindow::storeSettings(Settings& s) { + auto& settings = s.directInterface(); + settings.setValue("group_state", ui->groupCombo->currentIndex()); settings.setValue("selected_executable", ui->executablesListBox->currentIndex()); @@ -2367,7 +2357,6 @@ void MainWindow::storeSettings(QSettings &settings) { settings.remove("menubar_visible"); settings.remove("window_split"); settings.remove("window_monitor"); - settings.remove("log_split"); settings.remove("filters_visible"); settings.remove("browser_geometry"); settings.remove("geometry"); @@ -2383,7 +2372,6 @@ void MainWindow::storeSettings(QSettings &settings) { QScreen *screen = this->window()->windowHandle()->screen(); int screenId = QGuiApplication::screens().indexOf(screen); settings.setValue("window_monitor", screenId); - settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); @@ -2392,7 +2380,7 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue(key, kv.second->saveState()); } - DockFixer::save(this, settings); + DockFixer::save(this, s); } } @@ -5213,7 +5201,7 @@ void MainWindow::on_actionSettings_triggered() QString oldModDirectory(settings.getModDirectory()); QString oldCacheDirectory(settings.getCacheDirectory()); QString oldProfilesDirectory(settings.getProfileDirectory()); - QString oldManagedGameDirectory(settings.getManagedGameDirectory()); + QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); bool oldDisplayForeign(settings.displayForeign()); bool proxy = settings.useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 7326425a..d4513c0f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -119,8 +119,8 @@ public: QWidget *parent = 0); ~MainWindow(); - void storeSettings(QSettings &settings) override; - void readSettings(); + void storeSettings(Settings& settings) override; + void readSettings(const Settings& settings); void processUpdates(); virtual ILockedWaitingForProcess* lock() override; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6c6d0bca..e9910b83 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -47,10 +47,6 @@ 0 - - - Qt::Vertical - @@ -1286,7 +1282,6 @@ p, li { white-space: pre-wrap; } - diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 72c8dab5..a64d93b4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -94,15 +94,6 @@ static bool isOnline() return false; } -static bool renameFile(const QString &oldName, const QString &newName, - bool overwrite = true) -{ - if (overwrite && QFile::exists(newName)) { - QFile::remove(newName); - } - return QFile::rename(oldName, newName); -} - static std::wstring getProcessName(HANDLE process) { wchar_t buffer[MAX_PATH]; @@ -342,80 +333,37 @@ OrganizerCore::~OrganizerCore() delete m_DirectoryStructure; } -QString OrganizerCore::commitSettings(const QString &iniFile) -{ - if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) { - DWORD err = ::GetLastError(); - // make a second attempt using qt functions but if that fails print the - // error from the first attempt - if (!renameFile(iniFile + ".new", iniFile)) { - return QString::fromStdWString(formatSystemMessage(err)); - } - } - return QString(); -} - -QSettings::Status OrganizerCore::storeSettings(const QString &fileName) +void OrganizerCore::storeSettings() { - QSettings settings(fileName, QSettings::IniFormat); - if (m_UserInterface != nullptr) { - m_UserInterface->storeSettings(settings); + m_UserInterface->storeSettings(m_Settings); } if (m_CurrentProfile != nullptr) { - settings.setValue("selected_profile", - m_CurrentProfile->name().toUtf8().constData()); + m_Settings.setSelectedProfileName(m_CurrentProfile->name()); } - m_ExecutablesList.store(settings); - - FileDialogMemory::save(settings); + m_ExecutablesList.store(m_Settings); - settings.sync(); - return settings.status(); -} - -void OrganizerCore::storeSettings() -{ - QString iniFile = qApp->property("dataPath").toString() + "/" - + QString::fromStdWString(AppConfig::iniFileName()); - if (QFileInfo(iniFile).exists()) { - if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { - const auto e = GetLastError(); - QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occurred trying to update MO settings to %1: %2") - .arg(iniFile) - .arg(QString::fromStdWString(formatSystemMessage(e)))); - return; - } - } + FileDialogMemory::save(m_Settings); - QString writeTarget = iniFile + ".new"; + const auto result = m_Settings.sync(); - QSettings::Status result = storeSettings(writeTarget); + if (result != QSettings::NoError) { + QString reason; - if (result == QSettings::NoError) { - QString errMsg = commitSettings(iniFile); - if (!errMsg.isEmpty()) { - log::warn( - "settings file not writable, may be locked by another " - "application, trying direct write"); - writeTarget = iniFile; - result = storeSettings(iniFile); + if (result == QSettings::AccessError) { + reason = tr("File is write protected"); + } else if (result == QSettings::FormatError) { + reason = tr("Invalid file format (probably a bug)"); + } else { + reason = tr("Unknown error %1").arg(result); } - } - if (result != QSettings::NoError) { - QString reason = result == QSettings::AccessError - ? tr("File is write protected") - : result == QSettings::FormatError - ? tr("Invalid file format (probably a bug)") - : tr("Unknown error %1").arg(result); + QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occurred trying to write back MO settings to %1: %2") - .arg(writeTarget, reason)); + qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occurred trying to write back MO settings to %1: %2") + .arg(m_Settings.getFilename(), reason)); } } @@ -487,7 +435,7 @@ void OrganizerCore::updateExecutablesList() return; } - m_ExecutablesList.load(managedGame(), m_Settings.directInterface()); + m_ExecutablesList.load(managedGame(), m_Settings); // TODO this has nothing to do with executables list move to an appropriate // function! diff --git a/src/organizercore.h b/src/organizercore.h index 926a21f0..4bcfe745 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -288,10 +288,6 @@ private: void storeSettings(); - QSettings::Status storeSettings(const QString &fileName); - - QString commitSettings(const QString &iniFile); - bool queryApi(QString &apiKey); void updateModActiveState(int index, bool active); diff --git a/src/settings.cpp b/src/settings.cpp index 5d103267..d843a0db 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -26,10 +26,56 @@ along with Mod Organizer. If not, see . using namespace MOBase; +template +T convertVariant(const QVariant& v); + +template <> +QByteArray convertVariant(const QVariant& v) +{ + return v.toByteArray(); +} + +template <> +QString convertVariant(const QVariant& v) +{ + return v.toString(); +} + +template <> +int convertVariant(const QVariant& v) +{ + return v.toInt(); +} + +template <> +bool convertVariant(const QVariant& v) +{ + return v.toBool(); +} + +template <> +QSize convertVariant(const QVariant& v) +{ + return v.toSize(); +} + + + +template +std::optional getOptional(const QSettings& s, const QString& name) +{ + if (s.contains(name)) { + return convertVariant(s.value(name)); + } + + return {}; +} + + Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) - : m_Settings(path, QSettings::IniFormat) + : m_Settings(path, QSettings::IniFormat), m_Geometry(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -51,6 +97,11 @@ Settings &Settings::instance() return *s_Instance; } +QString Settings::getFilename() const +{ + return m_Settings.fileName(); +} + void Settings::clearPlugins() { m_Plugins.clear(); @@ -278,9 +329,13 @@ QString Settings::getModDirectory(bool resolve) const return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); } -QString Settings::getManagedGameDirectory() const +std::optional Settings::getManagedGameDirectory() const { - return QString::fromUtf8(m_Settings.value("gamePath", "").toByteArray()); + if (auto v=getOptional(m_Settings, "gamePath")) { + return QString::fromUtf8(*v); + } + + return {}; } void Settings::setManagedGameDirectory(const QString& path) @@ -288,9 +343,9 @@ void Settings::setManagedGameDirectory(const QString& path) m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); } -QString Settings::getManagedGameName() const +std::optional Settings::getManagedGameName() const { - return m_Settings.value("gameName", "").toString(); + return getOptional(m_Settings, "gameName"); } void Settings::setManagedGameName(const QString& name) @@ -298,9 +353,9 @@ void Settings::setManagedGameName(const QString& name) m_Settings.setValue("gameName", name); } -QString Settings::getManagedGameEdition() const +std::optional Settings::getManagedGameEdition() const { - return m_Settings.value("game_edition", "").toString(); + return getOptional(m_Settings, "game_edition"); } void Settings::setManagedGameEdition(const QString& name) @@ -308,19 +363,23 @@ void Settings::setManagedGameEdition(const QString& name) m_Settings.setValue("game_edition", name); } -QString Settings::getSelectedProfileName() const +std::optional Settings::getSelectedProfileName() const { - return QString::fromUtf8(m_Settings.value("selected_profile", "").toByteArray()); + if (auto v=getOptional(m_Settings, "selected_profile")) { + return QString::fromUtf8(*v); + } + + return {}; } -int Settings::getMainWindowMonitor() const +void Settings::setSelectedProfileName(const QString& name) { - return m_Settings.value("window_monitor", -1).toInt(); + m_Settings.setValue("selected_profile", name.toUtf8()); } -QString Settings::getStyleName() const +std::optional Settings::getStyleName() const { - return m_Settings.value("Settings/style", "").toString(); + return getOptional(m_Settings, "Settings/style"); } void Settings::setStyleName(const QString& name) @@ -328,9 +387,14 @@ void Settings::setStyleName(const QString& name) m_Settings.setValue("Settings/style", name); } -bool Settings::isCategoryListVisible() const +std::optional Settings::getSelectedExecutable() const { - return m_Settings.value("categorylist_visible", true).toBool(); + return getOptional(m_Settings, "selected_executable"); +} + +std::optional Settings::getUseProxy() const +{ + return getOptional(m_Settings, "Settings/use_proxy"); } QString Settings::getProfileDirectory(bool resolve) const @@ -659,6 +723,22 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } +GeometrySettings& Settings::geometry() +{ + return m_Geometry; +} + +const GeometrySettings& Settings::geometry() const +{ + return m_Geometry; +} + +QSettings::Status Settings::sync() const +{ + m_Settings.sync(); + return m_Settings.status(); +} + void Settings::dump() const { static const QStringList ignore({ @@ -679,3 +759,73 @@ void Settings::dump() const m_Settings.endGroup(); } + + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s) +{ +} + +std::optional GeometrySettings::getMainWindow() const +{ + return getOptional(m_Settings, "window_geometry"); +} + +std::optional GeometrySettings::getMainWindowState() const +{ + return getOptional(m_Settings, "window_state"); +} + +std::optional GeometrySettings::getToolbarSize() const +{ + return getOptional(m_Settings, "toolbar_size"); +} + +std::optional GeometrySettings::getToolbarButtonStyle() const +{ + if (auto v=getOptional(m_Settings, "toolbar_button_style")) { + return static_cast(*v); + } + + return {}; +} + +std::optional GeometrySettings::getMenubarVisible() const +{ + return getOptional(m_Settings, "menubar_visible"); +} + +std::optional GeometrySettings::getStatusbarVisible() const +{ + return getOptional(m_Settings, "statusbar_visible"); +} + +std::optional GeometrySettings::getMainSplitterState() const +{ + return getOptional(m_Settings, "window_split"); +} + +std::optional GeometrySettings::getFiltersVisible() const +{ + return getOptional(m_Settings, "filters_visible"); +} + +std::optional GeometrySettings::getMainWindowMonitor() const +{ + return getOptional(m_Settings, "window_monitor"); +} + +void GeometrySettings::setDockSize(const QString& name, int size) +{ + m_Settings.setValue("geometry/" + name + "_size", size); +} + +std::optional GeometrySettings::getDockSize(const QString& name) const +{ + return getOptional(m_Settings, "geometry/" + name + "_size"); +} + +std::optional GeometrySettings::isCategoryListVisible() const +{ + return getOptional(m_Settings, "categorylist_visible"); +} diff --git a/src/settings.h b/src/settings.h index f06aece9..066843c2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,13 +31,40 @@ namespace MOBase { class PluginContainer; struct ServerInfo; + +class GeometrySettings +{ +public: + GeometrySettings(QSettings& s); + + std::optional getMainWindow() const; + std::optional getMainWindowState() const; + std::optional getToolbarSize() const; + std::optional getToolbarButtonStyle() const; + std::optional getMenubarVisible() const; + std::optional getStatusbarVisible() const; + std::optional getMainSplitterState() const; + std::optional getFiltersVisible() const; + + std::optional getMainWindowMonitor() const; + void setDockSize(const QString& name, int size); + + std::optional getDockSize(const QString& name) const; + + std::optional isCategoryListVisible() const; + +private: + QSettings& m_Settings; +}; + + /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc **/ class Settings : public QObject { - Q_OBJECT + Q_OBJECT; public: Settings(const QString& path); @@ -45,6 +72,8 @@ public: static Settings &instance(); + QString getFilename() const; + /** * unregister all plugins from settings */ @@ -122,25 +151,26 @@ public: /** * retrieve the directory where the managed game is stored (with native separators) **/ - QString getManagedGameDirectory() const; + std::optional getManagedGameDirectory() const; void setManagedGameDirectory(const QString& path); - QString getManagedGameName() const; + std::optional getManagedGameName() const; void setManagedGameName(const QString& name); - QString getManagedGameEdition() const; + std::optional getManagedGameEdition() const; void setManagedGameEdition(const QString& name); - QString getSelectedProfileName() const; - - // returns -1 if not set - // - int getMainWindowMonitor() const; + std::optional getSelectedProfileName() const; + void setSelectedProfileName(const QString& name); - QString getStyleName() const; + std::optional getStyleName() const; void setStyleName(const QString& name); - bool isCategoryListVisible() const; + std::optional getSelectedExecutable() const; + std::optional getUseProxy() const; + + GeometrySettings& geometry(); + const GeometrySettings& geometry() const; /** * retrieve the directory where profiles stored (with native separators) @@ -388,6 +418,8 @@ public: MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + QSettings::Status sync() const; + void dump() const; // temp @@ -407,6 +439,7 @@ private: static Settings *s_Instance; MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; + GeometrySettings m_Geometry; LoadMechanism m_LoadMechanism; std::vector m_Plugins; -- cgit v1.3.1 From e4418b95fa24f9caea32adfe9d957ce37e46f127 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 02:30:20 -0400 Subject: moved settings updates to Settings::processUpdates() --- src/main.cpp | 2 +- src/mainwindow.cpp | 44 ++++++++++++-------------------------------- src/mainwindow.h | 2 +- src/settings.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 7 +++++++ 5 files changed, 67 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 3e26ea17..506c6270 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -739,7 +739,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); - mainWindow.processUpdates(); + mainWindow.processUpdates(settings); // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e77d08b1..0618f949 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2280,11 +2280,15 @@ void MainWindow::readSettings(const Settings& settings) DockFixer::restore(this, settings); } -void MainWindow::processUpdates() { - QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - QVersionNumber lastVersion = QVersionNumber::fromString(settings.value("version", "2.1.2").toString()).normalized(); - QVersionNumber currentVersion = QVersionNumber::fromString(m_OrganizerCore.getVersion().displayString()).normalized(); - if (!m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { +void MainWindow::processUpdates(Settings& settings) { + const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); + + const auto lastVersion = settings.getVersion().value_or(earliest); + const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); + + settings.processUpdates(currentVersion, lastVersion); + + if (!settings.getFirstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { @@ -2293,41 +2297,20 @@ void MainWindow::processUpdates() { lastHidden = hidden; } } + if (lastVersion < QVersionNumber(2, 1, 6)) { ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); } - if (lastVersion < QVersionNumber(2, 2, 0)) { - QSettings &instance = Settings::instance().directInterface(); - instance.beginGroup("Settings"); - instance.remove("steam_password"); - instance.remove("nexus_username"); - instance.remove("nexus_password"); - instance.remove("nexus_login"); - instance.remove("nexus_api_key"); - instance.remove("ask_for_nexuspw"); - instance.remove("nmm_version"); - instance.endGroup(); - instance.beginGroup("Servers"); - instance.remove(""); - instance.endGroup(); - } + if (lastVersion < QVersionNumber(2, 2, 1)) { // hide new columns by default for (int i=DownloadList::COL_MODNAME; idownloadView->header()->hideSection(i); } } - if (lastVersion < QVersionNumber(2, 2, 2)) { - QSettings &instance = Settings::instance().directInterface(); - - // log splitter is gone, it's a dock now - instance.remove("log_split"); - } } - if (currentVersion > lastVersion) { - //NOP - } else if (currentVersion < lastVersion) { + if (currentVersion < lastVersion) { const auto text = tr( "Notice: Your current MO version (%1) is lower than the previously used one (%2). " "The GUI may not downgrade gracefully, so you may experience oddities. " @@ -2337,9 +2320,6 @@ void MainWindow::processUpdates() { log::warn("{}", text); } - - //save version in all case - settings.setValue("version", currentVersion.toString()); } void MainWindow::storeSettings(Settings& s) { diff --git a/src/mainwindow.h b/src/mainwindow.h index d4513c0f..e8f60211 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -121,7 +121,7 @@ public: void storeSettings(Settings& settings) override; void readSettings(const Settings& settings); - void processUpdates(); + void processUpdates(Settings& settings); virtual ILockedWaitingForProcess* lock() override; virtual void unlock() override; diff --git a/src/settings.cpp b/src/settings.cpp index d843a0db..35be1298 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -97,6 +97,38 @@ Settings &Settings::instance() return *s_Instance; } +void Settings::processUpdates( + const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) +{ + if (getFirstStart()) { + return; + } + + if (lastVersion < QVersionNumber(2, 2, 0)) { + m_Settings.beginGroup("Settings"); + m_Settings.remove("steam_password"); + m_Settings.remove("nexus_username"); + m_Settings.remove("nexus_password"); + m_Settings.remove("nexus_login"); + m_Settings.remove("nexus_api_key"); + m_Settings.remove("ask_for_nexuspw"); + m_Settings.remove("nmm_version"); + m_Settings.endGroup(); + + m_Settings.beginGroup("Servers"); + m_Settings.remove(""); + m_Settings.endGroup(); + } + + if (lastVersion < QVersionNumber(2, 2, 2)) { + // log splitter is gone, it's a dock now + m_Settings.remove("log_split"); + } + + //save version in all case + m_Settings.setValue("version", currentVersion.toString()); +} + QString Settings::getFilename() const { return m_Settings.fileName(); @@ -397,6 +429,20 @@ std::optional Settings::getUseProxy() const return getOptional(m_Settings, "Settings/use_proxy"); } +std::optional Settings::getVersion() const +{ + if (auto v=getOptional(m_Settings, "version")) { + return QVersionNumber::fromString(*v).normalized(); + } + + return {}; +} + +bool Settings::getFirstStart() const +{ + return getOptional(m_Settings, "first_start").value_or(true); +} + QString Settings::getProfileDirectory(bool resolve) const { return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); diff --git a/src/settings.h b/src/settings.h index 066843c2..bf66c0dd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -72,6 +72,9 @@ public: static Settings &instance(); + void processUpdates( + const QVersionNumber& currentVersion, const QVersionNumber& lastVersion); + QString getFilename() const; /** @@ -169,9 +172,13 @@ public: std::optional getSelectedExecutable() const; std::optional getUseProxy() const; + std::optional getVersion() const; + bool getFirstStart() const; + GeometrySettings& geometry(); const GeometrySettings& geometry() const; + /** * retrieve the directory where profiles stored (with native separators) **/ -- cgit v1.3.1 From e40245abf46f133292636909fbacf10fc0712932 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 02:42:16 -0400 Subject: moved geometry handling to EditExecutablesDialog itself --- src/editexecutablesdialog.cpp | 14 ++++++++++++++ src/editexecutablesdialog.h | 4 ++++ src/mainwindow.cpp | 9 +-------- src/settings.cpp | 10 ++++++++++ src/settings.h | 2 ++ 5 files changed, 31 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 3ec3d64f..9c5ae44a 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -65,6 +65,20 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) EditExecutablesDialog::~EditExecutablesDialog() = default; +int EditExecutablesDialog::exec() +{ + auto& settings = m_organizerCore.settings(); + + if (auto v=settings.geometry().getExecutablesDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setExecutablesDialog(saveGeometry()); + + return r; +} void EditExecutablesDialog::loadCustomOverwrites() { diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 9715489e..494f0651 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -151,6 +151,10 @@ public: ~EditExecutablesDialog(); + // also saves and restores geometry + // + int exec() override; + ExecutablesList getExecutablesList() const; const CustomOverwrites& getCustomOverwrites() const; const ForcedLibraries& getForcedLibraries() const; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0618f949..7ef0c9b9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2480,21 +2480,14 @@ bool MainWindow::modifyExecutablesDialog() 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()); - } - result = (dialog.exec() == QDialog::Accepted); - settings.setValue(key, dialog.saveGeometry()); refreshExecutablesList(); updatePinnedExecutables(); } catch (const std::exception &e) { reportError(e.what()); } + return result; } diff --git a/src/settings.cpp b/src/settings.cpp index 35be1298..834bd1d8 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -856,6 +856,16 @@ std::optional GeometrySettings::getFiltersVisible() const return getOptional(m_Settings, "filters_visible"); } +std::optional GeometrySettings::getExecutablesDialog() const +{ + return getOptional(m_Settings, "geometry/EditExecutablesDialog"); +} + +void GeometrySettings::setExecutablesDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/EditExecutablesDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index bf66c0dd..0cdccd87 100644 --- a/src/settings.h +++ b/src/settings.h @@ -45,6 +45,8 @@ public: std::optional getStatusbarVisible() const; std::optional getMainSplitterState() const; std::optional getFiltersVisible() const; + std::optional getExecutablesDialog() const; + void setExecutablesDialog(const QByteArray& v); std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 7eed0450e84cc465b0d163a64ebb4d410db688c4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 02:52:55 -0400 Subject: moved geometry handling to ProfilesDialog --- src/mainwindow.cpp | 8 ++------ src/profilesdialog.cpp | 15 +++++++++++++++ src/profilesdialog.h | 4 ++++ src/settings.cpp | 10 ++++++++++ src/settings.h | 4 ++++ 5 files changed, 35 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7ef0c9b9..ac86d9d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2554,17 +2554,13 @@ void MainWindow::on_actionAdd_Profile_triggered() ProfilesDialog profilesDialog(m_OrganizerCore.currentProfile()->name(), m_OrganizerCore.managedGame(), this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(profilesDialog.objectName()); - if (settings.contains(key)) { - profilesDialog.restoreGeometry(settings.value(key).toByteArray()); - } + // workaround: need to disable monitoring of the saves directory, otherwise the active // profile directory is locked stopMonitorSaves(); profilesDialog.exec(); - settings.setValue(key, profilesDialog.saveGeometry()); refreshSaveList(); // since the save list may now be outdated we have to refresh it completely + if (refreshProfiles() && !profilesDialog.failed()) { break; } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index d7863fc8..25fff2b2 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -84,6 +84,21 @@ ProfilesDialog::~ProfilesDialog() delete ui; } +int ProfilesDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getProfilesDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setProfilesDialog(saveGeometry()); + + return r; +} + void ProfilesDialog::showEvent(QShowEvent *event) { TutorableDialog::showEvent(event); diff --git a/src/profilesdialog.h b/src/profilesdialog.h index a328ce40..a47367be 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -51,6 +51,10 @@ public: explicit ProfilesDialog(const QString &profileName, MOBase::IPluginGame const *game, QWidget *parent = 0); ~ProfilesDialog(); + // also saves and restores geometry + // + int exec() override; + /** * @return true if creation of a new profile failed * @todo the notion of a fail state makes little sense in the current dialog diff --git a/src/settings.cpp b/src/settings.cpp index 834bd1d8..1f5abb2a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -866,6 +866,16 @@ void GeometrySettings::setExecutablesDialog(const QByteArray& v) m_Settings.setValue("geometry/EditExecutablesDialog", v); } +std::optional GeometrySettings::getProfilesDialog() const +{ + return getOptional(m_Settings, "geometry/ProfilesDialog"); +} + +void GeometrySettings::setProfilesDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/ProfilesDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 0cdccd87..6d51c610 100644 --- a/src/settings.h +++ b/src/settings.h @@ -45,9 +45,13 @@ public: std::optional getStatusbarVisible() const; std::optional getMainSplitterState() const; std::optional getFiltersVisible() const; + std::optional getExecutablesDialog() const; void setExecutablesDialog(const QByteArray& v); + std::optional getProfilesDialog() const; + void setProfilesDialog(const QByteArray& v); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From cc3a16c6e9d58ed68a31be52f9fe2ef1d514ff5f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 03:28:33 -0400 Subject: moved geometry handling to mod info and overwrite dialogs --- src/mainwindow.cpp | 18 +----------- src/modinfodialog.cpp | 72 +++++++++++++-------------------------------- src/modinfodialog.h | 26 ++++++++-------- src/overwriteinfodialog.cpp | 19 ++++++++++++ src/overwriteinfodialog.h | 11 ++++++- src/settings.cpp | 66 +++++++++++++++++++++++++++++++++++++++++ src/settings.h | 9 ++++++ 7 files changed, 137 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ac86d9d8..32f728d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3165,9 +3165,6 @@ void MainWindow::overwriteClosed(int) OverwriteInfoDialog *dialog = this->findChild("__overwriteDialog"); if (dialog != nullptr) { m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo()); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog->objectName()); - settings.setValue(key, dialog->saveGeometry()); dialog->deleteLater(); } m_OrganizerCore.refreshDirectoryStructure(); @@ -3191,11 +3188,7 @@ void MainWindow::displayModInformation( } else { qobject_cast(dialog)->setModInfo(modInfo); } - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog->objectName()); - if (settings.contains(key)) { - dialog->restoreGeometry(settings.value(key).toByteArray()); - } + dialog->show(); dialog->raise(); dialog->activateWindow(); @@ -3214,16 +3207,7 @@ void MainWindow::displayModInformation( dialog.selectTab(tabID); } - dialog.restoreState(m_OrganizerCore.settings()); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } - dialog.exec(); - dialog.saveState(m_OrganizerCore.settings()); - settings.setValue(key, dialog.saveGeometry()); modInfo->saveMeta(); emit modInfoDisplayed(); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4b1e2f76..5e614358 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -210,6 +210,11 @@ void ModInfoDialog::createTabs() int ModInfoDialog::exec() { + restoreState(); + if (auto v=m_core->settings().geometry().getModInfoDialog()) { + restoreGeometry(*v); + } + // whether to select the first tab; if the main window requested a specific // tab, it is selected when encountered in update() const auto selectFirst = (m_initialTab == ModInfoTabIDs::None); @@ -220,7 +225,12 @@ int ModInfoDialog::exec() ui->tabWidget->setCurrentIndex(0); } - return TutorableDialog::exec(); + const int r = TutorableDialog::exec(); + + saveState(); + m_core->settings().geometry().setModInfoDialog(saveGeometry()); + + return r; } void ModInfoDialog::setMod(ModInfo::Ptr mod) @@ -356,7 +366,7 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) if (!firstTime) { // but don't do it the first time visibility is set because the tabs are // in the default order, which will clobber the current settings - saveTabOrder(Settings::instance()); + saveTabOrder(); } // remember selection, if any @@ -375,7 +385,7 @@ void ModInfoDialog::reAddTabs( Q_ASSERT(visibility.size() == m_tabs.size()); // ordered tab names from settings - const auto orderedNames = getOrderedTabNames(); + const auto orderedNames = m_core->settings().geometry().getModInfoTabOrder(); // whether the tabs can be sorted; if the object name of a tab widget is not // found in orderedNames, the list cannot be sorted safely @@ -575,37 +585,28 @@ MOShared::FilesOrigin* ModInfoDialog::getOrigin() return origin; } -void ModInfoDialog::saveState(Settings& s) const +void ModInfoDialog::saveState() const { - saveTabOrder(s); - - // remove 2.2.0 settings - s.directInterface().remove("mod_info_tabs"); - s.directInterface().remove("mod_info_conflict_expanders"); - s.directInterface().remove("mod_info_conflicts"); - s.directInterface().remove("mod_info_advanced_conflicts"); - s.directInterface().remove("mod_info_conflicts_overwrite"); - s.directInterface().remove("mod_info_conflicts_noconflict"); - s.directInterface().remove("mod_info_conflicts_overwritten"); + saveTabOrder(); // save state for each tab for (const auto& tabInfo : m_tabs) { - tabInfo.tab->saveState(s); + tabInfo.tab->saveState(m_core->settings()); } } -void ModInfoDialog::restoreState(const Settings& s) +void ModInfoDialog::restoreState() { // tab order is not restored here, it will be picked up if tabs have to be // removed and re-added // restore state for each tab for (const auto& tabInfo : m_tabs) { - tabInfo.tab->restoreState(s); + tabInfo.tab->restoreState(m_core->settings()); } } -void ModInfoDialog::saveTabOrder(Settings& s) const +void ModInfoDialog::saveTabOrder() const { if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { // only save tab state when all tabs are visible @@ -629,40 +630,7 @@ void ModInfoDialog::saveTabOrder(Settings& s) const names += ui->tabWidget->widget(i)->objectName(); } - s.directInterface().setValue("mod_info_tab_order", names); -} - -std::vector ModInfoDialog::getOrderedTabNames() const -{ - const auto& settings = Settings::instance().directInterface(); - - std::vector v; - - if (settings.contains("mod_info_tabs")) { - // old byte array from 2.2.0 - QDataStream stream(settings.value("mod_info_tabs").toByteArray()); - - int count = 0; - stream >> count; - - for (int i=0; i> s; - v.emplace_back(std::move(s)); - } - } else { - // string list - QString string = settings.value("mod_info_tab_order").toString(); - QTextStream stream(&string); - - while (!stream.atEnd()) { - QString s; - stream >> s; - v.emplace_back(std::move(s)); - } - } - - return v; + m_core->settings().geometry().setModInfoTabOrder(names); } void ModInfoDialog::onOriginModified(int originID) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 34555b0c..48680ca4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -61,18 +61,11 @@ public: // void selectTab(ModInfoTabIDs id); - // updates all tabs, selects the initial tab and opens the dialog + // updates all tabs, selects the initial tab, opens the dialog and + // saves/restores geometry // int exec() override; - // saves the dialog state and calls saveState() on all tabs - // - void saveState(Settings& s) const; - - // restores the dialog state and calls restoreState() on all tabs - // - void restoreState(const Settings& s); - signals: // emitted when a tab changes the origin // @@ -146,6 +139,15 @@ private: void createTabs(); + // saves the dialog state and calls saveState() on all tabs + // + void saveState() const; + + // restores the dialog state and calls restoreState() on all tabs + // + void restoreState(); + + // sets the currently selected mod; resets first activation, but doesn't // update anything // @@ -213,11 +215,7 @@ private: // setTabsVisibility() to make sure any changes to order are saved before // re-adding tabs // - void saveTabOrder(Settings& s) const; - - // returns a list of tab names in the order they should appear on the widget - // - std::vector getOrderedTabNames() const; + void saveTabOrder() const; // asks all the tabs if they accept closing the dialog, returns false if one // objected diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 715e11e3..f3ae0ff5 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -104,6 +104,25 @@ OverwriteInfoDialog::~OverwriteInfoDialog() delete ui; } +void OverwriteInfoDialog::showEvent(QShowEvent* e) +{ + const auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getOverwriteDialog()) { + restoreGeometry(*v); + } + + QDialog::showEvent(e); +} + +void OverwriteInfoDialog::done(int r) +{ + auto& settings = Settings::instance(); + settings.geometry().setOverwriteDialog(saveGeometry()); + + QDialog::done(r); +} + void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) { m_ModInfo = modInfo; diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 4b731736..bedb779a 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -31,7 +31,7 @@ class OverwriteInfoDialog; class OverwriteInfoDialog : public QDialog { Q_OBJECT - + public: explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0); @@ -39,8 +39,17 @@ public: ModInfo::Ptr modInfo() const { return m_ModInfo; } + // saves geometry + // + void done(int r) override; + void setModInfo(ModInfo::Ptr modInfo); +protected: + // restores geometry + // + void showEvent(QShowEvent* e) override; + private: void openFile(const QModelIndex &index); diff --git a/src/settings.cpp b/src/settings.cpp index 1f5abb2a..73595ac9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -120,6 +120,16 @@ void Settings::processUpdates( m_Settings.endGroup(); } + if (lastVersion < QVersionNumber(2, 2, 1)) { + m_Settings.remove("mod_info_tabs"); + m_Settings.remove("mod_info_conflict_expanders"); + m_Settings.remove("mod_info_conflicts"); + m_Settings.remove("mod_info_advanced_conflicts"); + m_Settings.remove("mod_info_conflicts_overwrite"); + m_Settings.remove("mod_info_conflicts_noconflict"); + m_Settings.remove("mod_info_conflicts_overwritten"); + } + if (lastVersion < QVersionNumber(2, 2, 2)) { // log splitter is gone, it's a dock now m_Settings.remove("log_split"); @@ -876,6 +886,62 @@ void GeometrySettings::setProfilesDialog(const QByteArray& v) m_Settings.setValue("geometry/ProfilesDialog", v); } +std::optional GeometrySettings::getOverwriteDialog() const +{ + return getOptional(m_Settings, "geometry/__overwriteDialog"); +} + +void GeometrySettings::setOverwriteDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/__overwriteDialog", v); +} + +std::optional GeometrySettings::getModInfoDialog() const +{ + return getOptional(m_Settings, "geometry/ModInfoDialog"); +} + +void GeometrySettings::setModInfoDialog(const QByteArray& v) const +{ + m_Settings.setValue("geometry/ModInfoDialog", v); +} + +QStringList GeometrySettings::getModInfoTabOrder() const +{ + QStringList v; + + if (m_Settings.contains("mod_info_tabs")) { + // old byte array from 2.2.0 + QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + + int count = 0; + stream >> count; + + for (int i=0; i> s; + v.push_back(s); + } + } else { + // string list since 2.2.1 + QString string = m_Settings.value("mod_info_tab_order").toString(); + QTextStream stream(&string); + + while (!stream.atEnd()) { + QString s; + stream >> s; + v.push_back(s); + } + } + + return v; +} + +void GeometrySettings::setModInfoTabOrder(const QString& names) +{ + m_Settings.setValue("mod_info_tab_order", names); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 6d51c610..f4e36b2a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -52,6 +52,15 @@ public: std::optional getProfilesDialog() const; void setProfilesDialog(const QByteArray& v); + std::optional getOverwriteDialog() const; + void setOverwriteDialog(const QByteArray& v); + + std::optional getModInfoDialog() const; + void setModInfoDialog(const QByteArray& v) const; + + QStringList getModInfoTabOrder() const; + void setModInfoTabOrder(const QString& names); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 37502f388422b2fdb60c2564d733ec015f579831 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 03:39:22 -0400 Subject: removed convertVariant(), turns out value() does it separator colors to settings --- src/mainwindow.cpp | 30 ++++++++++++++++------------ src/settings.cpp | 57 ++++++++++++++++++++---------------------------------- src/settings.h | 4 ++++ 3 files changed, 43 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 32f728d8..f98da391 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3786,32 +3786,37 @@ void MainWindow::createSeparator_clicked() { m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); } - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QColor previousColor = settings.value("previousSeparatorColor", QColor()).value(); - if (previousColor.isValid()) { - ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(previousColor); - } + if (auto c=m_OrganizerCore.settings().getPreviousSeparatorColor()) { + ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); + } } void MainWindow::setColor_clicked() { - QSettings &settings = m_OrganizerCore.settings().directInterface(); + auto& settings = m_OrganizerCore.settings(); ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + QColorDialog dialog(this); dialog.setOption(QColorDialog::ShowAlphaChannel); + QColor currentColor = modInfo->getColor(); - QColor previousColor = settings.value("previousSeparatorColor", QColor()).value(); - if (currentColor.isValid()) + if (currentColor.isValid()) { dialog.setCurrentColor(currentColor); - else - dialog.setCurrentColor(previousColor); + } + else if (auto c=settings.getPreviousSeparatorColor()) { + dialog.setCurrentColor(*c); + } + if (!dialog.exec()) return; + currentColor = dialog.currentColor(); if (!currentColor.isValid()) return; - settings.setValue("previousSeparatorColor", currentColor); + + settings.setPreviousSeparatorColor(currentColor); + QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { @@ -3846,7 +3851,8 @@ void MainWindow::resetColor_clicked() else { modInfo->setColor(color); } - Settings::instance().directInterface().remove("previousSeparatorColor"); + + m_OrganizerCore.settings().removePreviousSeparatorColor(); } void MainWindow::createModFromOverwrite() diff --git a/src/settings.cpp b/src/settings.cpp index 73595ac9..f980e0be 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -26,46 +26,11 @@ along with Mod Organizer. If not, see . using namespace MOBase; -template -T convertVariant(const QVariant& v); - -template <> -QByteArray convertVariant(const QVariant& v) -{ - return v.toByteArray(); -} - -template <> -QString convertVariant(const QVariant& v) -{ - return v.toString(); -} - -template <> -int convertVariant(const QVariant& v) -{ - return v.toInt(); -} - -template <> -bool convertVariant(const QVariant& v) -{ - return v.toBool(); -} - -template <> -QSize convertVariant(const QVariant& v) -{ - return v.toSize(); -} - - - template std::optional getOptional(const QSettings& s, const QString& name) { if (s.contains(name)) { - return convertVariant(s.value(name)); + return s.value(name).value(); } return {}; @@ -453,6 +418,26 @@ bool Settings::getFirstStart() const return getOptional(m_Settings, "first_start").value_or(true); } +std::optional Settings::getPreviousSeparatorColor() const +{ + const auto c = getOptional(m_Settings, "previousSeparatorColor"); + if (c && c->isValid()) { + return c; + } + + return {}; +} + +void Settings::setPreviousSeparatorColor(const QColor& c) const +{ + m_Settings.setValue("previousSeparatorColor", c); +} + +void Settings::removePreviousSeparatorColor() +{ + m_Settings.remove("previousSeparatorColor"); +} + QString Settings::getProfileDirectory(bool resolve) const { return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); diff --git a/src/settings.h b/src/settings.h index f4e36b2a..fff684b8 100644 --- a/src/settings.h +++ b/src/settings.h @@ -190,6 +190,10 @@ public: std::optional getVersion() const; bool getFirstStart() const; + std::optional getPreviousSeparatorColor() const; + void setPreviousSeparatorColor(const QColor& c) const; + void removePreviousSeparatorColor(); + GeometrySettings& geometry(); const GeometrySettings& geometry() const; -- cgit v1.3.1 From 61ad96cb54a20ce9f8e5380d67ba4bb26e19cc8e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 3 Aug 2019 03:47:35 -0400 Subject: moved geometry handling to ListDialog --- src/listdialog.cpp | 16 ++++++++++++++++ src/listdialog.h | 4 ++++ src/mainwindow.cpp | 8 -------- src/settings.cpp | 10 ++++++++++ src/settings.h | 3 +++ 5 files changed, 33 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/listdialog.cpp b/src/listdialog.cpp index b9857070..0fdcdb5f 100644 --- a/src/listdialog.cpp +++ b/src/listdialog.cpp @@ -17,6 +17,7 @@ along with Mod Organizer. If not, see . #include "listdialog.h" #include "ui_listdialog.h" +#include "settings.h" ListDialog::ListDialog(QWidget *parent) : QDialog(parent) @@ -32,6 +33,21 @@ ListDialog::~ListDialog() delete ui; } +int ListDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getListDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setListDialog(saveGeometry()); + + return r; +} + void ListDialog::setChoices(QStringList choices) { m_Choices = choices; diff --git a/src/listdialog.h b/src/listdialog.h index 7b5a5461..d0594bd7 100644 --- a/src/listdialog.h +++ b/src/listdialog.h @@ -15,6 +15,10 @@ public: explicit ListDialog(QWidget *parent = nullptr); ~ListDialog(); + // also saves and restores geometry + // + int exec() override; + void setChoices(QStringList choices); QString getChoice() const; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f98da391..f41bde17 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3898,17 +3898,10 @@ void MainWindow::moveOverwriteContentToExistingMod() } ListDialog dialog(this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - dialog.setWindowTitle("Select a mod..."); dialog.setChoices(mods); - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } if (dialog.exec() == QDialog::Accepted) { - QString result = dialog.getChoice(); if (!result.isEmpty()) { @@ -3930,7 +3923,6 @@ void MainWindow::moveOverwriteContentToExistingMod() doMoveOverwriteContentToMod(modAbsolutePath); } } - settings.setValue(key, dialog.saveGeometry()); } void MainWindow::doMoveOverwriteContentToMod(const QString &modAbsolutePath) diff --git a/src/settings.cpp b/src/settings.cpp index f980e0be..c36585b3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -927,6 +927,16 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) m_Settings.setValue("mod_info_tab_order", names); } +std::optional GeometrySettings::getListDialog() const +{ + return getOptional(m_Settings, "geometry/ListDialog"); +} + +void GeometrySettings::setListDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/ListDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index fff684b8..989ea1c6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -61,6 +61,9 @@ public: QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); + std::optional getListDialog() const; + void setListDialog(const QByteArray& v); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 3d86f150ca3a0992ddaca5055a270b7204c0682a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 06:30:37 -0400 Subject: moved geometry handling to ProblemsDialog and CategoriesDialog --- src/categoriesdialog.cpp | 16 ++++++++++++++++ src/categoriesdialog.h | 6 +++++- src/mainwindow.cpp | 26 ++++++++------------------ src/problemsdialog.cpp | 15 +++++++++++++++ src/problemsdialog.h | 4 ++++ src/settings.cpp | 20 ++++++++++++++++++++ src/settings.h | 6 ++++++ 7 files changed, 74 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 881179a4..91df5cae 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "ui_categoriesdialog.h" #include "categories.h" #include "utility.h" +#include "settings.h" #include #include #include @@ -109,6 +110,21 @@ CategoriesDialog::~CategoriesDialog() delete ui; } +int CategoriesDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getCategoriesDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setCategoriesDialog(saveGeometry()); + + return r; +} + void CategoriesDialog::cellChanged(int row, int) { diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 72d2154d..c743c157 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -33,12 +33,16 @@ class CategoriesDialog; class CategoriesDialog : public MOBase::TutorableDialog { Q_OBJECT - + public: explicit CategoriesDialog(QWidget *parent = 0); ~CategoriesDialog(); + // also saves and restores geometry + // + int exec() override; + /** * @brief store changes here to the global categories store (categories.h) * diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f41bde17..26398630 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6164,24 +6164,20 @@ void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) void MainWindow::on_actionNotifications_triggered() { updateProblemsButton(); - ProblemsDialog problems(m_PluginContainer.plugins(), this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(problems.objectName()); - if (settings.contains(key)) { - problems.restoreGeometry(settings.value(key).toByteArray()); - } + ProblemsDialog problems(m_PluginContainer.plugins(), this); problems.exec(); - settings.setValue(key, problems.saveGeometry()); + updateProblemsButton(); } void MainWindow::on_actionChange_Game_triggered() { - if (QMessageBox::question(this, tr("Are you sure?"), - tr("This will restart MO, continue?"), - QMessageBox::Yes | QMessageBox::Cancel) - == QMessageBox::Yes) { + const auto r = QMessageBox::question( + this, tr("Are you sure?"), tr("This will restart MO, continue?"), + QMessageBox::Yes | QMessageBox::Cancel); + + if (r == QMessageBox::Yes) { InstanceManager::instance().clearCurrentInstance(); qApp->exit(INT_MAX); } @@ -6206,16 +6202,10 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) void MainWindow::editCategories() { CategoriesDialog dialog(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) { dialog.commitChanges(); } - settings.setValue(key, dialog.saveGeometry()); - } void MainWindow::deselectFilters() diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index da09935b..99cc9833 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -29,6 +29,21 @@ ProblemsDialog::~ProblemsDialog() delete ui; } +int ProblemsDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getProblemsDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setProblemsDialog(saveGeometry()); + + return r; +} + void ProblemsDialog::runDiagnosis() { m_hasProblems = false; diff --git a/src/problemsdialog.h b/src/problemsdialog.h index c211e4f5..a30c8d48 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -20,6 +20,10 @@ public: explicit ProblemsDialog(std::vector pluginObjects, QWidget *parent = 0); ~ProblemsDialog(); + // also saves and restores geometry + // + int exec() override; + bool hasProblems() const; private: diff --git a/src/settings.cpp b/src/settings.cpp index c36585b3..da3b42a0 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -937,6 +937,26 @@ void GeometrySettings::setListDialog(const QByteArray& v) m_Settings.setValue("geometry/ListDialog", v); } +std::optional GeometrySettings::getProblemsDialog() const +{ + return getOptional(m_Settings, "geometry/ProblemsDialog"); +} + +void GeometrySettings::setProblemsDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/ProblemsDialog", v); +} + +std::optional GeometrySettings::getCategoriesDialog() const +{ + return getOptional(m_Settings, "geometry/CategoriesDialog"); +} + +void GeometrySettings::setCategoriesDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/CategoriesDialog", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 989ea1c6..217c8db6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -64,6 +64,12 @@ public: std::optional getListDialog() const; void setListDialog(const QByteArray& v); + std::optional getProblemsDialog() const; + void setProblemsDialog(const QByteArray& v); + + std::optional getCategoriesDialog() const; + void setCategoriesDialog(const QByteArray& v); + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From f387a670d119e501c5750b7efa1d3c11832ccf8c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 06:58:15 -0400 Subject: moved mod list stuff to setupModList(), no changes --- src/mainwindow.cpp | 120 ++++++++++++++++++++++++++++++++++------------------- src/mainwindow.h | 1 + 2 files changed, 79 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 26398630..d4673701 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -354,41 +354,7 @@ MainWindow::MainWindow(Settings &settings TaskProgressManager::instance().tryCreateTaskbar(); - // set up mod list - m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); - - ui->modList->setModel(m_ModListSortProxy); - - GenericIconDelegate *contentDelegate = new GenericIconDelegate(ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); - connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), contentDelegate, SLOT(columnResized(int,int,int))); - ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate(ui->modList, ModList::COL_FLAGS, 120); - connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), flagDelegate, SLOT(columnResized(int,int,int))); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); - ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); - ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - connect(ui->modList->header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(modListSectionResized(int, int, int))); - - bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state"); - - if (modListAdjusted) { - // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that - for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { - int sectionSize = ui->modList->header()->sectionSize(column); - ui->modList->header()->resizeSection(column, sectionSize + 1); - ui->modList->header()->resizeSection(column, sectionSize); - } - } else { - // hide these columns by default - ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); - ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); - ui->modList->header()->setSectionHidden(ModList::COL_GAME, true); - ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); - ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); - } - - ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden - ui->modList->installEventFilter(m_OrganizerCore.modList()); + setupModList(); // set up plugin list m_PluginListSortProxy = m_OrganizerCore.createPluginListProxyModel(); @@ -401,10 +367,14 @@ MainWindow::MainWindow(Settings &settings ui->bsaList->setLocalMoveOnly(true); initDownloadView(); - bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); + + bool pluginListAdjusted = registerWidgetState( + ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); + registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); - registerWidgetState(ui->downloadView->objectName(), - ui->downloadView->header()); + + registerWidgetState( + ui->downloadView->objectName(), ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); @@ -446,8 +416,6 @@ MainWindow::MainWindow(Settings &settings connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); - connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); - connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); connect(m_ModListSortProxy, SIGNAL(layoutChanged()), this, SLOT(updateModCount())); connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); @@ -494,7 +462,6 @@ MainWindow::MainWindow(Settings &settings connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); - connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ updateToolbarMenu(); }); connect(ui->menuView, &QMenu::aboutToShow, [&]{ updateViewMenu(); }); @@ -508,7 +475,6 @@ MainWindow::MainWindow(Settings &settings connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection))); - connect(ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(modlistSelectionsChanged(QItemSelection))); new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Enter), this, SLOT(openExplorer_activated())); new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return), this, SLOT(openExplorer_activated())); @@ -570,6 +536,76 @@ MainWindow::MainWindow(Settings &settings updateModCount(); } +void MainWindow::setupModList() +{ + m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); + ui->modList->setModel(m_ModListSortProxy); + ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + + + connect( + ui->modList, SIGNAL(dropModeUpdate(bool)), + m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); + + connect( + ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), + this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); + + connect( + ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), + this, SLOT(modlistSelectionsChanged(QItemSelection))); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int, int, int)), + this, SLOT(modListSectionResized(int, int, int))); + + + GenericIconDelegate *contentDelegate = new GenericIconDelegate( + ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int,int,int)), + contentDelegate, SLOT(columnResized(int,int,int))); + + + ModFlagIconDelegate *flagDelegate = new ModFlagIconDelegate( + ui->modList, ModList::COL_FLAGS, 120); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int,int,int)), + flagDelegate, SLOT(columnResized(int,int,int))); + + + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); + ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); + ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); + + + const bool modListAdjusted = registerWidgetState( + ui->modList->objectName(), ui->modList->header(), "mod_list_state"); + + if (modListAdjusted) { + // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that + for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { + int sectionSize = ui->modList->header()->sectionSize(column); + ui->modList->header()->resizeSection(column, sectionSize + 1); + ui->modList->header()->resizeSection(column, sectionSize); + } + } else { + // hide these columns by default + ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); + ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); + ui->modList->header()->setSectionHidden(ModList::COL_GAME, true); + ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); + ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); + } + + // prevent the name-column from being hidden + ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); + + ui->modList->installEventFilter(m_OrganizerCore.modList()); +} + void MainWindow::resetActionIcons() { // this is a bit of a hack diff --git a/src/mainwindow.h b/src/mainwindow.h index e8f60211..5ddb9bef 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -698,6 +698,7 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); + void setupModList(); void showMenuBar(bool b); void showStatusBar(bool b); }; -- cgit v1.3.1 From ea3840a39deacf269c1859389c3b1847bcbdb93b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 07:14:06 -0400 Subject: removed registerWidgetState(), was used just for header list headers, now saved and restored directly --- src/mainwindow.cpp | 73 +++++++++++++++++++----------------------------------- src/mainwindow.h | 9 +------ src/settings.cpp | 40 ++++++++++++++++++++++++++++++ src/settings.h | 12 +++++++++ 4 files changed, 79 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d4673701..95aa0b38 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -368,18 +368,24 @@ MainWindow::MainWindow(Settings &settings initDownloadView(); - bool pluginListAdjusted = registerWidgetState( - ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); + bool pluginListAdjusted = false; + if (auto v=m_OrganizerCore.settings().geometry().getPluginListHeader()) { + ui->espList->header()->restoreState(*v); + pluginListAdjusted = true; + } - registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); + if (auto v=m_OrganizerCore.settings().geometry().getDataTreeHeader()) { + ui->dataTree->header()->restoreState(*v); + } - registerWidgetState( - ui->downloadView->objectName(), ui->downloadView->header()); + if (auto v=m_OrganizerCore.settings().geometry().getDownloadViewHeader()) { + ui->downloadView->header()->restoreState(*v); + } ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); - resizeLists(modListAdjusted, pluginListAdjusted); + resizeLists(pluginListAdjusted); QMenu *linkMenu = new QMenu(this); m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); @@ -581,10 +587,9 @@ void MainWindow::setupModList() ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - const bool modListAdjusted = registerWidgetState( - ui->modList->objectName(), ui->modList->header(), "mod_list_state"); + if (auto v=m_OrganizerCore.settings().geometry().getModListHeader()) { + ui->modList->header()->restoreState(*v); - if (modListAdjusted) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { int sectionSize = ui->modList->header()->sectionSize(column); @@ -598,6 +603,13 @@ void MainWindow::setupModList() ui->modList->header()->setSectionHidden(ModList::COL_GAME, true); ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); + + // resize mod list to fit content + for (int i = 0; i < ui->modList->header()->count(); ++i) { + ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + + ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); } // prevent the name-column from being hidden @@ -720,16 +732,8 @@ void MainWindow::disconnectPlugins() } -void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) +void MainWindow::resizeLists(bool pluginListCustom) { - if (!modListCustom) { - // resize mod list to fit content - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); - } - // ensure the columns aren't so small you can't see them any more for (int i = 0; i < ui->modList->header()->count(); ++i) { if (ui->modList->header()->sectionSize(i) < 10) { @@ -2391,10 +2395,10 @@ void MainWindow::storeSettings(Settings& s) { settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); - for (const std::pair kv : m_PersistedGeometry) { - QString key = QString("geometry/") + kv.first; - settings.setValue(key, kv.second->saveState()); - } + s.geometry().setPluginListHeader(ui->espList->header()->saveState()); + s.geometry().setDataTreeHeader(ui->dataTree->header()->saveState()); + s.geometry().setDownloadViewHeader(ui->downloadView->header()->saveState()); + s.geometry().setModListHeader(ui->modList->header()->saveState()); DockFixer::save(this, s); } @@ -6892,31 +6896,6 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m } } -bool MainWindow::registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName) { - // register the view so it's geometry gets saved at exit - m_PersistedGeometry.push_back(std::make_pair(name, view)); - - // also, restore the geometry if it was saved before - QSettings &settings = m_OrganizerCore.settings().directInterface(); - - QString key = QString("geometry/%1").arg(name); - QByteArray data; - - if ((oldSettingName != nullptr) && settings.contains(oldSettingName)) { - data = settings.value(oldSettingName).toByteArray(); - settings.remove(oldSettingName); - } else if (settings.contains(key)) { - data = settings.value(key).toByteArray(); - } - - if (!data.isEmpty()) { - view->restoreState(data); - return true; - } else { - return false; - } -} - void MainWindow::dropEvent(QDropEvent *event) { Qt::DropAction action = event->proposedAction(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 5ddb9bef..46f04784 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -316,8 +316,6 @@ private: void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); - bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr); - void sendSelectedModsToPriority(int newPriority); void sendSelectedPluginsToPriority(int newPriority); @@ -405,8 +403,6 @@ private: bool m_showArchiveData{ true }; - std::vector> m_PersistedGeometry; - MOBase::DelayedFileWriter m_ArchiveListWriter; QAction* m_LinkToolbar; @@ -604,10 +600,7 @@ private slots: void expandModList(const QModelIndex &index); - /** - * @brief resize columns in mod list and plugin list to content - */ - void resizeLists(bool modListCustom, bool pluginListCustom); + void resizeLists(bool pluginListCustom); /** * @brief allow columns in mod list and plugin list to be resized diff --git a/src/settings.cpp b/src/settings.cpp index da3b42a0..d9440aa4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -957,6 +957,46 @@ void GeometrySettings::setCategoriesDialog(const QByteArray& v) m_Settings.setValue("geometry/CategoriesDialog", v); } +std::optional GeometrySettings::getPluginListHeader() const +{ + return getOptional(m_Settings, "geometry/espList"); +} + +void GeometrySettings::setPluginListHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/espList", v); +} + +std::optional GeometrySettings::getDataTreeHeader() const +{ + return getOptional(m_Settings, "geometry/dataTree"); +} + +void GeometrySettings::setDataTreeHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/dataTree", v); +} + +std::optional GeometrySettings::getDownloadViewHeader() const +{ + return getOptional(m_Settings, "geometry/downloadView"); +} + +void GeometrySettings::setDownloadViewHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/downloadView", v); +} + +std::optional GeometrySettings::getModListHeader() const +{ + return getOptional(m_Settings, "geometry/modList"); +} + +void GeometrySettings::setModListHeader(const QByteArray& v) const +{ + m_Settings.setValue("geometry/modList", v); +} + std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); diff --git a/src/settings.h b/src/settings.h index 217c8db6..110cfa76 100644 --- a/src/settings.h +++ b/src/settings.h @@ -70,6 +70,18 @@ public: std::optional getCategoriesDialog() const; void setCategoriesDialog(const QByteArray& v); + std::optional getPluginListHeader() const; + void setPluginListHeader(const QByteArray& v) const; + + std::optional getDataTreeHeader() const; + void setDataTreeHeader(const QByteArray& v) const; + + std::optional getDownloadViewHeader() const; + void setDownloadViewHeader(const QByteArray& v) const; + + std::optional getModListHeader() const; + void setModListHeader(const QByteArray& v) const; + std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); -- cgit v1.3.1 From 4c6ccb38459152089d2d964f842c349b19fdb56a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 07:20:30 -0400 Subject: geo already saved by ListDialog --- src/mainwindow.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 95aa0b38..532914a5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -138,7 +138,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -6986,12 +6985,9 @@ void MainWindow::sendSelectedModsToSeparator_clicked() } ListDialog dialog(this); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - dialog.setWindowTitle("Select a separator..."); dialog.setChoices(separators); - dialog.restoreGeometry(settings.value(key).toByteArray()); + if (dialog.exec() == QDialog::Accepted) { QString result = dialog.getChoice(); if (!result.isEmpty()) { @@ -7025,7 +7021,6 @@ void MainWindow::sendSelectedModsToSeparator_clicked() } } } - settings.setValue(key, dialog.saveGeometry()); } void MainWindow::on_showArchiveDataCheckBox_toggled(const bool checked) -- cgit v1.3.1 From 89415ca5c3903ced870d3bf5698dfa0e53122520 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 07:32:12 -0400 Subject: moved geometry handling to PreviewDialog fixed dialogs not having a parent --- src/mainwindow.h | 1 - src/organizercore.cpp | 23 +++-------------------- src/previewdialog.cpp | 16 ++++++++++++++++ src/previewdialog.h | 4 ++++ src/settings.cpp | 10 ++++++++++ src/settings.h | 3 +++ 6 files changed, 36 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/mainwindow.h b/src/mainwindow.h index 46f04784..7460019d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -83,7 +83,6 @@ class QProgressDialog; class QTranslator; class QTreeWidgetItem; class QUrl; -class QSettings; class QWidget; #ifndef Q_MOC_RUN diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a64d93b4..2d11dafd 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1297,7 +1297,8 @@ bool OrganizerCore::previewFileWithAlternatives( } // set up preview dialog - PreviewDialog preview(fileName); + PreviewDialog preview(fileName, parent); + auto addFunc = [&](int originId) { FilesOrigin &origin = directoryStructure()->getOriginByID(originId); QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; @@ -1352,16 +1353,7 @@ bool OrganizerCore::previewFileWithAlternatives( } if (preview.numVariants() > 0) { - QSettings &s = settings().directInterface(); - QString key = QString("geometry/%1").arg(preview.objectName()); - if (s.contains(key)) { - preview.restoreGeometry(s.value(key).toByteArray()); - } - preview.exec(); - - s.setValue(key, preview.saveGeometry()); - return true; } else { @@ -1381,7 +1373,7 @@ bool OrganizerCore::previewFile( return false; } - PreviewDialog preview(path); + PreviewDialog preview(path, parent); QWidget *wid = m_PluginContainer->previewGenerator().genPreview(path); if (wid == nullptr) { @@ -1390,17 +1382,8 @@ bool OrganizerCore::previewFile( } preview.addVariant(originName, wid); - - QSettings &s = settings().directInterface(); - QString key = QString("geometry/%1").arg(preview.objectName()); - if (s.contains(key)) { - preview.restoreGeometry(s.value(key).toByteArray()); - } - preview.exec(); - s.setValue(key, preview.saveGeometry()); - return true; } diff --git a/src/previewdialog.cpp b/src/previewdialog.cpp index de33cdd0..06dcd674 100644 --- a/src/previewdialog.cpp +++ b/src/previewdialog.cpp @@ -1,5 +1,6 @@ #include "previewdialog.h" #include "ui_previewdialog.h" +#include "settings.h" #include PreviewDialog::PreviewDialog(const QString &fileName, QWidget *parent) : @@ -17,6 +18,21 @@ PreviewDialog::~PreviewDialog() delete ui; } +int PreviewDialog::exec() +{ + auto& settings = Settings::instance(); + + if (auto v=settings.geometry().getPreviewDialog()) { + restoreGeometry(*v); + } + + const int r = QDialog::exec(); + + settings.geometry().setPreviewDialog(saveGeometry()); + + return r; +} + void PreviewDialog::addVariant(const QString &modName, QWidget *widget) { widget->setProperty("modName", modName); diff --git a/src/previewdialog.h b/src/previewdialog.h index 0011bc50..9525f127 100644 --- a/src/previewdialog.h +++ b/src/previewdialog.h @@ -15,6 +15,10 @@ public: explicit PreviewDialog(const QString &fileName, QWidget *parent = 0); ~PreviewDialog(); + // also saves and restores geometry + // + int exec() override; + void addVariant(const QString &modName, QWidget *widget); int numVariants() const; diff --git a/src/settings.cpp b/src/settings.cpp index d9440aa4..44aa56ba 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -957,6 +957,16 @@ void GeometrySettings::setCategoriesDialog(const QByteArray& v) m_Settings.setValue("geometry/CategoriesDialog", v); } +std::optional GeometrySettings::getPreviewDialog() const +{ + return getOptional(m_Settings, "geometry/PreviewDialog"); +} + +void GeometrySettings::setPreviewDialog(const QByteArray& v) +{ + m_Settings.setValue("geometry/PreviewDialog", v); +} + std::optional GeometrySettings::getPluginListHeader() const { return getOptional(m_Settings, "geometry/espList"); diff --git a/src/settings.h b/src/settings.h index 110cfa76..615cdcbe 100644 --- a/src/settings.h +++ b/src/settings.h @@ -70,6 +70,9 @@ public: std::optional getCategoriesDialog() const; void setCategoriesDialog(const QByteArray& v); + std::optional getPreviewDialog() const; + void setPreviewDialog(const QByteArray& v); + std::optional getPluginListHeader() const; void setPluginListHeader(const QByteArray& v) const; -- cgit v1.3.1 From ab14a8bac3368fc2c1005bcc33009b65a0c728f2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 4 Aug 2019 07:51:12 -0400 Subject: moved recent directories to Settings use global cache variable instead of an instance inside a function --- src/filedialogmemory.cpp | 53 ++++++++---------------------------------------- src/filedialogmemory.h | 8 ++------ src/settings.cpp | 39 +++++++++++++++++++++++++++++++++++ src/settings.h | 3 +++ 4 files changed, 53 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 48828563..96587ac7 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -21,46 +21,18 @@ along with Mod Organizer. If not, see . #include "settings.h" #include - -FileDialogMemory::FileDialogMemory() -{ -} - +static std::map g_Cache; void FileDialogMemory::save(Settings& s) { - auto& settings = s.directInterface(); - - settings.remove("recentDirectories"); - settings.beginWriteArray("recentDirectories"); - int index = 0; - for (std::map::const_iterator iter = instance().m_Cache.begin(); - iter != instance().m_Cache.end(); ++iter) { - settings.setArrayIndex(index++); - settings.setValue("name", iter->first); - settings.setValue("directory", iter->second); - } - settings.endArray(); + s.setRecentDirectories(g_Cache); } - void FileDialogMemory::restore(const Settings& s) { - auto& settings = const_cast(s.directInterface()); - - int size = settings.beginReadArray("recentDirectories"); - for (int i = 0; i < size; ++i) { - settings.setArrayIndex(i); - QVariant name = settings.value("name"); - QVariant dir = settings.value("directory"); - if (name.isValid() && dir.isValid()) { - instance().m_Cache.insert(std::make_pair(name.toString(), dir.toString())); - } - } - settings.endArray(); + g_Cache = s.getRecentDirectories(); } - QString FileDialogMemory::getOpenFileName( const QString &dirID, QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, @@ -69,8 +41,8 @@ QString FileDialogMemory::getOpenFileName( QString currentDir = dir; if (currentDir.isEmpty()) { - auto itor = instance().m_Cache.find(dirID); - if (itor != instance().m_Cache.end()) { + auto itor = g_Cache.find(dirID); + if (itor != g_Cache.end()) { currentDir = itor->second; } } @@ -79,7 +51,7 @@ QString FileDialogMemory::getOpenFileName( parent, caption, currentDir, filter, selectedFilter, options); if (!result.isNull()) { - instance().m_Cache[dirID] = QFileInfo(result).path(); + g_Cache[dirID] = QFileInfo(result).path(); } return result; @@ -93,8 +65,8 @@ QString FileDialogMemory::getExistingDirectory( QString currentDir = dir; if (currentDir.isEmpty()) { - auto itor = instance().m_Cache.find(dirID); - if (itor != instance().m_Cache.end()) { + auto itor = g_Cache.find(dirID); + if (itor != g_Cache.end()) { currentDir = itor->second; } } @@ -103,15 +75,8 @@ QString FileDialogMemory::getExistingDirectory( parent, caption, currentDir, options); if (!result.isNull()) { - instance().m_Cache[dirID] = QFileInfo(result).path(); + g_Cache[dirID] = result; } return result; } - - -FileDialogMemory &FileDialogMemory::instance() -{ - static FileDialogMemory instance; - return instance; -} diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index d214a8e6..8b8a3b76 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -30,6 +30,8 @@ class Settings; class FileDialogMemory { public: + FileDialogMemory() = delete; + static void save(Settings& settings); static void restore(const Settings& settings); @@ -42,12 +44,6 @@ public: const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), const QString &dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly); - -private: - std::map m_Cache; - - FileDialogMemory(); - static FileDialogMemory &instance(); }; #endif // FILEDIALOGMEMORY_H diff --git a/src/settings.cpp b/src/settings.cpp index 44aa56ba..cfc5c1d7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -764,6 +764,45 @@ void Settings::writePluginBlacklist() m_Settings.endArray(); } +std::map Settings::getRecentDirectories() const +{ + std::map map; + + const int size = m_Settings.beginReadArray("recentDirectories"); + + for (int i=0; i& map) +{ + m_Settings.remove("recentDirectories"); + m_Settings.beginWriteArray("recentDirectories"); + + int index = 0; + for (auto&& p : map) { + m_Settings.setArrayIndex(index); + m_Settings.setValue("name", p.first); + m_Settings.setValue("directory", p.second); + + ++index; + } + + m_Settings.endArray(); +} + GeometrySettings& Settings::geometry() { return m_Geometry; diff --git a/src/settings.h b/src/settings.h index 615cdcbe..5b02ca67 100644 --- a/src/settings.h +++ b/src/settings.h @@ -218,6 +218,9 @@ public: void setPreviousSeparatorColor(const QColor& c) const; void removePreviousSeparatorColor(); + std::map getRecentDirectories() const; + void setRecentDirectories(const std::map& map); + GeometrySettings& geometry(); const GeometrySettings& geometry() const; -- cgit v1.3.1 From 4d4f25d1774659e0dfae8e60e13c494cab0f0a44 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 5 Aug 2019 13:10:21 -0400 Subject: moved getting and setting executables to Settings --- src/executableslist.cpp | 54 +++++++++++++++++++++---------------------------- src/settings.cpp | 44 ++++++++++++++++++++++++++++++++++++++++ src/settings.h | 3 +++ 3 files changed, 70 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 2b3219df..f2df2d6d 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -75,34 +75,29 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; - auto& settings = const_cast(s.directInterface()); - - int numCustomExecutables = settings.beginReadArray("customExecutables"); - for (int i = 0; i < numCustomExecutables; ++i) { - settings.setArrayIndex(i); - + for (auto& map : s.getExecutables()) { Executable::Flags flags; - if (settings.value("toolbar", false).toBool()) + + if (map["toolbar"].toBool()) flags |= Executable::ShowInToolbar; - if (settings.value("ownicon", false).toBool()) + + if (map["ownicon"].toBool()) flags |= Executable::UseApplicationIcon; - if (settings.contains("custom")) { + if (map.contains("custom")) { // the "custom" setting only exists in older versions needsUpgrade = true; } 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()) + .title(map["title"].toString()) + .binaryInfo(map["binary"].toString()) + .arguments(map["arguments"].toString()) + .steamAppID(map["steamAppID"].toString()) + .workingDirectory(map["workingDirectory"].toString()) .flags(flags)); } - settings.endArray(); - addFromPlugin(game, IgnoreExisting); if (needsUpgrade) @@ -113,26 +108,23 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) void ExecutablesList::store(Settings& s) { - auto& settings = s.directInterface(); + std::vector> v; - settings.remove("customExecutables"); - settings.beginWriteArray("customExecutables"); + for (const auto& item : *this) { + std::map map; - int count = 0; + map["title"] = item.title(); + map["toolbar"] = item.isShownOnToolbar(); + map["ownicon"] = item.usesOwnIcon(); + map["binary"] = item.binaryInfo().absoluteFilePath(); + map["arguments"] = item.arguments(); + map["workingDirectory"] = item.workingDirectory(); + map["steamAppID"] = item.steamAppID(); - 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()); + v.push_back(std::move(map)); } - settings.endArray(); + s.setExecutables(v); } std::vector ExecutablesList::getPluginExecutables( diff --git a/src/settings.cpp b/src/settings.cpp index cfc5c1d7..a8dcfa39 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "serverinfo.h" +#include "executableslist.h" #include "appconfig.h" #include #include @@ -803,6 +804,49 @@ void Settings::setRecentDirectories(const std::map& map) m_Settings.endArray(); } +std::vector> Settings::getExecutables() const +{ + const int count = m_Settings.beginReadArray("customExecutables"); + std::vector> v; + + for (int i=0; i map; + + const auto keys = m_Settings.childKeys(); + for (auto&& key : keys) { + map[key] = m_Settings.value(key); + } + + v.push_back(map); + } + + m_Settings.endArray(); + + return v; +} + +void Settings::setExecutables(const std::vector>& v) +{ + m_Settings.remove("customExecutables"); + m_Settings.beginWriteArray("customExecutables"); + + int i = 0; + + for (const auto& map : v) { + m_Settings.setArrayIndex(i); + + for (auto&& p : map) { + m_Settings.setValue(p.first, p.second); + } + + ++i; + } + + m_Settings.endArray(); +} + GeometrySettings& Settings::geometry() { return m_Geometry; diff --git a/src/settings.h b/src/settings.h index 5b02ca67..2c4c7ca6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -221,6 +221,9 @@ public: std::map getRecentDirectories() const; void setRecentDirectories(const std::map& map); + std::vector> getExecutables() const; + void setExecutables(const std::vector>& v); + GeometrySettings& geometry(); const GeometrySettings& geometry() const; -- cgit v1.3.1 From 3ca0a9fe1e7b751ecda37de53f9a01c88fcb576c Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 14 Aug 2019 15:38:31 +0000 Subject: Updated string of the hideAPICounterBox The api counter box is in the bottom right corner since the last update. This change reflects that. --- src/settingsdialog.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index e011542e..660cae92 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -718,10 +718,10 @@ p, li { white-space: pre-wrap; } - <html><head/><body><p>By default, a counter is displayed under the mod list. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> + <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> - <html><head/><body><p>By default, a counter is displayed under the mod list. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> + <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> Hide API Request Counter -- cgit v1.3.1 From 799ddb1b2477434252d06975fd4c68106dc3826f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 06:03:05 -0400 Subject: added GeometrySaver removed widget-specific functions in GeometrySettings, now using generic functions in Settings removed some unused member variables in MainWindow --- src/categoriesdialog.cpp | 13 +-- src/editexecutablesdialog.cpp | 13 +-- src/listdialog.cpp | 13 +-- src/mainwindow.cpp | 49 ++++------ src/mainwindow.h | 4 - src/modinfodialog.cpp | 6 +- src/overwriteinfodialog.cpp | 11 +-- src/previewdialog.cpp | 13 +-- src/problemsdialog.cpp | 13 +-- src/profilesdialog.cpp | 13 +-- src/settings.cpp | 218 +++++++++++++++++++----------------------- src/settings.h | 64 +++++-------- 12 files changed, 158 insertions(+), 272 deletions(-) (limited to 'src') diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 91df5cae..b5194bf0 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -112,17 +112,8 @@ CategoriesDialog::~CategoriesDialog() int CategoriesDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getCategoriesDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setCategoriesDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 9c5ae44a..7823fadc 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -67,17 +67,8 @@ EditExecutablesDialog::~EditExecutablesDialog() = default; int EditExecutablesDialog::exec() { - auto& settings = m_organizerCore.settings(); - - if (auto v=settings.geometry().getExecutablesDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setExecutablesDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void EditExecutablesDialog::loadCustomOverwrites() diff --git a/src/listdialog.cpp b/src/listdialog.cpp index 0fdcdb5f..2ad88408 100644 --- a/src/listdialog.cpp +++ b/src/listdialog.cpp @@ -35,17 +35,8 @@ ListDialog::~ListDialog() int ListDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getListDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setListDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void ListDialog::setChoices(QStringList choices) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 532914a5..85be8563 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -290,7 +290,6 @@ MainWindow::MainWindow(Settings &settings , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) - , m_ModListGroupingProxy(nullptr) , m_ModListSortProxy(nullptr) , m_OldExecutableIndex(-1) , m_CategoryFactory(CategoryFactory::instance()) @@ -309,8 +308,8 @@ MainWindow::MainWindow(Settings &settings { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); - QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory()); - QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setCachePath(settings.getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); ui->setupUi(this); m_statusBar.reset(new StatusBar(statusBar(), ui)); @@ -340,7 +339,7 @@ MainWindow::MainWindow(Settings &settings m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } - languageChange(m_OrganizerCore.settings().language()); + languageChange(settings.language()); m_CategoryFactory.loadCategories(); @@ -367,19 +366,9 @@ MainWindow::MainWindow(Settings &settings initDownloadView(); - bool pluginListAdjusted = false; - if (auto v=m_OrganizerCore.settings().geometry().getPluginListHeader()) { - ui->espList->header()->restoreState(*v); - pluginListAdjusted = true; - } - - if (auto v=m_OrganizerCore.settings().geometry().getDataTreeHeader()) { - ui->dataTree->header()->restoreState(*v); - } - - if (auto v=m_OrganizerCore.settings().geometry().getDownloadViewHeader()) { - ui->downloadView->header()->restoreState(*v); - } + const bool pluginListAdjusted = settings.restoreState(ui->espList->header()); + settings.restoreState(ui->dataTree->header()); + settings.restoreState(ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); @@ -586,9 +575,7 @@ void MainWindow::setupModList() ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - if (auto v=m_OrganizerCore.settings().geometry().getModListHeader()) { - ui->modList->header()->restoreState(*v); - + if (m_OrganizerCore.settings().restoreState(ui->modList->header())) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { int sectionSize = ui->modList->header()->sectionSize(column); @@ -2272,13 +2259,8 @@ void MainWindow::activateProxy(bool activate) void MainWindow::readSettings(const Settings& settings) { - if (auto v=settings.geometry().getMainWindow()) { - restoreGeometry(*v); - } - - if (auto v=settings.geometry().getMainWindowState()) { - restoreState(*v); - } + settings.restoreGeometry(this); + settings.restoreState(this); if (auto v=settings.geometry().getToolbarSize()) { setToolbarSize(*v); @@ -2381,8 +2363,9 @@ void MainWindow::storeSettings(Settings& s) { settings.remove("geometry"); settings.remove("reset_geometry"); } else { - settings.setValue("window_geometry", saveGeometry()); - settings.setValue("window_state", saveState()); + s.saveState(this); + s.saveGeometry(this); + settings.setValue("toolbar_size", ui->toolBar->iconSize()); settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); settings.setValue("menubar_visible", m_menuBarVisible); @@ -2394,10 +2377,10 @@ void MainWindow::storeSettings(Settings& s) { settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); - s.geometry().setPluginListHeader(ui->espList->header()->saveState()); - s.geometry().setDataTreeHeader(ui->dataTree->header()->saveState()); - s.geometry().setDownloadViewHeader(ui->downloadView->header()->saveState()); - s.geometry().setModListHeader(ui->modList->header()->saveState()); + s.saveState(ui->espList->header()); + s.saveState(ui->dataTree->header()); + s.saveState(ui->downloadView->header()); + s.saveState(ui->modList->header()); DockFixer::save(this, s); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 7460019d..946a341b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -347,11 +347,9 @@ private: int m_OldProfileIndex; std::vector m_ModNameList; // the mod-list to go with the directory structure - bool m_Refreshing; QStringList m_DefaultArchives; - QAbstractItemModel *m_ModListGroupingProxy; ModListSortProxy *m_ModListSortProxy; PluginListSortProxy *m_PluginListSortProxy; @@ -367,8 +365,6 @@ private: CategoryFactory &m_CategoryFactory; - bool m_LoginAttempted; - QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; QTimer m_UpdateProblemsTimer; diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 5e614358..f3840230 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -210,10 +210,8 @@ void ModInfoDialog::createTabs() int ModInfoDialog::exec() { + GeometrySaver gs(Settings::instance(), this); restoreState(); - if (auto v=m_core->settings().geometry().getModInfoDialog()) { - restoreGeometry(*v); - } // whether to select the first tab; if the main window requested a specific // tab, it is selected when encountered in update() @@ -226,9 +224,7 @@ int ModInfoDialog::exec() } const int r = TutorableDialog::exec(); - saveState(); - m_core->settings().geometry().setModInfoDialog(saveGeometry()); return r; } diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index f3ae0ff5..47416311 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -106,20 +106,13 @@ OverwriteInfoDialog::~OverwriteInfoDialog() void OverwriteInfoDialog::showEvent(QShowEvent* e) { - const auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getOverwriteDialog()) { - restoreGeometry(*v); - } - + Settings::instance().restoreGeometry(this); QDialog::showEvent(e); } void OverwriteInfoDialog::done(int r) { - auto& settings = Settings::instance(); - settings.geometry().setOverwriteDialog(saveGeometry()); - + Settings::instance().saveGeometry(this); QDialog::done(r); } diff --git a/src/previewdialog.cpp b/src/previewdialog.cpp index 06dcd674..91a5f13e 100644 --- a/src/previewdialog.cpp +++ b/src/previewdialog.cpp @@ -20,17 +20,8 @@ PreviewDialog::~PreviewDialog() int PreviewDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getPreviewDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setPreviewDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void PreviewDialog::addVariant(const QString &modName, QWidget *widget) diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 99cc9833..63d58295 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -31,17 +31,8 @@ ProblemsDialog::~ProblemsDialog() int ProblemsDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getProblemsDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setProblemsDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void ProblemsDialog::runDiagnosis() diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 25fff2b2..2f1bd059 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -86,17 +86,8 @@ ProfilesDialog::~ProfilesDialog() int ProfilesDialog::exec() { - auto& settings = Settings::instance(); - - if (auto v=settings.geometry().getProfilesDialog()) { - restoreGeometry(*v); - } - - const int r = QDialog::exec(); - - settings.geometry().setProfilesDialog(saveGeometry()); - - return r; + GeometrySaver gs(Settings::instance(), this); + return QDialog::exec(); } void ProfilesDialog::showEvent(QShowEvent *event) diff --git a/src/settings.cpp b/src/settings.cpp index a8dcfa39..91e667d5 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -884,94 +884,146 @@ void Settings::dump() const m_Settings.endGroup(); } +QString widgetNameWithTopLevel(const QWidget* widget) +{ + QStringList components; -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s) + auto* tl = widget->window(); + + if (tl == widget) { + // this is a top level widget, such as a dialog + components.push_back(widget->objectName()); + } else { + // this is a widget + const auto toplevelName = tl->objectName(); + if (!toplevelName.isEmpty()) { + components.push_back(toplevelName); + } + + const auto widgetName = widget->objectName(); + if (!widgetName.isEmpty()) { + components.push_back(widgetName); + } + } + + if (components.isEmpty()) { + // can't do much + return "unknown_widget"; + } + + return components.join("_"); +} + +QString widgetName(const QMainWindow* w) { + return w->objectName(); } -std::optional GeometrySettings::getMainWindow() const +QString widgetName(const QHeaderView* w) { - return getOptional(m_Settings, "window_geometry"); + return widgetNameWithTopLevel(w->parentWidget()); } -std::optional GeometrySettings::getMainWindowState() const +QString widgetName(const QWidget* w) { - return getOptional(m_Settings, "window_state"); + return widgetNameWithTopLevel(w); } -std::optional GeometrySettings::getToolbarSize() const +template +QString geoSettingName(const Widget* widget) { - return getOptional(m_Settings, "toolbar_size"); + return "geometry/" + widgetName(widget) + "_geometry"; } -std::optional GeometrySettings::getToolbarButtonStyle() const +template +QString stateSettingName(const Widget* widget) { - if (auto v=getOptional(m_Settings, "toolbar_button_style")) { - return static_cast(*v); - } + return "geometry/" + widgetName(widget) + "_state"; +} - return {}; +void Settings::saveGeometry(const QWidget* w) +{ + m_Settings.setValue(geoSettingName(w), w->saveGeometry()); } -std::optional GeometrySettings::getMenubarVisible() const +bool Settings::restoreGeometry(QWidget* w) const { - return getOptional(m_Settings, "menubar_visible"); + if (auto v=getOptional(m_Settings, geoSettingName(w))) { + w->restoreGeometry(*v); + return true; + } + + return false; } -std::optional GeometrySettings::getStatusbarVisible() const +void Settings::saveState(const QMainWindow* w) { - return getOptional(m_Settings, "statusbar_visible"); + m_Settings.setValue(stateSettingName(w), w->saveGeometry()); } -std::optional GeometrySettings::getMainSplitterState() const +bool Settings::restoreState(QMainWindow* w) const { - return getOptional(m_Settings, "window_split"); + if (auto v=getOptional(m_Settings, stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; } -std::optional GeometrySettings::getFiltersVisible() const +void Settings::saveState(const QHeaderView* w) { - return getOptional(m_Settings, "filters_visible"); + m_Settings.setValue(stateSettingName(w), w->saveState()); } -std::optional GeometrySettings::getExecutablesDialog() const +bool Settings::restoreState(QHeaderView* w) const { - return getOptional(m_Settings, "geometry/EditExecutablesDialog"); + if (auto v=getOptional(m_Settings, stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; } -void GeometrySettings::setExecutablesDialog(const QByteArray& v) + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s) { - m_Settings.setValue("geometry/EditExecutablesDialog", v); } -std::optional GeometrySettings::getProfilesDialog() const +std::optional GeometrySettings::getToolbarSize() const { - return getOptional(m_Settings, "geometry/ProfilesDialog"); + return getOptional(m_Settings, "toolbar_size"); } -void GeometrySettings::setProfilesDialog(const QByteArray& v) +std::optional GeometrySettings::getToolbarButtonStyle() const { - m_Settings.setValue("geometry/ProfilesDialog", v); + if (auto v=getOptional(m_Settings, "toolbar_button_style")) { + return static_cast(*v); + } + + return {}; } -std::optional GeometrySettings::getOverwriteDialog() const +std::optional GeometrySettings::getMenubarVisible() const { - return getOptional(m_Settings, "geometry/__overwriteDialog"); + return getOptional(m_Settings, "menubar_visible"); } -void GeometrySettings::setOverwriteDialog(const QByteArray& v) +std::optional GeometrySettings::getStatusbarVisible() const { - m_Settings.setValue("geometry/__overwriteDialog", v); + return getOptional(m_Settings, "statusbar_visible"); } -std::optional GeometrySettings::getModInfoDialog() const +std::optional GeometrySettings::getMainSplitterState() const { - return getOptional(m_Settings, "geometry/ModInfoDialog"); + return getOptional(m_Settings, "window_split"); } -void GeometrySettings::setModInfoDialog(const QByteArray& v) const +std::optional GeometrySettings::getFiltersVisible() const { - m_Settings.setValue("geometry/ModInfoDialog", v); + return getOptional(m_Settings, "filters_visible"); } QStringList GeometrySettings::getModInfoTabOrder() const @@ -1010,86 +1062,6 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) m_Settings.setValue("mod_info_tab_order", names); } -std::optional GeometrySettings::getListDialog() const -{ - return getOptional(m_Settings, "geometry/ListDialog"); -} - -void GeometrySettings::setListDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/ListDialog", v); -} - -std::optional GeometrySettings::getProblemsDialog() const -{ - return getOptional(m_Settings, "geometry/ProblemsDialog"); -} - -void GeometrySettings::setProblemsDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/ProblemsDialog", v); -} - -std::optional GeometrySettings::getCategoriesDialog() const -{ - return getOptional(m_Settings, "geometry/CategoriesDialog"); -} - -void GeometrySettings::setCategoriesDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/CategoriesDialog", v); -} - -std::optional GeometrySettings::getPreviewDialog() const -{ - return getOptional(m_Settings, "geometry/PreviewDialog"); -} - -void GeometrySettings::setPreviewDialog(const QByteArray& v) -{ - m_Settings.setValue("geometry/PreviewDialog", v); -} - -std::optional GeometrySettings::getPluginListHeader() const -{ - return getOptional(m_Settings, "geometry/espList"); -} - -void GeometrySettings::setPluginListHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/espList", v); -} - -std::optional GeometrySettings::getDataTreeHeader() const -{ - return getOptional(m_Settings, "geometry/dataTree"); -} - -void GeometrySettings::setDataTreeHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/dataTree", v); -} - -std::optional GeometrySettings::getDownloadViewHeader() const -{ - return getOptional(m_Settings, "geometry/downloadView"); -} - -void GeometrySettings::setDownloadViewHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/downloadView", v); -} - -std::optional GeometrySettings::getModListHeader() const -{ - return getOptional(m_Settings, "geometry/modList"); -} - -void GeometrySettings::setModListHeader(const QByteArray& v) const -{ - m_Settings.setValue("geometry/modList", v); -} - std::optional GeometrySettings::getMainWindowMonitor() const { return getOptional(m_Settings, "window_monitor"); @@ -1109,3 +1081,15 @@ std::optional GeometrySettings::isCategoryListVisible() const { return getOptional(m_Settings, "categorylist_visible"); } + + +GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) + : m_settings(s), m_dialog(dialog) +{ + m_settings.restoreGeometry(m_dialog); +} + +GeometrySaver::~GeometrySaver() +{ + m_settings.saveGeometry(m_dialog); +} diff --git a/src/settings.h b/src/settings.h index 2c4c7ca6..1575b3cd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -30,6 +30,18 @@ namespace MOBase { class PluginContainer; struct ServerInfo; +class Settings; + +class GeometrySaver +{ +public: + GeometrySaver(Settings& s, QDialog* dialog); + ~GeometrySaver(); + +private: + Settings& m_settings; + QDialog* m_dialog; +}; class GeometrySettings @@ -37,54 +49,17 @@ class GeometrySettings public: GeometrySettings(QSettings& s); - std::optional getMainWindow() const; - std::optional getMainWindowState() const; std::optional getToolbarSize() const; std::optional getToolbarButtonStyle() const; + std::optional getMenubarVisible() const; std::optional getStatusbarVisible() const; std::optional getMainSplitterState() const; std::optional getFiltersVisible() const; - std::optional getExecutablesDialog() const; - void setExecutablesDialog(const QByteArray& v); - - std::optional getProfilesDialog() const; - void setProfilesDialog(const QByteArray& v); - - std::optional getOverwriteDialog() const; - void setOverwriteDialog(const QByteArray& v); - - std::optional getModInfoDialog() const; - void setModInfoDialog(const QByteArray& v) const; - QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); - std::optional getListDialog() const; - void setListDialog(const QByteArray& v); - - std::optional getProblemsDialog() const; - void setProblemsDialog(const QByteArray& v); - - std::optional getCategoriesDialog() const; - void setCategoriesDialog(const QByteArray& v); - - std::optional getPreviewDialog() const; - void setPreviewDialog(const QByteArray& v); - - std::optional getPluginListHeader() const; - void setPluginListHeader(const QByteArray& v) const; - - std::optional getDataTreeHeader() const; - void setDataTreeHeader(const QByteArray& v) const; - - std::optional getDownloadViewHeader() const; - void setDownloadViewHeader(const QByteArray& v) const; - - std::optional getModListHeader() const; - void setModListHeader(const QByteArray& v) const; - std::optional getMainWindowMonitor() const; void setDockSize(const QString& name, int size); @@ -228,6 +203,19 @@ public: const GeometrySettings& geometry() const; + void saveGeometry(const QWidget* w); + bool restoreGeometry(QWidget* w) const; + + void saveState(const QMainWindow* window); + bool restoreState(QMainWindow* window) const; + + void saveState(const QHeaderView* header); + bool restoreState(QHeaderView* header) const; + + void saveState(const QToolBar* toolbar); + bool restoreState(QToolBar* toolbar) const; + + /** * retrieve the directory where profiles stored (with native separators) **/ -- cgit v1.3.1 From 3f487a5a6c9c23824298fdde3d76dc82edf3ca46 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 07:03:52 -0400 Subject: merged toolbars into restoreToolbars() and saveToolbars() added centerOnMainWindowMonitor(), now also used by validation dialog added overloads for splitter, used by main splitter fixed saveState() for QMainWindow calling the wrong function --- src/main.cpp | 11 +------ src/mainwindow.cpp | 28 +++++----------- src/nxmaccessmanager.cpp | 13 ++++++-- src/nxmaccessmanager.h | 2 ++ src/pch.h | 1 + src/settings.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++------ src/settings.h | 19 ++++++++--- 7 files changed, 114 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 506c6270..8eee41e4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -697,16 +697,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); - const auto monitor = settings.geometry().getMainWindowMonitor(); - if (monitor && QGuiApplication::screens().size() > *monitor) { - QGuiApplication::screens().at(*monitor)->geometry().center(); - const QPoint center = QGuiApplication::screens().at(*monitor)->geometry().center(); - splash.move(center - splash.rect().center()); - } else { - const QPoint center = QGuiApplication::primaryScreen()->geometry().center(); - splash.move(center - splash.rect().center()); - } - + settings.geometry().centerOnMainWindowMonitor(&splash); splash.show(); splash.activateWindow(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 85be8563..6e6e3d22 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2261,14 +2261,8 @@ void MainWindow::readSettings(const Settings& settings) { settings.restoreGeometry(this); settings.restoreState(this); - - if (auto v=settings.geometry().getToolbarSize()) { - setToolbarSize(*v); - } - - if (auto v=settings.geometry().getToolbarButtonStyle()) { - setToolbarButtonStyle(*v); - } + settings.geometry().restoreToolbars(this); + settings.restoreState(ui->splitter); if (auto v=settings.geometry().getMenubarVisible()) { showMenuBar(*v); @@ -2278,10 +2272,6 @@ void MainWindow::readSettings(const Settings& settings) showStatusBar(*v); } - if (auto v=settings.geometry().getMainSplitterState()) { - ui->splitter->restoreState(*v); - } - { auto v = settings.geometry().getFiltersVisible().value_or(false); setCategoryListVisible(v); @@ -2366,14 +2356,12 @@ void MainWindow::storeSettings(Settings& s) { s.saveState(this); s.saveGeometry(this); - settings.setValue("toolbar_size", ui->toolBar->iconSize()); - settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); - settings.setValue("menubar_visible", m_menuBarVisible); - settings.setValue("statusbar_visible", m_statusBarVisible); - settings.setValue("window_split", ui->splitter->saveState()); - QScreen *screen = this->window()->windowHandle()->screen(); - int screenId = QGuiApplication::screens().indexOf(screen); - settings.setValue("window_monitor", screenId); + s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveToolbars(this); + s.geometry().setStatusbarVisible(m_statusBarVisible); + s.saveState(ui->splitter); + s.geometry().saveMainWindowMonitor(this); + settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index fd1dc0c1..16190ca4 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -48,8 +48,9 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) - : m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr) +ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : + m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr), + m_first(true) { m_bar = new QProgressBar; m_bar->setTextVisible(false); @@ -103,6 +104,14 @@ void ValidationProgressDialog::stop() hide(); } +void ValidationProgressDialog::showEvent(QShowEvent* e) +{ + if (m_first) { + Settings::instance().geometry().centerOnMainWindowMonitor(this); + m_first = false; + } +} + void ValidationProgressDialog::closeEvent(QCloseEvent* e) { hide(); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index eed7c1c9..0c85153b 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -48,6 +48,7 @@ public: using QDialog::show; protected: + void showEvent(QShowEvent* e) override; void closeEvent(QCloseEvent* e) override; private: @@ -56,6 +57,7 @@ private: QDialogButtonBox* m_buttons; QTimer* m_timer; QElapsedTimer m_elapsed; + bool m_first; void onButton(QAbstractButton* b); void onTimer(); diff --git a/src/pch.h b/src/pch.h index 504ef8f1..dd65efbe 100644 --- a/src/pch.h +++ b/src/pch.h @@ -189,6 +189,7 @@ #include #include #include +#include #include #include #include diff --git a/src/settings.cpp b/src/settings.cpp index 91e667d5..a3d12070 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -958,7 +958,7 @@ bool Settings::restoreGeometry(QWidget* w) const void Settings::saveState(const QMainWindow* w) { - m_Settings.setValue(stateSettingName(w), w->saveGeometry()); + m_Settings.setValue(stateSettingName(w), w->saveState()); } bool Settings::restoreState(QMainWindow* w) const @@ -986,24 +986,61 @@ bool Settings::restoreState(QHeaderView* w) const return false; } +void Settings::saveState(const QSplitter* w) +{ + m_Settings.setValue(stateSettingName(w), w->saveState()); +} + +bool Settings::restoreState(QSplitter* w) const +{ + if (auto v=getOptional(m_Settings, stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; +} + GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s) { } -std::optional GeometrySettings::getToolbarSize() const +bool GeometrySettings::restoreToolbars(QMainWindow* w) const { - return getOptional(m_Settings, "toolbar_size"); + const auto size = getOptional(m_Settings, "toolbar_size"); + const auto style = getOptional(m_Settings, "toolbar_button_style"); + + if (!size && !style) { + return false; + } + + for (auto* tb : w->findChildren()) { + if (size) { + tb->setIconSize(*size); + } + + if (style) { + tb->setToolButtonStyle(static_cast(*style)); + } + } + + return true; } -std::optional GeometrySettings::getToolbarButtonStyle() const +void GeometrySettings::saveToolbars(const QMainWindow* w) { - if (auto v=getOptional(m_Settings, "toolbar_button_style")) { - return static_cast(*v); + // all toolbars are identical, just save the first one + const auto tbs = w->findChildren(); + if (tbs.isEmpty()) { + return; } - return {}; + const auto* tb = tbs[0]; + + m_Settings.setValue("toolbar_size", tb->iconSize()); + m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); } std::optional GeometrySettings::getMenubarVisible() const @@ -1011,14 +1048,19 @@ std::optional GeometrySettings::getMenubarVisible() const return getOptional(m_Settings, "menubar_visible"); } +void GeometrySettings::setMenubarVisible(bool b) +{ + m_Settings.setValue("menubar_visible", b); +} + std::optional GeometrySettings::getStatusbarVisible() const { return getOptional(m_Settings, "statusbar_visible"); } -std::optional GeometrySettings::getMainSplitterState() const +void GeometrySettings::setStatusbarVisible(bool b) { - return getOptional(m_Settings, "window_split"); + m_Settings.setValue("statusbar_visible", b); } std::optional GeometrySettings::getFiltersVisible() const @@ -1064,7 +1106,31 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) std::optional GeometrySettings::getMainWindowMonitor() const { - return getOptional(m_Settings, "window_monitor"); + return getOptional(m_Settings, "geometry/window_monitor"); +} + +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) +{ + const auto monitor = getMainWindowMonitor(); + QPoint center; + + if (monitor && QGuiApplication::screens().size() > *monitor) { + center = QGuiApplication::screens().at(*monitor)->geometry().center(); + } else { + center = QGuiApplication::primaryScreen()->geometry().center(); + } + + w->move(center - w->rect().center()); +} + +void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) +{ + if (auto* handle=w->windowHandle()) { + if (auto* screen = handle->screen()) { + const int screenId = QGuiApplication::screens().indexOf(screen); + m_Settings.setValue("geometry/window_monitor", screenId); + } + } } void GeometrySettings::setDockSize(const QString& name, int size) diff --git a/src/settings.h b/src/settings.h index 1575b3cd..bbf008f0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -28,6 +28,8 @@ namespace MOBase { class IPluginGame; } +class QSplitter; + class PluginContainer; struct ServerInfo; class Settings; @@ -49,18 +51,24 @@ class GeometrySettings public: GeometrySettings(QSettings& s); - std::optional getToolbarSize() const; - std::optional getToolbarButtonStyle() const; - std::optional getMenubarVisible() const; + void setMenubarVisible(bool b); + + bool restoreToolbars(QMainWindow* w) const; + void saveToolbars(const QMainWindow* w); + std::optional getStatusbarVisible() const; - std::optional getMainSplitterState() const; + void setStatusbarVisible(bool b); + std::optional getFiltersVisible() const; QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); std::optional getMainWindowMonitor() const; + void centerOnMainWindowMonitor(QWidget* w); + void saveMainWindowMonitor(const QMainWindow* w); + void setDockSize(const QString& name, int size); std::optional getDockSize(const QString& name) const; @@ -215,6 +223,9 @@ public: void saveState(const QToolBar* toolbar); bool restoreState(QToolBar* toolbar) const; + void saveState(const QSplitter* splitter); + bool restoreState(QSplitter* splitter) const; + /** * retrieve the directory where profiles stored (with native separators) -- cgit v1.3.1 From a5cb39aaf44b1f84003fb2ec2d36f07bf28916e4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 08:02:14 -0400 Subject: moved all geometry save, restore and reset to GeometrySettings changed reset button in settings to restart immediately --- src/browserdialog.cpp | 3 +- src/main.cpp | 12 ++++-- src/mainwindow.cpp | 75 +++++++++++-------------------------- src/mainwindow.h | 1 - src/overwriteinfodialog.cpp | 4 +- src/settings.cpp | 79 ++++++++++++++++++++++++++------------- src/settings.h | 39 +++++++++---------- src/settingsdialog.cpp | 11 +----- src/settingsdialog.h | 2 - src/settingsdialog.ui | 3 -- src/settingsdialogworkarounds.cpp | 16 ++++++-- 11 files changed, 121 insertions(+), 124 deletions(-) (limited to 'src') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 73a6a2d0..70da0b9c 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -72,7 +72,7 @@ BrowserDialog::~BrowserDialog() void BrowserDialog::closeEvent(QCloseEvent *event) { -// m_AccessManager->showCookies(); + Settings::instance().geometry().saveGeometry(this); QDialog::closeEvent(event); } @@ -126,6 +126,7 @@ void BrowserDialog::urlChanged(const QUrl &url) void BrowserDialog::openUrl(const QUrl &url) { if (isHidden()) { + Settings::instance().geometry().restoreGeometry(this); show(); } openInNewTab(url); diff --git a/src/main.cpp b/src/main.cpp index 8eee41e4..6d4108fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -718,6 +718,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, } int res = 1; + { // scope to control lifetime of mainwindow // set up main window and its data structures MainWindow mainWindow(settings, organizer, pluginContainer); @@ -743,17 +744,20 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); - const auto ret = application.exec(); + res = application.exec(); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(nullptr); - - return ret; } + + settings.geometry().resetIfNeeded(); + return res; + } catch (const std::exception &e) { reportError(e.what()); - return 1; } + + return 1; } int doCoreDump(env::CoreDumpTypes type) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e6e3d22..28e1de2e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -366,9 +366,11 @@ MainWindow::MainWindow(Settings &settings initDownloadView(); - const bool pluginListAdjusted = settings.restoreState(ui->espList->header()); - settings.restoreState(ui->dataTree->header()); - settings.restoreState(ui->downloadView->header()); + const bool pluginListAdjusted = + settings.geometry().restoreState(ui->espList->header()); + + settings.geometry().restoreState(ui->dataTree->header()); + settings.geometry().restoreState(ui->downloadView->header()); ui->splitter->setStretchFactor(0, 3); ui->splitter->setStretchFactor(1, 2); @@ -575,7 +577,7 @@ void MainWindow::setupModList() ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - if (m_OrganizerCore.settings().restoreState(ui->modList->header())) { + if (m_OrganizerCore.settings().geometry().restoreState(ui->modList->header())) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { int sectionSize = ui->modList->header()->sectionSize(column); @@ -1417,12 +1419,6 @@ void MainWindow::cleanup() m_MetaSave.waitForFinished(); } - -void MainWindow::setBrowserGeometry(const QByteArray &geometry) -{ - m_IntegratedBrowser.restoreGeometry(geometry); -} - void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) { // don't display the widget if the main window doesn't have focus @@ -2259,10 +2255,10 @@ void MainWindow::activateProxy(bool activate) void MainWindow::readSettings(const Settings& settings) { - settings.restoreGeometry(this); - settings.restoreState(this); + settings.geometry().restoreGeometry(this); + settings.geometry().restoreState(this); settings.geometry().restoreToolbars(this); - settings.restoreState(ui->splitter); + settings.geometry().restoreState(ui->splitter); if (auto v=settings.geometry().getMenubarVisible()) { showMenuBar(*v); @@ -2340,38 +2336,22 @@ void MainWindow::storeSettings(Settings& s) { settings.setValue("selected_executable", ui->executablesListBox->currentIndex()); - if (settings.value("reset_geometry", false).toBool()) { - settings.remove("window_geometry"); - settings.remove("window_state"); - settings.remove("toolbar_size"); - settings.remove("toolbar_button_style"); - settings.remove("menubar_visible"); - settings.remove("window_split"); - settings.remove("window_monitor"); - settings.remove("filters_visible"); - settings.remove("browser_geometry"); - settings.remove("geometry"); - settings.remove("reset_geometry"); - } else { - s.saveState(this); - s.saveGeometry(this); - - s.geometry().setMenubarVisible(m_menuBarVisible); - s.geometry().saveToolbars(this); - s.geometry().setStatusbarVisible(m_statusBarVisible); - s.saveState(ui->splitter); - s.geometry().saveMainWindowMonitor(this); + s.geometry().saveState(this); + s.geometry().saveGeometry(this); - settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); - settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveToolbars(this); + s.geometry().setStatusbarVisible(m_statusBarVisible); + s.geometry().saveState(ui->splitter); + s.geometry().saveMainWindowMonitor(this); + s.geometry().setFiltersVisible(ui->displayCategoriesBtn->isChecked()); - s.saveState(ui->espList->header()); - s.saveState(ui->dataTree->header()); - s.saveState(ui->downloadView->header()); - s.saveState(ui->modList->header()); + s.geometry().saveState(ui->espList->header()); + s.geometry().saveState(ui->dataTree->header()); + s.geometry().saveState(ui->downloadView->header()); + s.geometry().saveState(ui->modList->header()); - DockFixer::save(this, s); - } + DockFixer::save(this, s); } ILockedWaitingForProcess* MainWindow::lock() @@ -6489,7 +6469,6 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe void MainWindow::on_bossButton_clicked() { - std::string reportURL; std::string errorMessages; //m_OrganizerCore.currentProfile()->writeModlistNow(); @@ -6637,16 +6616,6 @@ void MainWindow::on_bossButton_clicked() if (success) { m_DidUpdateMasterList = true; - if (reportURL.length() > 0) { - m_IntegratedBrowser.setWindowTitle("LOOT Report"); - QString report(reportURL.c_str()); - QStringList temp = report.split("?"); - QUrl url = QUrl::fromLocalFile(temp.at(0)); - if (temp.size() > 1) { - url.setQuery(temp.at(1).toUtf8()); - } - m_IntegratedBrowser.openUrl(url); - } m_OrganizerCore.refreshESPList(false); m_OrganizerCore.savePluginList(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 946a341b..8542dc8a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -293,7 +293,6 @@ private: static void setupNetworkProxy(bool activate); void activateProxy(bool activate); - void setBrowserGeometry(const QByteArray &geometry); bool createBackup(const QString &filePath, const QDateTime &time); QString queryRestore(const QString &filePath); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 47416311..fe1d8825 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -106,13 +106,13 @@ OverwriteInfoDialog::~OverwriteInfoDialog() void OverwriteInfoDialog::showEvent(QShowEvent* e) { - Settings::instance().restoreGeometry(this); + Settings::instance().geometry().restoreGeometry(this); QDialog::showEvent(e); } void OverwriteInfoDialog::done(int r) { - Settings::instance().saveGeometry(this); + Settings::instance().geometry().saveGeometry(this); QDialog::done(r); } diff --git a/src/settings.cpp b/src/settings.cpp index a3d12070..db6cecdf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -884,6 +884,7 @@ void Settings::dump() const m_Settings.endGroup(); } + QString widgetNameWithTopLevel(const QWidget* widget) { QStringList components; @@ -941,12 +942,46 @@ QString stateSettingName(const Widget* widget) return "geometry/" + widgetName(widget) + "_state"; } -void Settings::saveGeometry(const QWidget* w) + +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s), m_Reset(false) +{ +} + +void GeometrySettings::requestReset() +{ + m_Reset = true; +} + +void GeometrySettings::resetIfNeeded() +{ + if (!m_Reset) { + return; + } + + m_Settings.beginGroup("geometry"); + m_Settings.remove(""); + m_Settings.endGroup(); + + /*settings.remove("window_geometry"); + settings.remove("window_state"); + settings.remove("toolbar_size"); + settings.remove("toolbar_button_style"); + settings.remove("menubar_visible"); + settings.remove("window_split"); + settings.remove("window_monitor"); + settings.remove("filters_visible"); + settings.remove("browser_geometry"); + settings.remove("geometry"); + settings.remove("reset_geometry");*/ +} + +void GeometrySettings::saveGeometry(const QWidget* w) { m_Settings.setValue(geoSettingName(w), w->saveGeometry()); } -bool Settings::restoreGeometry(QWidget* w) const +bool GeometrySettings::restoreGeometry(QWidget* w) const { if (auto v=getOptional(m_Settings, geoSettingName(w))) { w->restoreGeometry(*v); @@ -956,12 +991,12 @@ bool Settings::restoreGeometry(QWidget* w) const return false; } -void Settings::saveState(const QMainWindow* w) +void GeometrySettings::saveState(const QMainWindow* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QMainWindow* w) const +bool GeometrySettings::restoreState(QMainWindow* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -971,12 +1006,12 @@ bool Settings::restoreState(QMainWindow* w) const return false; } -void Settings::saveState(const QHeaderView* w) +void GeometrySettings::saveState(const QHeaderView* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QHeaderView* w) const +bool GeometrySettings::restoreState(QHeaderView* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -986,12 +1021,12 @@ bool Settings::restoreState(QHeaderView* w) const return false; } -void Settings::saveState(const QSplitter* w) +void GeometrySettings::saveState(const QSplitter* w) { m_Settings.setValue(stateSettingName(w), w->saveState()); } -bool Settings::restoreState(QSplitter* w) const +bool GeometrySettings::restoreState(QSplitter* w) const { if (auto v=getOptional(m_Settings, stateSettingName(w))) { w->restoreState(*v); @@ -1001,12 +1036,6 @@ bool Settings::restoreState(QSplitter* w) const return false; } - -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s) -{ -} - bool GeometrySettings::restoreToolbars(QMainWindow* w) const { const auto size = getOptional(m_Settings, "toolbar_size"); @@ -1068,6 +1097,11 @@ std::optional GeometrySettings::getFiltersVisible() const return getOptional(m_Settings, "filters_visible"); } +void GeometrySettings::setFiltersVisible(bool b) +{ + m_Settings.setValue("filters_visible", b); +} + QStringList GeometrySettings::getModInfoTabOrder() const { QStringList v; @@ -1106,7 +1140,7 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) std::optional GeometrySettings::getMainWindowMonitor() const { - return getOptional(m_Settings, "geometry/window_monitor"); + return getOptional(m_Settings, "geometry/MainWindow_monitor"); } void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) @@ -1128,34 +1162,29 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) if (auto* handle=w->windowHandle()) { if (auto* screen = handle->screen()) { const int screenId = QGuiApplication::screens().indexOf(screen); - m_Settings.setValue("geometry/window_monitor", screenId); + m_Settings.setValue("geometry/MainWindow_monitor", screenId); } } } void GeometrySettings::setDockSize(const QString& name, int size) { - m_Settings.setValue("geometry/" + name + "_size", size); + m_Settings.setValue("geometry/MainWindow_docks_" + name + "_size", size); } std::optional GeometrySettings::getDockSize(const QString& name) const { - return getOptional(m_Settings, "geometry/" + name + "_size"); -} - -std::optional GeometrySettings::isCategoryListVisible() const -{ - return getOptional(m_Settings, "categorylist_visible"); + return getOptional(m_Settings, "geometry/MainWindow_docks_" + name + "_size"); } GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) : m_settings(s), m_dialog(dialog) { - m_settings.restoreGeometry(m_dialog); + m_settings.geometry().restoreGeometry(m_dialog); } GeometrySaver::~GeometrySaver() { - m_settings.saveGeometry(m_dialog); + m_settings.geometry().saveGeometry(m_dialog); } diff --git a/src/settings.h b/src/settings.h index bbf008f0..9ae58803 100644 --- a/src/settings.h +++ b/src/settings.h @@ -51,6 +51,24 @@ class GeometrySettings public: GeometrySettings(QSettings& s); + void requestReset(); + void resetIfNeeded(); + + void saveGeometry(const QWidget* w); + bool restoreGeometry(QWidget* w) const; + + void saveState(const QMainWindow* window); + bool restoreState(QMainWindow* window) const; + + void saveState(const QHeaderView* header); + bool restoreState(QHeaderView* header) const; + + void saveState(const QToolBar* toolbar); + bool restoreState(QToolBar* toolbar) const; + + void saveState(const QSplitter* splitter); + bool restoreState(QSplitter* splitter) const; + std::optional getMenubarVisible() const; void setMenubarVisible(bool b); @@ -61,6 +79,7 @@ public: void setStatusbarVisible(bool b); std::optional getFiltersVisible() const; + void setFiltersVisible(bool b); QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); @@ -73,10 +92,9 @@ public: std::optional getDockSize(const QString& name) const; - std::optional isCategoryListVisible() const; - private: QSettings& m_Settings; + bool m_Reset; }; @@ -210,23 +228,6 @@ public: GeometrySettings& geometry(); const GeometrySettings& geometry() const; - - void saveGeometry(const QWidget* w); - bool restoreGeometry(QWidget* w) const; - - void saveState(const QMainWindow* window); - bool restoreState(QMainWindow* window) const; - - void saveState(const QHeaderView* header); - bool restoreState(QHeaderView* header) const; - - void saveState(const QToolBar* toolbar); - bool restoreState(QToolBar* toolbar) const; - - void saveState(const QSplitter* splitter); - bool restoreState(QSplitter* splitter) const; - - /** * retrieve the directory where profiles stored (with native separators) **/ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index fbd9ecd1..d74507c9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -34,7 +34,6 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti , ui(new Ui::SettingsDialog) , m_settings(settings) , m_PluginContainer(pluginContainer) - , m_GeometriesReset(false) , m_keyChanged(false) { ui->setupUi(this); @@ -101,10 +100,7 @@ int SettingsDialog::exec() if (getApiKeyChanged()) { restartNeeded = true; } - if (getResetGeometries()) { - restartNeeded = true; - qsettings.setValue("reset_geometry", true); - } + if (restartNeeded) { if (QMessageBox::question(nullptr, tr("Restart Mod Organizer?"), @@ -156,11 +152,6 @@ void SettingsDialog::accept() TutorableDialog::accept(); } -bool SettingsDialog::getResetGeometries() -{ - return ui->resetGeometryBtn->isChecked(); -} - bool SettingsDialog::getApiKeyChanged() { return m_keyChanged; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 03bba7cf..efc4a095 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -71,7 +71,6 @@ public: // temp Ui::SettingsDialog *ui; bool m_keyChanged; - bool m_GeometriesReset; PluginContainer *m_PluginContainer; int exec() override; @@ -81,7 +80,6 @@ public slots: public: bool getApiKeyChanged(); - bool getResetGeometries(); private: Settings* m_settings; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index e011542e..e7676387 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1233,9 +1233,6 @@ programs you are intentionally running. Reset Window Geometries - - true - diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 9ac46ac1..fc859289 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -26,8 +26,6 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo ui->lockGUIBox->setChecked(m_parent->lockGUI()); ui->enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); - ui->resetGeometryBtn->setChecked(m_parent->directInterface().value("reset_geometry", false).toBool()); - setExecutableBlacklist(m_parent->executablesBlacklist()); QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); @@ -89,6 +87,16 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - m_dialog.m_GeometriesReset = true; - ui->resetGeometryBtn->setChecked(true); + const auto caption = QObject::tr("Restart Mod Organizer?"); + const auto text = QObject::tr( + "In order to reset the geometry, Mod Organizer must be restarted.\n" + "Restart now?"); + + const auto res = QMessageBox::question( + nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel); + + if (res == QMessageBox::Yes) { + m_parent->geometry().requestReset(); + qApp->exit(INT_MAX); + } } -- cgit v1.3.1 From 0374291a3451c464fb27e53077da42ad21c27cd6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 09:00:31 -0400 Subject: StatusBar now inherits from QStatusBar to handle hide/show events merged settings into saveVisibility() and restoreVisibility() call MainWindow::storeSettings() earlier so widget visibility is still valid --- src/iuserinterface.h | 5 - src/main.cpp | 2 - src/mainwindow.cpp | 78 +- src/mainwindow.h | 15 +- src/mainwindow.ui | 2129 +++++++++++++++++++++++++------------------------ src/organizercore.cpp | 4 - src/settings.cpp | 96 +-- src/settings.h | 13 +- src/statusbar.cpp | 65 +- src/statusbar.h | 14 +- 10 files changed, 1200 insertions(+), 1221 deletions(-) (limited to 'src') diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 7205f982..a309ed9b 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -10,14 +10,9 @@ #include -class Settings; - class IUserInterface { public: - - virtual void storeSettings(Settings &settings) = 0; - virtual void registerPluginTool(MOBase::IPluginTool *tool, QString name = QString(), QMenu *menu = nullptr) = 0; virtual void registerPluginTools(std::vector toolPlugins) = 0; virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0; diff --git a/src/main.cpp b/src/main.cpp index 6d4108fa..aa781c19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -736,8 +736,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); - mainWindow.readSettings(settings); - log::debug("displaying main window"); mainWindow.show(); mainWindow.activateWindow(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28e1de2e..7e471d24 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -285,8 +285,6 @@ MainWindow::MainWindow(Settings &settings : QMainWindow(parent) , ui(new Ui::MainWindow) , m_WasVisible(false) - , m_menuBarVisible(true) - , m_statusBarVisible(true) , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) @@ -312,7 +310,7 @@ MainWindow::MainWindow(Settings &settings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); ui->setupUi(this); - m_statusBar.reset(new StatusBar(statusBar(), ui)); + ui->statusBar->setup(ui); { auto* ni = NexusInterface::instance(&m_PluginContainer); @@ -336,7 +334,7 @@ MainWindow::MainWindow(Settings &settings // in the rare case where the user restarts MO through the settings, this // will correctly pick up the previous values updateWindowTitle(ni->getAPIUserAccount()); - m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); + ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } languageChange(settings.language()); @@ -708,7 +706,7 @@ void MainWindow::updateWindowTitle(const APIUserAccount& user) void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user) { - m_statusBar->setAPI(stats, user); + ui->statusBar->setAPI(stats, user); } @@ -902,7 +900,7 @@ QMenu* MainWindow::createPopupMenu() void MainWindow::on_actionMainMenuToggle_triggered() { - showMenuBar(!ui->menuBar->isVisible()); + ui->menuBar->setVisible(!ui->menuBar->isVisible()); } void MainWindow::on_actionToolBarMainToggle_triggered() @@ -912,7 +910,7 @@ void MainWindow::on_actionToolBarMainToggle_triggered() void MainWindow::on_actionStatusBarToggle_triggered() { - showStatusBar(!ui->statusBar->isVisible()); + ui->statusBar->setVisible(!ui->statusBar->isVisible()); } void MainWindow::on_actionToolBarSmallIcons_triggered() @@ -964,36 +962,6 @@ void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s) } } -void MainWindow::showMenuBar(bool b) -{ - ui->menuBar->setVisible(b); - m_menuBarVisible = b; -} - -void MainWindow::showStatusBar(bool b) -{ - ui->statusBar->setVisible(b); - m_statusBarVisible = b; - - // the central widget typically has no bottom padding because the status bar - // is more than enough, but when it's hidden, the bottom widget (currently - // the log) touches the bottom border of the window, which looks ugly - // - // when hiding the statusbar, the central widget is given the same border - // margin as it has on the top (which is typically 6, as it's the default from - // the qt designer) - - auto m = ui->centralWidget->layout()->contentsMargins(); - - if (b) { - m.setBottom(0); - } else { - m.setBottom(m.top()); - } - - ui->centralWidget->layout()->setContentsMargins(m); -} - void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) { // this allows for getting the context menu even if both the menubar and all @@ -1075,8 +1043,8 @@ void MainWindow::updateProblemsButton() } // updating the status bar, may be null very early when MO is starting - if (m_statusBar) { - m_statusBar->setNotifications(numProblems > 0); + if (ui->statusBar) { + ui->statusBar->setNotifications(numProblems > 0); } } @@ -1319,6 +1287,8 @@ void MainWindow::hookUpWindowTutorials() void MainWindow::showEvent(QShowEvent *event) { + readSettings(m_OrganizerCore.settings()); + refreshFilters(); QMainWindow::showEvent(event); @@ -1378,7 +1348,10 @@ void MainWindow::closeEvent(QCloseEvent* event) { if (!confirmExit()) { event->ignore(); + return; } + + storeSettings(m_OrganizerCore.settings()); } bool MainWindow::confirmExit() @@ -2259,17 +2232,12 @@ void MainWindow::readSettings(const Settings& settings) settings.geometry().restoreState(this); settings.geometry().restoreToolbars(this); settings.geometry().restoreState(ui->splitter); - - if (auto v=settings.geometry().getMenubarVisible()) { - showMenuBar(*v); - } - - if (auto v=settings.geometry().getStatusbarVisible()) { - showStatusBar(*v); - } + settings.geometry().restoreVisibility(ui->menuBar); + settings.geometry().restoreVisibility(ui->statusBar); { - auto v = settings.geometry().getFiltersVisible().value_or(false); + settings.geometry().restoreVisibility(ui->categoriesGroup, false); + const auto v = ui->categoriesGroup->isVisible(); setCategoryListVisible(v); ui->displayCategoriesBtn->setChecked(v); } @@ -2339,12 +2307,12 @@ void MainWindow::storeSettings(Settings& s) { s.geometry().saveState(this); s.geometry().saveGeometry(this); - s.geometry().setMenubarVisible(m_menuBarVisible); + s.geometry().saveVisibility(ui->menuBar); + s.geometry().saveVisibility(ui->statusBar); s.geometry().saveToolbars(this); - s.geometry().setStatusbarVisible(m_statusBarVisible); s.geometry().saveState(ui->splitter); s.geometry().saveMainWindowMonitor(this); - s.geometry().setFiltersVisible(ui->displayCategoriesBtn->isChecked()); + s.geometry().saveVisibility(ui->categoriesGroup); s.geometry().saveState(ui->espList->header()); s.geometry().saveState(ui->dataTree->header()); @@ -2606,7 +2574,7 @@ void MainWindow::setESPListSorting(int index) void MainWindow::refresher_progress(int percent) { setEnabled(percent == 100); - m_statusBar->setProgress(percent); + ui->statusBar->setProgress(percent); } void MainWindow::directory_refreshed() @@ -5216,7 +5184,7 @@ void MainWindow::on_actionSettings_triggered() activateProxy(settings.useProxy()); } - m_statusBar->checkSettings(m_OrganizerCore.settings()); + ui->statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); m_OrganizerCore.setLogLevel(settings.logLevel()); @@ -5525,7 +5493,7 @@ void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); ui->actionUpdate->setToolTip(tr("Update available")); - m_statusBar->setUpdateAvailable(true); + ui->statusBar->setUpdateAvailable(true); } @@ -6858,7 +6826,7 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) // if the menubar is hidden, pressing Alt will make it visible if (event->key() == Qt::Key_Alt) { if (!ui->menuBar->isVisible()) { - showMenuBar(true); + ui->menuBar->show(); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 8542dc8a..a905a163 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -40,7 +40,6 @@ class Executable; class CategoryFactory; class LockedDialogBase; class OrganizerCore; -class StatusBar; class PluginListSortProxy; namespace BSA { class Archive; } @@ -118,8 +117,6 @@ public: QWidget *parent = 0); ~MainWindow(); - void storeSettings(Settings& settings) override; - void readSettings(const Settings& settings); void processUpdates(Settings& settings); virtual ILockedWaitingForProcess* lock() override; @@ -331,12 +328,6 @@ private: bool m_WasVisible; - // this has to be remembered because by the time storeSettings() is called, - // the window is closed and the all bars are hidden - bool m_menuBarVisible, m_statusBarVisible; - - std::unique_ptr m_statusBar; - // last separator on the toolbar, used to add spacer for right-alignment and // as an insert point for executables QAction* m_linksSeparator; @@ -685,11 +676,9 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); + void storeSettings(Settings& settings); + void readSettings(const Settings& settings); void setupModList(); - void showMenuBar(bool b); - void showStatusBar(bool b); }; - - #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e9910b83..02c6dec0 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -48,1239 +48,1239 @@ - + + + - - - - - Categories - - - + + + Categories + + + + 0 + + + 3 + + + 7 + + + 3 + + + 1 + + + + + + 120 + 0 + + + + + 214 + 16777215 + + + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + 0 - - 3 + + true - - 7 + + false + + + + 1 + + + + + + + + false - - 3 + + + 0 + 0 + - - 1 + + + 0 + 25 + - - - - - 120 - 0 - - - - - 214 - 16777215 - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - 0 - - - true - - - false - - - - 1 - - - - - - - - false - - - - 0 - 0 - - - - - 0 - 25 - - - - Clear - - - true - - - - - - - - 0 - 0 - - - - - - - If checked, only mods that match all selected categories are displayed. - - - And - - - true - - - - - - - If checked, all mods that match at least one of the selected categories are displayed. - - - Or - - - - - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - 2 - - - - - - - - 0 - 0 - - - - Profile - - - profileBox - - - - - - - Pick a module collection - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 16777215 - 16777215 - - - - Open list options... - - - Refresh list. This is usually not necessary unless you modified data outside the program. - - - - - - - :/MO/gui/settings:/MO/gui/settings - - - - 16 - 16 - - - - - - - - Show Open Folders menu... - - - - - - - :/MO/gui/open_folder:/MO/gui/open_folder - - - + + Clear + + + true + + + + + + + + 0 + 0 + + + - + - Restore Backup... + If checked, only mods that match all selected categories are displayed. - + And - - - :/MO/gui/restore:/MO/gui/restore + + true - + - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup + If checked, all mods that match at least one of the selected categories are displayed. - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 5 - - - QLCDNumber::Flat + Or + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 2 + + + + + + + + 0 + 0 + + + + Profile + + + profileBox + + - - + + + Pick a module collection + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> + + + + + + + Qt::Horizontal + + - 330 - 400 + 40 + 20 - - Qt::CustomContextMenu + + + + + + + 16777215 + 16777215 + - List of available mods. + Open list options... - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + Refresh list. This is usually not necessary unless you modified data outside the program. - + + + + + + :/MO/gui/settings:/MO/gui/settings + + + + 16 + 16 + + + + + + + + Show Open Folders menu... + + + + + + + :/MO/gui/open_folder:/MO/gui/open_folder + + + + + + + Restore Backup... + + - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + :/MO/gui/restore:/MO/gui/restore - - true + + + + + + Create Backup - - true + + - - QAbstractItemView::DragDrop + + + :/MO/gui/backup:/MO/gui/backup - - Qt::MoveAction + + + + + + Active: - - true + + + + + + + 0 + 26 + - - QAbstractItemView::ExtendedSelection + + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - QAbstractItemView::SelectRows + + QFrame::Sunken - - 20 + + 5 - - true + + QLCDNumber::Flat - - true + + + + + + + + + 330 + 400 + + + + Qt::CustomContextMenu + + + List of available mods. + + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + + + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 20 + + + true + + + true + + + true + + + false + + + 35 + + + true + + + false + + + + + + + + + + 20 + 16777215 + - - true + + x - - false + + + 20 + 20 + - - 35 - - + true - - + + + + + + + + 0 + 0 + + + + Filter + + + + + + + + 8 + true + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + + 95 + 0 + + + false - + + + Qt::RightToLeft + + + border:1px solid #ff0000; + + + Clear all Filters + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + 12 + 12 + + - - - - - - 20 - 16777215 - - - - x - - - - 20 - 20 - - - - true - - - - - - - - 0 - 0 - - - - Filter - - - - - - - - 8 - true - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - - 95 - 0 - - - - false - - - Qt::RightToLeft - - - border:1px solid #ff0000; - - - Clear all Filters - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - 12 - 12 - - - + + + + 220 + 0 + + + + Qt::ClickFocus + + + + No groups + - - - - 220 - 0 - - - - Qt::ClickFocus - - - - No groups - - - - - Categories - - - - - Nexus IDs - - - + + Categories + - - - - 220 - 0 - - - - Filter - - + + Nexus IDs + - + - - - - - - + + + + 220 + 0 + + + + Filter + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 9 + 75 + true + + + + Pick a program to run. + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + + + + 32 + 32 + + + + false + + + + + - + - + 0 0 - 0 - 40 + 120 + 0 + + + + + 16777215 + 16777215 - 9 + 10 75 true - Pick a program to run. + Run program <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + + + + + + Run + + + + :/MO/gui/run:/MO/gui/run - 32 - 32 + 36 + 36 - - false - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 16777215 - 16777215 - - - - - 10 - 75 - true - - - - Run program - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - - - - - - Run - - - - :/MO/gui/run:/MO/gui/run - - - - 36 - 36 - - - - - - - - - 0 - 0 - - - - - 140 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - Create a shortcut in your start menu or on the desktop to the specified program - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + 0 + 0 + + + + + 140 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + Create a shortcut in your start menu or on the desktop to the specified program + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - - - Shortcut - - - - :/MO/gui/link:/MO/gui/link - - - - + + + Shortcut + + + + :/MO/gui/link:/MO/gui/link + + - - - - - - - 340 - 250 - - - - - 16777215 - 16777215 - + + + + + + + + + 340 + 250 + + + + + 16777215 + 16777215 + + + + Qt::NoContextMenu + + + QTabWidget::Rounded + + + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Plugins + + + + 6 - - Qt::NoContextMenu + + 6 - - QTabWidget::Rounded + + 6 - + 0 - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Plugins - - - - 6 + + + + + + true + + + Sort + + + + :/MO/gui/sort:/MO/gui/sort + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + 16 + 16 + + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + + + Active: + + + + + + + + 0 + 26 + + + + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + QFrame::Sunken + + + 4 + + + QLCDNumber::Flat + + + + + + + + + + 250 + 250 + + + + Qt::CustomContextMenu + + + List of available esp/esm files + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + false + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 0 + + + true - - 6 + + false - - 6 + + true - - 0 + + false + + false + + + + + - - - - - true - - - Sort - - - - :/MO/gui/sort:/MO/gui/sort - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Restore Backup... - - - - - - - :/MO/gui/restore:/MO/gui/restore - - - - 16 - 16 - - - - - - - - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup - - - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 4 - - - QLCDNumber::Flat - - - - - - - - - - 250 - 250 - + + + - - Qt::CustomContextMenu + + Filter + + + + + + + + + false + + + Archives + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + - List of available esp/esm files - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::MoveAction - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - 0 - - - true + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - false + + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + true - - false - - - false - - - - - - - - - - Filter - - - - - - - - - false - - - Archives - - - - 6 + + + + + Qt::CustomContextMenu - - 6 + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - 6 + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! + + BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - 6 + + false + + false + + + false + + + 20 + + + true + + + 1 + + + + + + + + Data + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + refresh data-directory overview + + + Refresh the overview. This may take a moment. + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + - - - - - <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - - - true - - - - - - - + Qt::CustomContextMenu - - List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. - By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! - - BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - - false - - - false - - - false + This is an overview of your data directory as visible to the game (and tools). - - 20 - - + true - - 1 + + true + + 400 + + + + File + + + + + Mod + + - - - - Data - - - - 6 - - - 6 - - - 6 - - - 6 - + + + - + - refresh data-directory overview + Filters the above list so that only conflicts are displayed. - Refresh the overview. This may take a moment. + Filters the above list so that only conflicts are displayed. - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + Show only conflicts - - - - - Qt::CustomContextMenu - - - This is an overview of your data directory as visible to the game (and tools). - - - true - - - true - - - 400 - - - - File - - - - - Mod - - - - - - - - - - - - Filters the above list so that only conflicts are displayed. - - - Filters the above list so that only conflicts are displayed. - - - Show only conflicts - - - - - - - Filters the above list so that files from archives are not shown - - - - - - Filters the above list so that files from archives are not shown - - - Show files from Archives - - - - + + + Filters the above list so that files from archives are not shown + + + + + + Filters the above list so that files from archives are not shown + + + Show files from Archives + + - - - - Saves - - - - 6 + + + + + + Saves + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Qt::CustomContextMenu + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows - - 6 + + + + + + + Downloads + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Refresh downloads view - - 6 + + Refresh - - 6 + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + - + + + + 320 + 0 + + Qt::CustomContextMenu + + true + - + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. + + + Qt::ScrollBarAlwaysOn + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ScrollPerPixel + + + 0 - - QAbstractItemView::ExtendedSelection + + false - - QAbstractItemView::SelectRows + + true - - - - Downloads - - - - 2 - - - 2 - - - 2 - - - 2 - + + + - - - Refresh downloads view - + - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + Show Hidden - - - - - - 320 - 0 - - - - Qt::CustomContextMenu - - - true - - - - - - This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - - - Qt::ScrollBarAlwaysOn - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::ScrollPerPixel - - - 0 - - - false - - - true - - - - + + + Qt::Horizontal + + + + 40 + 20 + + + - - - - - Show Hidden - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - + + + Filter + + - - - - - - - - + + + + + + + + + + @@ -1320,7 +1320,7 @@ p, li { white-space: pre-wrap; } - + @@ -1790,6 +1790,11 @@ p, li { white-space: pre-wrap; } QTreeView
loglist.h
+ + StatusBar + QStatusBar +
statusbar.h
+
diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2d11dafd..a2b0fd69 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -335,10 +335,6 @@ OrganizerCore::~OrganizerCore() void OrganizerCore::storeSettings() { - if (m_UserInterface != nullptr) { - m_UserInterface->storeSettings(m_Settings); - } - if (m_CurrentProfile != nullptr) { m_Settings.setSelectedProfileName(m_CurrentProfile->name()); } diff --git a/src/settings.cpp b/src/settings.cpp index db6cecdf..06b4446a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -942,6 +942,12 @@ QString stateSettingName(const Widget* widget) return "geometry/" + widgetName(widget) + "_state"; } +template +QString visibilitySettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_visibility"; +} + GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s), m_Reset(false) @@ -962,18 +968,6 @@ void GeometrySettings::resetIfNeeded() m_Settings.beginGroup("geometry"); m_Settings.remove(""); m_Settings.endGroup(); - - /*settings.remove("window_geometry"); - settings.remove("window_state"); - settings.remove("toolbar_size"); - settings.remove("toolbar_button_style"); - settings.remove("menubar_visible"); - settings.remove("window_split"); - settings.remove("window_monitor"); - settings.remove("filters_visible"); - settings.remove("browser_geometry"); - settings.remove("geometry"); - settings.remove("reset_geometry");*/ } void GeometrySettings::saveGeometry(const QWidget* w) @@ -1036,15 +1030,32 @@ bool GeometrySettings::restoreState(QSplitter* w) const return false; } -bool GeometrySettings::restoreToolbars(QMainWindow* w) const +void GeometrySettings::saveVisibility(const QWidget* w) { - const auto size = getOptional(m_Settings, "toolbar_size"); - const auto style = getOptional(m_Settings, "toolbar_button_style"); + m_Settings.setValue(visibilitySettingName(w), w->isVisible()); +} - if (!size && !style) { - return false; +bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const +{ + auto v = getOptional(m_Settings, visibilitySettingName(w)); + if (!v) { + v = def; + } + + if (v) { + w->setVisible(*v); + return true; } + return false; +} + +void GeometrySettings::restoreToolbars(QMainWindow* w) const +{ + // all toolbars have the same size and button style settings + const auto size = getOptional(m_Settings, "toolbar_size"); + const auto style = getOptional(m_Settings, "toolbar_button_style"); + for (auto* tb : w->findChildren()) { if (size) { tb->setIconSize(*size); @@ -1053,53 +1064,28 @@ bool GeometrySettings::restoreToolbars(QMainWindow* w) const if (style) { tb->setToolButtonStyle(static_cast(*style)); } - } - return true; + restoreVisibility(tb); + } } void GeometrySettings::saveToolbars(const QMainWindow* w) { - // all toolbars are identical, just save the first one const auto tbs = w->findChildren(); - if (tbs.isEmpty()) { - return; - } - - const auto* tb = tbs[0]; - - m_Settings.setValue("toolbar_size", tb->iconSize()); - m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); -} - -std::optional GeometrySettings::getMenubarVisible() const -{ - return getOptional(m_Settings, "menubar_visible"); -} - -void GeometrySettings::setMenubarVisible(bool b) -{ - m_Settings.setValue("menubar_visible", b); -} - -std::optional GeometrySettings::getStatusbarVisible() const -{ - return getOptional(m_Settings, "statusbar_visible"); -} -void GeometrySettings::setStatusbarVisible(bool b) -{ - m_Settings.setValue("statusbar_visible", b); -} + // save visibility for all + for (auto* tb : tbs) { + saveVisibility(tb); + } -std::optional GeometrySettings::getFiltersVisible() const -{ - return getOptional(m_Settings, "filters_visible"); -} + // all toolbars have the same size and button style settings, just save the + // first one + if (!tbs.isEmpty()) { + const auto* tb = tbs[0]; -void GeometrySettings::setFiltersVisible(bool b) -{ - m_Settings.setValue("filters_visible", b); + m_Settings.setValue("toolbar_size", tb->iconSize()); + m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); + } } QStringList GeometrySettings::getModInfoTabOrder() const diff --git a/src/settings.h b/src/settings.h index 9ae58803..072b4066 100644 --- a/src/settings.h +++ b/src/settings.h @@ -54,6 +54,7 @@ public: void requestReset(); void resetIfNeeded(); + void saveGeometry(const QWidget* w); bool restoreGeometry(QWidget* w) const; @@ -69,17 +70,13 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - std::optional getMenubarVisible() const; - void setMenubarVisible(bool b); - bool restoreToolbars(QMainWindow* w) const; - void saveToolbars(const QMainWindow* w); + void saveVisibility(const QWidget* w); + bool restoreVisibility(QWidget* w, std::optional defaultValue={}) const; - std::optional getStatusbarVisible() const; - void setStatusbarVisible(bool b); - std::optional getFiltersVisible() const; - void setFiltersVisible(bool b); + void saveToolbars(const QMainWindow* w); + void restoreToolbars(QMainWindow* w) const; QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); diff --git a/src/statusbar.cpp b/src/statusbar.cpp index e9a6e658..d22010a5 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -3,26 +3,32 @@ #include "settings.h" #include "ui_mainwindow.h" -StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : - m_bar(bar), m_progress(new QProgressBar), - m_notifications(new StatusBarAction(ui->actionNotifications)), - m_update(new StatusBarAction(ui->actionUpdate)), - m_api(new QLabel) +StatusBar::StatusBar(QWidget* parent) : + QStatusBar(parent), ui(nullptr), m_progress(new QProgressBar), + m_notifications(nullptr), m_update(nullptr), m_api(new QLabel) { +} + +void StatusBar::setup(Ui::MainWindow* mainWindowUI) +{ + ui = mainWindowUI; + m_notifications = new StatusBarAction(ui->actionNotifications); + m_update = new StatusBarAction(ui->actionUpdate); + QWidget* spacer1 = new QWidget; spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spacer1->setHidden(true); spacer1->setVisible(true); - m_bar->addPermanentWidget(spacer1, 0); - m_bar->addPermanentWidget(m_progress); + addPermanentWidget(spacer1, 0); + addPermanentWidget(m_progress); QWidget* spacer2 = new QWidget; spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); spacer2->setHidden(true); spacer2->setVisible(true); - m_bar->addPermanentWidget(spacer2,0); - m_bar->addPermanentWidget(m_notifications); - m_bar->addPermanentWidget(m_update); - m_bar->addPermanentWidget(m_api); + addPermanentWidget(spacer2,0); + addPermanentWidget(m_notifications); + addPermanentWidget(m_update); + addPermanentWidget(m_api); m_progress->setTextVisible(true); @@ -42,7 +48,7 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : "be unable to queue downloads, check updates, parse mod info, or even log " "in. Both pools must be consumed before this happens.")); - m_bar->clearMessage(); + clearMessage(); setProgress(-1); setAPI({}, {}); } @@ -50,10 +56,10 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : void StatusBar::setProgress(int percent) { if (percent < 0 || percent >= 100) { - m_bar->clearMessage(); + clearMessage(); m_progress->setVisible(false); } else { - m_bar->showMessage(QObject::tr("Loading...")); + showMessage(QObject::tr("Loading...")); m_progress->setVisible(true); m_progress->setValue(percent); } @@ -126,6 +132,37 @@ void StatusBar::checkSettings(const Settings& settings) m_api->setVisible(!settings.hideAPICounter()); } +void StatusBar::showEvent(QShowEvent*) +{ + visibilityChanged(true); +} + +void StatusBar::hideEvent(QHideEvent*) +{ + visibilityChanged(false); +} + +void StatusBar::visibilityChanged(bool visible) +{ + // the central widget typically has no bottom padding because the status bar + // is more than enough, but when it's hidden, the bottom widget (currently + // the log) touches the bottom border of the window, which looks ugly + // + // when hiding the statusbar, the central widget is given the same border + // margin as it has on the top (which is typically 6, as it's the default from + // the qt designer) + + auto m = ui->centralWidget->layout()->contentsMargins(); + + if (visible) { + m.setBottom(0); + } else { + m.setBottom(m.top()); + } + + ui->centralWidget->layout()->setContentsMargins(m); +} + StatusBarAction::StatusBarAction(QAction* action) : m_action(action), m_icon(new QLabel), m_text(new QLabel) diff --git a/src/statusbar.h b/src/statusbar.h index 2baf12ee..442b9acf 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -29,10 +29,12 @@ private: }; -class StatusBar +class StatusBar : public QStatusBar { public: - StatusBar(QStatusBar* bar, Ui::MainWindow* ui); + StatusBar(QWidget* parent=nullptr); + + void setup(Ui::MainWindow* ui); void setProgress(int percent); void setNotifications(bool hasNotifications); @@ -40,12 +42,18 @@ public: void setUpdateAvailable(bool b); void checkSettings(const Settings& settings); +protected: + void showEvent(QShowEvent* e); + void hideEvent(QHideEvent* e); + private: - QStatusBar* m_bar; + Ui::MainWindow* ui; QProgressBar* m_progress; StatusBarAction* m_notifications; StatusBarAction* m_update; QLabel* m_api; + + void visibilityChanged(bool visible); }; #endif // MO_STATUSBAR_H -- cgit v1.3.1 From 965eccb328a0a2b0cb4d1945a0382df9f0f91147 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 16 Aug 2019 10:29:42 -0400 Subject: merged DockFixer into GeometrySettings added combobox index to settings --- src/mainwindow.cpp | 156 ++++++-------------------------- src/mainwindow.h | 2 - src/pch.h | 1 + src/settings.cpp | 258 +++++++++++++++++++++++++++++++++++------------------ src/settings.h | 18 ++-- 5 files changed, 210 insertions(+), 225 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7e471d24..bce92e48 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -194,90 +194,6 @@ const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); -// this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock -// sizes are not restored when the main window is maximized; it is used in -// MainWindow::readSettings() and MainWindow::storeSettings() -// -// there's also https://stackoverflow.com/questions/44005852, which has what -// seems to be a popular fix, but it breaks the restored size of the window -// by setting it to the desktop's resolution, so that doesn't work -// -// the only fix I could find is to remember the sizes of the docks and manually -// setting them back; saving is straightforward, but restoring is messy -// -// this also depends on the window being visible before the timer in restore() -// is fired and the timer must be processed by application.exec(); therefore, -// the splash screen _must_ be closed before readSettings() is called, because -// it has its own event loop, which seems to interfere with this -// -// all of this should become unnecessary when QTBUG-46620 is fixed -// -class DockFixer -{ -public: - static void save(MainWindow* mw, Settings& settings) - { - // saves the size of each dock - for (const auto* dock : mw->findChildren()) { - int size = 0; - - // save the width for horizontal docks, or the height for vertical - if (orientation(mw, dock) == Qt::Horizontal) { - size = dock->size().width(); - } else { - size = dock->size().height(); - } - - settings.geometry().setDockSize(dock->objectName(), size); - } - } - - static void restore(MainWindow* mw, const Settings& settings) - { - struct DockInfo - { - QDockWidget* d; - int size = 0; - Qt::Orientation ori; - }; - - std::vector dockInfos; - - // for each dock - for (auto* dock : mw->findChildren()) { - if (auto size=settings.geometry().getDockSize(dock->objectName())) { - // remember this dock, its size and orientation - dockInfos.push_back({dock, *size, orientation(mw, dock)}); - } - } - - // the main window must have had time to process the settings from - // readSettings() or it seems to override whatever is set here - // - // some people said a single processEvents() call is enough, but it doesn't - // look like it - QTimer::singleShot(5, [=] { - for (const auto& info : dockInfos) { - mw->resizeDocks({info.d}, {info.size}, info.ori); - } - }); - } - - static Qt::Orientation orientation(QMainWindow* mw, const QDockWidget* d) - { - // docks in these areas are horizontal - const auto horizontalAreas = - Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; - - if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { - return Qt::Horizontal; - } else { - return Qt::Vertical; - } - } -}; - - MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer @@ -1328,12 +1244,10 @@ void MainWindow::showEvent(QShowEvent *event) QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); } - m_OrganizerCore.settings().directInterface().setValue("first_start", false); + m_OrganizerCore.settings().directInterface().setValue("first_start", false); } - // this has no visible impact when called before the ui is visible - int grouping = m_OrganizerCore.settings().directInterface().value("group_state").toInt(); - ui->groupCombo->setCurrentIndex(grouping); + m_OrganizerCore.settings().restoreIndex(ui->groupCombo); allowListResize(); @@ -1621,18 +1535,6 @@ void MainWindow::startExeAction() } - -void MainWindow::setExecutableIndex(int index) -{ - QComboBox *executableBox = findChild("executablesListBox"); - - if ((index != 0) && (executableBox->count() > index)) { - executableBox->setCurrentIndex(index); - } else { - executableBox->setCurrentIndex(1); - } -} - void MainWindow::activateSelectedProfile() { m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText()); @@ -1895,7 +1797,7 @@ void MainWindow::refreshExecutablesList() ++i; } - setExecutableIndex(1); + ui->executablesListBox->setCurrentIndex(1); executablesList->setEnabled(true); } @@ -2230,11 +2132,24 @@ void MainWindow::readSettings(const Settings& settings) { settings.geometry().restoreGeometry(this); settings.geometry().restoreState(this); + settings.geometry().restoreDocks(this); settings.geometry().restoreToolbars(this); settings.geometry().restoreState(ui->splitter); settings.geometry().restoreVisibility(ui->menuBar); settings.geometry().restoreVisibility(ui->statusBar); + { + // special case in case someone puts 0 in the INI + auto v = settings.getIndex(ui->executablesListBox); + if (!v || v == 0) { + v = 1; + } + + ui->executablesListBox->setCurrentIndex(*v); + } + + settings.restoreIndex(ui->groupCombo); + { settings.geometry().restoreVisibility(ui->categoriesGroup, false); const auto v = ui->categoriesGroup->isVisible(); @@ -2242,17 +2157,11 @@ void MainWindow::readSettings(const Settings& settings) ui->displayCategoriesBtn->setChecked(v); } - if (auto v=settings.getSelectedExecutable()) { - setExecutableIndex(*v); - } - if (auto v=settings.getUseProxy()) { if (*v) { activateProxy(true); } } - - DockFixer::restore(this, settings); } void MainWindow::processUpdates(Settings& settings) { @@ -2297,15 +2206,11 @@ void MainWindow::processUpdates(Settings& settings) { } } -void MainWindow::storeSettings(Settings& s) { - auto& settings = s.directInterface(); - - settings.setValue("group_state", ui->groupCombo->currentIndex()); - settings.setValue("selected_executable", - ui->executablesListBox->currentIndex()); - +void MainWindow::storeSettings(Settings& s) +{ s.geometry().saveState(this); s.geometry().saveGeometry(this); + s.geometry().saveDocks(this); s.geometry().saveVisibility(ui->menuBar); s.geometry().saveVisibility(ui->statusBar); @@ -2319,7 +2224,8 @@ void MainWindow::storeSettings(Settings& s) { s.geometry().saveState(ui->downloadView->header()); s.geometry().saveState(ui->modList->header()); - DockFixer::save(this, s); + s.saveIndex(ui->groupCombo); + s.saveIndex(ui->executablesListBox); } ILockedWaitingForProcess* MainWindow::lock() @@ -2451,20 +2357,16 @@ bool MainWindow::modifyExecutablesDialog() void MainWindow::on_executablesListBox_currentIndexChanged(int index) { - QComboBox* executablesList = findChild("executablesListBox"); + if (!ui->executablesListBox->isEnabled()) { + return; + } - int previousIndex = m_OldExecutableIndex; + const int previousIndex = m_OldExecutableIndex; m_OldExecutableIndex = index; - if (executablesList->isEnabled()) { - //I think the 2nd test is impossible - if ((index == 0) || (index > static_cast(m_OrganizerCore.executablesList()->size()))) { - if (modifyExecutablesDialog()) { - setExecutableIndex(previousIndex); - } - } else { - setExecutableIndex(index); - } + if (index == 0) { + modifyExecutablesDialog(); + ui->executablesListBox->setCurrentIndex(previousIndex); } } @@ -2540,7 +2442,7 @@ void MainWindow::on_actionAdd_Profile_triggered() void MainWindow::on_actionModify_Executables_triggered() { if (modifyExecutablesDialog()) { - setExecutableIndex(m_OldExecutableIndex); + ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index a905a163..6f06b9d5 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -226,8 +226,6 @@ private: QMenu* createPopupMenu() override; void activateSelectedProfile(); - void setExecutableIndex(int index); - void startSteam(); void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly, QIcon *fileIcon, QIcon *folderIcon); diff --git a/src/pch.h b/src/pch.h index dd65efbe..af1a4ade 100644 --- a/src/pch.h +++ b/src/pch.h @@ -95,6 +95,7 @@ #include #include #include +#include #include #include #include diff --git a/src/settings.cpp b/src/settings.cpp index 06b4446a..40f4dd95 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -28,13 +28,88 @@ along with Mod Organizer. If not, see . using namespace MOBase; template -std::optional getOptional(const QSettings& s, const QString& name) +std::optional getOptional( + const QSettings& s, const QString& name, std::optional def={}) { if (s.contains(name)) { return s.value(name).value(); } - return {}; + return def; +} + + +QString widgetNameWithTopLevel(const QWidget* widget) +{ + QStringList components; + + auto* tl = widget->window(); + + if (tl == widget) { + // this is a top level widget, such as a dialog + components.push_back(widget->objectName()); + } else { + // this is a widget + const auto toplevelName = tl->objectName(); + if (!toplevelName.isEmpty()) { + components.push_back(toplevelName); + } + + const auto widgetName = widget->objectName(); + if (!widgetName.isEmpty()) { + components.push_back(widgetName); + } + } + + if (components.isEmpty()) { + // can't do much + return "unknown_widget"; + } + + return components.join("_"); +} + +QString widgetName(const QMainWindow* w) +{ + return w->objectName(); +} + +QString widgetName(const QHeaderView* w) +{ + return widgetNameWithTopLevel(w->parentWidget()); +} + +QString widgetName(const QWidget* w) +{ + return widgetNameWithTopLevel(w); +} + +template +QString geoSettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_geometry"; +} + +template +QString stateSettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_state"; +} + +template +QString visibilitySettingName(const Widget* widget) +{ + return "geometry/" + widgetName(widget) + "_visibility"; +} + +QString dockSettingName(const QDockWidget* dock) +{ + return "geometry/MainWindow_docks_" + dock->objectName() + "_size"; +} + +QString indexSettingName(const QWidget* widget) +{ + return widgetNameWithTopLevel(widget) + "_index"; } @@ -395,11 +470,6 @@ void Settings::setStyleName(const QString& name) m_Settings.setValue("Settings/style", name); } -std::optional Settings::getSelectedExecutable() const -{ - return getOptional(m_Settings, "selected_executable"); -} - std::optional Settings::getUseProxy() const { return getOptional(m_Settings, "Settings/use_proxy"); @@ -847,6 +917,23 @@ void Settings::setExecutables(const std::vector>& v) m_Settings.endArray(); } +std::optional Settings::getIndex(QComboBox* cb) const +{ + return getOptional(m_Settings, indexSettingName(cb)); +} + +void Settings::saveIndex(const QComboBox* cb) +{ + m_Settings.setValue(indexSettingName(cb), cb->currentIndex()); +} + +void Settings::restoreIndex(QComboBox* cb, std::optional def) const +{ + if (auto v=getOptional(m_Settings, indexSettingName(cb), def)) { + cb->setCurrentIndex(*v); + } +} + GeometrySettings& Settings::geometry() { return m_Geometry; @@ -885,70 +972,6 @@ void Settings::dump() const } -QString widgetNameWithTopLevel(const QWidget* widget) -{ - QStringList components; - - auto* tl = widget->window(); - - if (tl == widget) { - // this is a top level widget, such as a dialog - components.push_back(widget->objectName()); - } else { - // this is a widget - const auto toplevelName = tl->objectName(); - if (!toplevelName.isEmpty()) { - components.push_back(toplevelName); - } - - const auto widgetName = widget->objectName(); - if (!widgetName.isEmpty()) { - components.push_back(widgetName); - } - } - - if (components.isEmpty()) { - // can't do much - return "unknown_widget"; - } - - return components.join("_"); -} - -QString widgetName(const QMainWindow* w) -{ - return w->objectName(); -} - -QString widgetName(const QHeaderView* w) -{ - return widgetNameWithTopLevel(w->parentWidget()); -} - -QString widgetName(const QWidget* w) -{ - return widgetNameWithTopLevel(w); -} - -template -QString geoSettingName(const Widget* widget) -{ - return "geometry/" + widgetName(widget) + "_geometry"; -} - -template -QString stateSettingName(const Widget* widget) -{ - return "geometry/" + widgetName(widget) + "_state"; -} - -template -QString visibilitySettingName(const Widget* widget) -{ - return "geometry/" + widgetName(widget) + "_visibility"; -} - - GeometrySettings::GeometrySettings(QSettings& s) : m_Settings(s), m_Reset(false) { @@ -1037,12 +1060,7 @@ void GeometrySettings::saveVisibility(const QWidget* w) bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - auto v = getOptional(m_Settings, visibilitySettingName(w)); - if (!v) { - v = def; - } - - if (v) { + if (auto v=getOptional(m_Settings, visibilitySettingName(w), def)) { w->setVisible(*v); return true; } @@ -1124,14 +1142,10 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) m_Settings.setValue("mod_info_tab_order", names); } -std::optional GeometrySettings::getMainWindowMonitor() const -{ - return getOptional(m_Settings, "geometry/MainWindow_monitor"); -} - void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { - const auto monitor = getMainWindowMonitor(); + const auto monitor = getOptional(m_Settings, "geometry/MainWindow_monitor"); + QPoint center; if (monitor && QGuiApplication::screens().size() > *monitor) { @@ -1153,14 +1167,84 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) } } -void GeometrySettings::setDockSize(const QString& name, int size) +Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) { - m_Settings.setValue("geometry/MainWindow_docks_" + name + "_size", size); + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } } -std::optional GeometrySettings::getDockSize(const QString& name) const +void GeometrySettings::saveDocks(const QMainWindow* mw) +{ + // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock + // sizes are not restored when the main window is maximized; it is used in + // MainWindow::readSettings() and MainWindow::storeSettings() + // + // there's also https://stackoverflow.com/questions/44005852, which has what + // seems to be a popular fix, but it breaks the restored size of the window + // by setting it to the desktop's resolution, so that doesn't work + // + // the only fix I could find is to remember the sizes of the docks and manually + // setting them back; saving is straightforward, but restoring is messy + // + // this also depends on the window being visible before the timer in restore() + // is fired and the timer must be processed by application.exec(); therefore, + // the splash screen _must_ be closed before readSettings() is called, because + // it has its own event loop, which seems to interfere with this + // + // all of this should become unnecessary when QTBUG-46620 is fixed + // + + // saves the size of each dock + for (const auto* dock : mw->findChildren()) { + int size = 0; + + // save the width for horizontal docks, or the height for vertical + if (dockOrientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); + } else { + size = dock->size().height(); + } + + m_Settings.setValue(dockSettingName(dock), size); + } +} + +void GeometrySettings::restoreDocks(QMainWindow* mw) const { - return getOptional(m_Settings, "geometry/MainWindow_docks_" + name + "_size"); + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; + + std::vector dockInfos; + + // for each dock + for (auto* dock : mw->findChildren()) { + if (auto size=getOptional(m_Settings, dockSettingName(dock))) { + // remember this dock, its size and orientation + dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + } + } + + // the main window must have had time to process the settings from + // readSettings() or it seems to override whatever is set here + // + // some people said a single processEvents() call is enough, but it doesn't + // look like it + QTimer::singleShot(5, [=] { + for (const auto& info : dockInfos) { + mw->resizeDocks({info.d}, {info.size}, info.ori); + } + }); } diff --git a/src/settings.h b/src/settings.h index 072b4066..1b6616a0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -70,25 +70,21 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - void saveVisibility(const QWidget* w); - bool restoreVisibility(QWidget* w, std::optional defaultValue={}) const; - + bool restoreVisibility(QWidget* w, std::optional def={}) const; void saveToolbars(const QMainWindow* w); void restoreToolbars(QMainWindow* w) const; + void saveDocks(const QMainWindow* w); + void restoreDocks(QMainWindow* w) const; + QStringList getModInfoTabOrder() const; void setModInfoTabOrder(const QString& names); - std::optional getMainWindowMonitor() const; void centerOnMainWindowMonitor(QWidget* w); void saveMainWindowMonitor(const QMainWindow* w); - void setDockSize(const QString& name, int size); - - std::optional getDockSize(const QString& name) const; - private: QSettings& m_Settings; bool m_Reset; @@ -206,7 +202,6 @@ public: std::optional getStyleName() const; void setStyleName(const QString& name); - std::optional getSelectedExecutable() const; std::optional getUseProxy() const; std::optional getVersion() const; @@ -222,6 +217,11 @@ public: std::vector> getExecutables() const; void setExecutables(const std::vector>& v); + + std::optional getIndex(QComboBox* cb) const; + void saveIndex(const QComboBox* cb); + void restoreIndex(QComboBox* cb, std::optional def={}) const; + GeometrySettings& geometry(); const GeometrySettings& geometry() const; -- cgit v1.3.1 From d9cb15f1d117b91f0d75c1b7702696f7da93d3d2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 07:42:37 -0400 Subject: put endorsement state and first start in settings --- src/mainwindow.cpp | 54 +++++++++++++++++++++++++++++++++++++----------------- src/settings.cpp | 18 ++++++++++++++++++ src/settings.h | 11 +++++++++++ 3 files changed, 66 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bce92e48..f0e2fe56 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1225,7 +1225,7 @@ void MainWindow::showEvent(QShowEvent *event) hookUpWindowTutorials(); - if (m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { + if (m_OrganizerCore.settings().getFirstStart()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { if (QMessageBox::question(this, tr("Show tutorial?"), @@ -1244,7 +1244,7 @@ void MainWindow::showEvent(QShowEvent *event) QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); } - m_OrganizerCore.settings().directInterface().setValue("first_start", false); + m_OrganizerCore.settings().setFirstStart(false); } m_OrganizerCore.settings().restoreIndex(ui->groupCombo); @@ -5567,22 +5567,42 @@ void MainWindow::modUpdateCheck(std::multimap IDs) void MainWindow::toggleMO2EndorseState() { - if (Settings::instance().endorsementIntegration()) { - ui->actionEndorseMO->setVisible(true); - if (Settings::instance().directInterface().contains("endorse_state")) { - ui->actionEndorseMO->menu()->setEnabled(false); - if (Settings::instance().directInterface().value("endorse_state").toString() == "Endorsed") { - ui->actionEndorseMO->setToolTip(tr("Thank you for endorsing MO2! :)")); - ui->actionEndorseMO->setStatusTip(tr("Thank you for endorsing MO2! :)")); - } else if (Settings::instance().directInterface().value("endorse_state").toString() == "Abstained") { - ui->actionEndorseMO->setToolTip(tr("Please reconsider endorsing MO2 on Nexus!")); - ui->actionEndorseMO->setStatusTip(tr("Please reconsider endorsing MO2 on Nexus!")); - } - } else { - ui->actionEndorseMO->menu()->setEnabled(true); - } - } else + const auto& s = m_OrganizerCore.settings(); + + if (!s.endorsementIntegration()) { ui->actionEndorseMO->setVisible(false); + return; + } + + ui->actionEndorseMO->setVisible(true); + + bool enabled = false; + QString text; + + switch (s.endorsementState()) + { + case EndorsementState::Accepted: + { + text = tr("Thank you for endorsing MO2! :)"); + break; + } + + case EndorsementState::Refused: + { + text = tr("Please reconsider endorsing MO2 on Nexus!"); + break; + } + + case EndorsementState::NoDecision: + { + enabled = true; + break; + } + } + + ui->actionEndorseMO->menu()->setEnabled(enabled); + ui->actionEndorseMO->setToolTip(text); + ui->actionEndorseMO->setStatusTip(text); } void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int) diff --git a/src/settings.cpp b/src/settings.cpp index 40f4dd95..882984f3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -489,6 +489,11 @@ bool Settings::getFirstStart() const return getOptional(m_Settings, "first_start").value_or(true); } +void Settings::setFirstStart(bool b) +{ + m_Settings.setValue("first_start", b); +} + std::optional Settings::getPreviousSeparatorColor() const { const auto c = getOptional(m_Settings, "previousSeparatorColor"); @@ -689,6 +694,19 @@ bool Settings::endorsementIntegration() const return m_Settings.value("Settings/endorsement_integration", true).toBool(); } +EndorsementState Settings::endorsementState() const +{ + const auto v = getOptional(m_Settings, "endorse_state"); + + if (!v) { + return EndorsementState::NoDecision; + } else if (*v == "Abstained") { + return EndorsementState::Refused; + } else { + return EndorsementState::Accepted; + } +} + bool Settings::hideAPICounter() const { return m_Settings.value("Settings/hide_api_counter", false).toBool(); diff --git a/src/settings.h b/src/settings.h index 1b6616a0..167c74fc 100644 --- a/src/settings.h +++ b/src/settings.h @@ -91,6 +91,13 @@ private: }; +enum class EndorsementState +{ + Accepted = 1, + Refused, + NoDecision +}; + /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc @@ -205,7 +212,9 @@ public: std::optional getUseProxy() const; std::optional getVersion() const; + bool getFirstStart() const; + void setFirstStart(bool b); std::optional getPreviousSeparatorColor() const; void setPreviousSeparatorColor(const QColor& c) const; @@ -354,6 +363,8 @@ public: */ bool endorsementIntegration() const; + EndorsementState endorsementState() const; + /** * @return true if the API counter should be hidden */ -- cgit v1.3.1 From 7cc5f220520ab19940462fb6d2f660d8b7e2d600 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 08:27:13 -0400 Subject: put tutorials in the settings finished moving endorsement to settings --- src/mainwindow.cpp | 52 ++++++++++++++++++++++++++++++++++++++++----------- src/settings.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++----- src/settings.h | 8 ++++++++ 3 files changed, 99 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f0e2fe56..6e77f507 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1194,7 +1194,7 @@ void MainWindow::hookUpWindowTutorials() QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//WIN")) { QString windowName = firstLine.mid(6).trimmed(); - if (!m_OrganizerCore.settings().directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { + if (!m_OrganizerCore.settings().isTutorialCompleted(windowName)) { TutorialManager::instance().activateTutorial(windowName, fileName); } } @@ -3017,7 +3017,7 @@ void MainWindow::untrack_clicked() void MainWindow::windowTutorialFinished(const QString &windowName) { - m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); + m_OrganizerCore.settings().setTutorialCompleted(windowName); } void MainWindow::overwriteClosed(int) @@ -5636,7 +5636,9 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData if (Settings::instance().endorsementIntegration()) { if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) { - Settings::instance().directInterface().setValue("endorse_state", result->second.second); + m_OrganizerCore.settings().setEndorsementState( + endorsementStateFromString(result->second.second)); + toggleMO2EndorseState(); } } @@ -5649,7 +5651,9 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData auto iter = sorted.equal_range(gamePlugin->gameNexusName()); for (auto result = iter.first; result != iter.second; ++result) { if (result->second.first == gamePlugin->nexusModOrganizerID()) { - Settings::instance().directInterface().setValue("endorse_state", result->second.second); + m_OrganizerCore.settings().setEndorsementState( + endorsementStateFromString(result->second.second)); + toggleMO2EndorseState(); break; } @@ -5829,15 +5833,41 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultData, int) { - QMap results = resultData.toMap(); - if (results["status"].toString().compare("Endorsed") == 0) { - QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!")); - Settings::instance().directInterface().setValue("endorse_state", "Endorsed"); - } else if (results["status"].toString().compare("Abstained") == 0) { - QMessageBox::information(this, tr("Okay."), tr("This mod will not be endorsed and will no longer ask you to endorse.")); - Settings::instance().directInterface().setValue("endorse_state", "Abstained"); + const QMap results = resultData.toMap(); + + auto itor = results.find("status"); + if (itor == results.end()) { + log::error("endorsement response has no status"); + return; + } + + const auto s = endorsementStateFromString(itor->toString()); + + switch (s) + { + case EndorsementState::Accepted: + { + QMessageBox::information(this, tr("Thank you!"), tr("Thank you for your endorsement!")); + break; + } + + case EndorsementState::Refused: + { + // don't spam message boxes if the user doesn't want to endorse + log::info("Mod Organizer will not be endorsed and will no longer ask you to endorse."); + break; + } + + case EndorsementState::NoDecision: + { + log::error("bad status '{}' in endorsement response", itor->toString()); + return; + } } + + m_OrganizerCore.settings().setEndorsementState(s); toggleMO2EndorseState(); + if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), this, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)))) { log::error("failed to disconnect endorsement slot"); diff --git a/src/settings.cpp b/src/settings.cpp index 882984f3..af32a082 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -39,6 +39,34 @@ std::optional getOptional( } +EndorsementState endorsementStateFromString(const QString& s) +{ + if (s == "Endorsed") { + return EndorsementState::Accepted; + } else if (s == "Abstained") { + return EndorsementState::Refused; + } else { + return EndorsementState::NoDecision; + } +} + +QString toString(EndorsementState s) +{ + switch (s) + { + case EndorsementState::Accepted: + return "Endorsed"; + + case EndorsementState::Refused: + return "Abstained"; + + case EndorsementState::NoDecision: // fall-through + default: + return {}; + } +} + + QString widgetNameWithTopLevel(const QWidget* widget) { QStringList components; @@ -697,13 +725,17 @@ bool Settings::endorsementIntegration() const EndorsementState Settings::endorsementState() const { const auto v = getOptional(m_Settings, "endorse_state"); + return endorsementStateFromString(v.value_or("")); +} - if (!v) { - return EndorsementState::NoDecision; - } else if (*v == "Abstained") { - return EndorsementState::Refused; +void Settings::setEndorsementState(EndorsementState s) +{ + const auto v = toString(s); + + if (v.isEmpty()) { + m_Settings.remove("endorse_state"); } else { - return EndorsementState::Accepted; + m_Settings.setValue("endorse_state", v); } } @@ -935,6 +967,19 @@ void Settings::setExecutables(const std::vector>& v) m_Settings.endArray(); } +bool Settings::isTutorialCompleted(const QString& windowName) const +{ + const auto v = getOptional( + m_Settings, "CompletedWindowTutorials/" + windowName); + + return v.value_or(false); +} + +void Settings::setTutorialCompleted(const QString& windowName, bool b) +{ + m_Settings.setValue("CompletedWindowTutorials/" + windowName, true); +} + std::optional Settings::getIndex(QComboBox* cb) const { return getOptional(m_Settings, indexSettingName(cb)); diff --git a/src/settings.h b/src/settings.h index 167c74fc..5044af98 100644 --- a/src/settings.h +++ b/src/settings.h @@ -98,6 +98,10 @@ enum class EndorsementState NoDecision }; +EndorsementState endorsementStateFromString(const QString& s); +QString toString(EndorsementState s); + + /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc @@ -226,6 +230,8 @@ public: std::vector> getExecutables() const; void setExecutables(const std::vector>& v); + bool isTutorialCompleted(const QString& windowName) const; + void setTutorialCompleted(const QString& windowName, bool b=true); std::optional getIndex(QComboBox* cb) const; void saveIndex(const QComboBox* cb); @@ -364,6 +370,8 @@ public: bool endorsementIntegration() const; EndorsementState endorsementState() const; + void setEndorsementState(EndorsementState s); + void setEndorsementState(const QString& s); /** * @return true if the API counter should be hidden -- cgit v1.3.1 From dfa15218f33ad06a6e868e8e5f1022026b6530a9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 12:42:27 -0400 Subject: passes callbacks to QuestionBoxMemory so it doesn't access the ini directly fixed selected executable being empty after closing the edit dialog put backup_install inside Settings --- src/installationmanager.cpp | 13 ++++++--- src/mainwindow.cpp | 6 ++-- src/organizercore.cpp | 2 -- src/settings.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++ src/settings.h | 16 ++++++++++ src/settingsdialoggeneral.cpp | 2 +- 6 files changed, 97 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 89d0079f..522489e4 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -476,13 +476,18 @@ QString InstallationManager::generateBackupName(const QString &directoryName) co bool InstallationManager::testOverwrite(GuessedValue &modName, bool *merge) const { QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); + while (QDir(targetDirectory).exists()) { Settings &settings(Settings::instance()); - bool backup = settings.directInterface().value("backup_install", false).toBool(); - QueryOverwriteDialog overwriteDialog(m_ParentWidget, - backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO); + + const bool backup = settings.keepBackupOnInstall(); + QueryOverwriteDialog overwriteDialog( + m_ParentWidget, + backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO); + if (overwriteDialog.exec()) { - settings.directInterface().setValue("backup_install", overwriteDialog.backup()); + settings.setKeepBackupOnInstall(overwriteDialog.backup()); + if (overwriteDialog.backup()) { QString backupDirectory = generateBackupName(targetDirectory); if (!copyDir(targetDirectory, backupDirectory, false)) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6e77f507..2ce6f9d9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2340,8 +2340,6 @@ bool MainWindow::modifyExecutablesDialog() bool result = false; try { - const auto oldExecutables = *m_OrganizerCore.executablesList(); - EditExecutablesDialog dialog(m_OrganizerCore, this); result = (dialog.exec() == QDialog::Accepted); @@ -2361,7 +2359,9 @@ void MainWindow::on_executablesListBox_currentIndexChanged(int index) return; } - const int previousIndex = m_OldExecutableIndex; + const int previousIndex = + (m_OldExecutableIndex > 0 ? m_OldExecutableIndex : 1); + m_OldExecutableIndex = index; if (index == 0) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a2b0fd69..233a631e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -280,8 +280,6 @@ OrganizerCore::OrganizerCore(Settings &settings) NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); - MOBase::QuestionBoxMemory::init(m_Settings.directInterface().fileName()); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); diff --git a/src/settings.cpp b/src/settings.cpp index af32a082..9001ac65 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -151,10 +151,16 @@ Settings::Settings(const QString& path) } else { s_Instance = this; } + + MOBase::QuestionBoxMemory::setCallbacks( + [this](auto&& w, auto&& f){ return getQuestionButton(w, f); }, + [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, + [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); } Settings::~Settings() { + MOBase::QuestionBoxMemory::setCallbacks({}, {}, {}); s_Instance = nullptr; } @@ -980,6 +986,68 @@ void Settings::setTutorialCompleted(const QString& windowName, bool b) m_Settings.setValue("CompletedWindowTutorials/" + windowName, true); } +bool Settings::keepBackupOnInstall() const +{ + return getOptional(m_Settings, "backup_install").value_or(false); +} + +void Settings::setKeepBackupOnInstall(bool b) +{ + m_Settings.setValue("backup_install", b); +} + +QuestionBoxMemory::Button Settings::getQuestionButton( + const QString& windowName, const QString& filename) const +{ + const QString windowSetting("DialogChoices/" + windowName); + + if (!filename.isEmpty()) { + const auto fileSetting = windowSetting + "/" + filename; + + if (auto v=getOptional(m_Settings, fileSetting)) { + return static_cast(*v); + } + } + + if (auto v=getOptional(m_Settings, windowSetting)) { + return static_cast(*v); + } + + return QuestionBoxMemory::NoButton; +} + +void Settings::setQuestionWindowButton( + const QString& windowName, QuestionBoxMemory::Button button) +{ + const QString settingName("DialogChoices/" + windowName); + + if (button == QuestionBoxMemory::NoButton) { + m_Settings.remove(settingName); + } else { + m_Settings.setValue(settingName, static_cast(button)); + } +} + +void Settings::setQuestionFileButton( + const QString& windowName, const QString& filename, + QuestionBoxMemory::Button button) +{ + const QString settingName("DialogChoices/" + windowName + "/" + filename); + + if (button == QuestionBoxMemory::NoButton) { + m_Settings.remove(settingName); + } else { + m_Settings.setValue(settingName, static_cast(button)); + } +} + +void Settings::resetQuestionButtons() +{ + m_Settings.beginGroup("DialogChoices"); + m_Settings.remove(""); + m_Settings.endGroup(); +} + std::optional Settings::getIndex(QComboBox* cb) const { return getOptional(m_Settings, indexSettingName(cb)); diff --git a/src/settings.h b/src/settings.h index 5044af98..d46c358c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGS_H #include "loadmechanism.h" +#include #include namespace MOBase { @@ -233,6 +234,21 @@ public: bool isTutorialCompleted(const QString& windowName) const; void setTutorialCompleted(const QString& windowName, bool b=true); + bool keepBackupOnInstall() const; + void setKeepBackupOnInstall(bool b); + + MOBase::QuestionBoxMemory::Button getQuestionButton( + const QString& windowName, const QString& filename) const; + + void setQuestionWindowButton( + const QString& windowName, MOBase::QuestionBoxMemory::Button button); + + void setQuestionFileButton( + const QString& windowName, const QString& filename, + MOBase::QuestionBoxMemory::Button choice); + + void resetQuestionButtons(); + std::optional getIndex(QComboBox* cb) const; void saveIndex(const QComboBox* cb); void restoreIndex(QComboBox* cb, std::optional def={}) const; diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 324dc4f4..fda50220 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -141,7 +141,7 @@ void GeneralSettingsTab::addStyles() void GeneralSettingsTab::resetDialogs() { - QuestionBoxMemory::resetDialogs(); + m_parent->resetQuestionButtons(); } void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) -- cgit v1.3.1 From 2ffad7edf2946e66585a67c4ab58c0522cd8e412 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 17 Aug 2019 13:20:53 -0400 Subject: made member variables in SettingsTab private, added accessors SettingsDialog now uses GeometrySaver --- src/loglist.h | 2 + src/mainwindow.cpp | 2 +- src/organizercore.cpp | 9 +--- src/settingsdialog.cpp | 40 ++++++++-------- src/settingsdialog.h | 18 ++++--- src/settingsdialogdiagnostics.cpp | 16 +++---- src/settingsdialogdiagnostics.h | 2 +- src/settingsdialoggeneral.cpp | 98 +++++++++++++++++++-------------------- src/settingsdialoggeneral.h | 2 +- src/settingsdialognexus.cpp | 66 +++++++++++++------------- src/settingsdialognexus.h | 2 +- src/settingsdialogpaths.cpp | 48 +++++++++---------- src/settingsdialogpaths.h | 3 +- src/settingsdialogplugins.cpp | 26 +++++------ src/settingsdialogplugins.h | 2 +- src/settingsdialogsteam.cpp | 8 ++-- src/settingsdialogsteam.h | 5 +- src/settingsdialogworkarounds.cpp | 46 +++++++++--------- src/settingsdialogworkarounds.h | 3 +- 19 files changed, 196 insertions(+), 202 deletions(-) (limited to 'src') diff --git a/src/loglist.h b/src/loglist.h index 0b25dfd1..36671be4 100644 --- a/src/loglist.h +++ b/src/loglist.h @@ -61,6 +61,8 @@ private: class LogList : public QTreeView { + Q_OBJECT; + public: LogList(QWidget* parent=nullptr); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2ce6f9d9..f1a2047f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5018,7 +5018,7 @@ void MainWindow::on_actionSettings_triggered() DownloadManager *dlManager = m_OrganizerCore.downloadManager(); - SettingsDialog dialog(&m_PluginContainer, &settings, this); + SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); if (oldManagedGameDirectory != settings.getManagedGameDirectory()) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 233a631e..73d0abac 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -745,16 +745,11 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->deactivateInvalidation(); } + m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList)), this, SLOT(modStatusChanged(QList))); refreshDirectoryStructure(); - - //This line is not actually needed and was only added to allow some - //outside detection of Mo2 profile change. (like BaobobMiller utility) - if (m_CurrentProfile != nullptr) { - settings().directInterface().setValue("selected_profile", - m_CurrentProfile->name().toUtf8().constData()); - } } MOBase::IModRepositoryBridge *OrganizerCore::createNexusBridge() const diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index d74507c9..097dafc8 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -29,7 +29,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; -SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) +SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) @@ -45,18 +45,13 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* setti m_tabs.push_back(std::unique_ptr(new SteamSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new PluginsSettingsTab(settings, *this))); m_tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); - - auto& qsettings = settings->directInterface(); - - QString key = QString("geometry/%1").arg(objectName()); - if (qsettings.contains(key)) { - restoreGeometry(qsettings.value(key).toByteArray()); - } } int SettingsDialog::exec() { - auto& qsettings = m_settings->directInterface(); + GeometrySaver gs(m_settings, this); + + auto& qsettings = m_settings.directInterface(); auto ret = TutorableDialog::exec(); if (ret == QDialog::Accepted) { @@ -92,9 +87,6 @@ int SettingsDialog::exec() qsettings.endGroup(); } - QString key = QString("geometry/%1").arg(objectName()); - qsettings.setValue(key, saveGeometry()); - // These changes happen regardless of accepted or rejected bool restartNeeded = false; if (getApiKeyChanged()) { @@ -158,18 +150,24 @@ bool SettingsDialog::getApiKeyChanged() } -SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : m_parent(m_parent) - , m_Settings(m_parent->directInterface()) - , m_dialog(m_dialog) - , ui(m_dialog.ui) +SettingsTab::SettingsTab(Settings& s, SettingsDialog& d) + : ui(d.ui), m_settings(s), m_qsettings(s.directInterface()), m_dialog(d) { } -SettingsTab::~SettingsTab() -{} +SettingsTab::~SettingsTab() = default; + +Settings& SettingsTab::settings() +{ + return m_settings; +} + +QSettings& SettingsTab::qsettings() +{ + return m_qsettings; +} -QWidget* SettingsTab::parentWidget() +SettingsDialog& SettingsTab::dialog() { - return &m_dialog; + return m_dialog; } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index efc4a095..0aad8863 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -31,19 +31,23 @@ namespace Ui { class SettingsDialog; } class SettingsTab { public: - SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + SettingsTab(Settings& settings, SettingsDialog& m_dialog); virtual ~SettingsTab(); virtual void update() = 0; virtual void closing() {} protected: - Settings *m_parent; - QSettings &m_Settings; - SettingsDialog &m_dialog; Ui::SettingsDialog* ui; - QWidget* parentWidget(); + Settings& settings(); + QSettings& qsettings(); + SettingsDialog& dialog(); + +private: + Settings& m_settings; + QSettings& m_qsettings; + SettingsDialog& m_dialog; }; @@ -58,7 +62,7 @@ class SettingsDialog : public MOBase::TutorableDialog public: explicit SettingsDialog( - PluginContainer *pluginContainer, Settings* settings, QWidget *parent = 0); + PluginContainer *pluginContainer, Settings& settings, QWidget *parent = 0); ~SettingsDialog(); @@ -82,7 +86,7 @@ public: bool getApiKeyChanged(); private: - Settings* m_settings; + Settings& m_settings; std::vector> m_tabs; }; diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index daf81d5c..227d1dfa 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -6,12 +6,12 @@ using namespace MOBase; -DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { setLevelsBox(); - ui->dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); - ui->dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); + ui->dumpsTypeBox->setCurrentIndex(settings().crashDumpsType()); + ui->dumpsMaxEdit->setValue(settings().crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); ui->diagnosticsExplainedLabel->setText( @@ -33,7 +33,7 @@ void DiagnosticsSettingsTab::setLevelsBox() ui->logLevelBox->addItem(QObject::tr("Error"), log::Error); for (int i=0; ilogLevelBox->count(); ++i) { - if (ui->logLevelBox->itemData(i) == m_parent->logLevel()) { + if (ui->logLevelBox->itemData(i) == settings().logLevel()) { ui->logLevelBox->setCurrentIndex(i); break; } @@ -42,7 +42,7 @@ void DiagnosticsSettingsTab::setLevelsBox() void DiagnosticsSettingsTab::update() { - m_Settings.setValue("Settings/log_level", ui->logLevelBox->currentData().toInt()); - m_Settings.setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); - m_Settings.setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); + qsettings().setValue("Settings/log_level", ui->logLevelBox->currentData().toInt()); + qsettings().setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); + qsettings().setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h index 4c1805e2..f20413f8 100644 --- a/src/settingsdialogdiagnostics.h +++ b/src/settingsdialogdiagnostics.h @@ -7,7 +7,7 @@ class DiagnosticsSettingsTab : public SettingsTab { public: - DiagnosticsSettingsTab(Settings *parent, SettingsDialog &dialog); + DiagnosticsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index fda50220..35012db7 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -6,12 +6,12 @@ using MOBase::QuestionBoxMemory; -GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { addLanguages(); { - QString languageCode = m_parent->language(); + QString languageCode = settings().language(); int currentID = ui->languageBox->findData(languageCode); // I made a mess. :( Most languages are stored with only the iso country // code (2 characters like "de") but chinese @@ -28,31 +28,31 @@ GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dia addStyles(); { int currentID = ui->styleBox->findData( - m_Settings.value("Settings/style", "").toString()); + qsettings().value("Settings/style", "").toString()); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); } } //version with stylesheet - setButtonColor(ui->overwritingBtn, m_parent->modlistOverwritingLooseColor()); - setButtonColor(ui->overwrittenBtn, m_parent->modlistOverwrittenLooseColor()); - setButtonColor(ui->overwritingArchiveBtn, m_parent->modlistOverwritingArchiveColor()); - setButtonColor(ui->overwrittenArchiveBtn, m_parent->modlistOverwrittenArchiveColor()); - setButtonColor(ui->containsBtn, m_parent->modlistContainsPluginColor()); - setButtonColor(ui->containedBtn, m_parent->pluginListContainedColor()); - - setOverwritingColor(m_parent->modlistOverwritingLooseColor()); - setOverwrittenColor(m_parent->modlistOverwrittenLooseColor()); - setOverwritingArchiveColor(m_parent->modlistOverwritingArchiveColor()); - setOverwrittenArchiveColor(m_parent->modlistOverwrittenArchiveColor()); - setContainsColor(m_parent->modlistContainsPluginColor()); - setContainedColor(m_parent->pluginListContainedColor()); - - ui->compactBox->setChecked(m_parent->compactDownloads()); - ui->showMetaBox->setChecked(m_parent->metaDownloads()); - ui->usePrereleaseBox->setChecked(m_parent->usePrereleases()); - ui->colorSeparatorsBox->setChecked(m_parent->colorSeparatorScrollbar()); + setButtonColor(ui->overwritingBtn, settings().modlistOverwritingLooseColor()); + setButtonColor(ui->overwrittenBtn, settings().modlistOverwrittenLooseColor()); + setButtonColor(ui->overwritingArchiveBtn, settings().modlistOverwritingArchiveColor()); + setButtonColor(ui->overwrittenArchiveBtn, settings().modlistOverwrittenArchiveColor()); + setButtonColor(ui->containsBtn, settings().modlistContainsPluginColor()); + setButtonColor(ui->containedBtn, settings().pluginListContainedColor()); + + setOverwritingColor(settings().modlistOverwritingLooseColor()); + setOverwrittenColor(settings().modlistOverwrittenLooseColor()); + setOverwritingArchiveColor(settings().modlistOverwritingArchiveColor()); + setOverwrittenArchiveColor(settings().modlistOverwrittenArchiveColor()); + setContainsColor(settings().modlistContainsPluginColor()); + setContainedColor(settings().pluginListContainedColor()); + + ui->compactBox->setChecked(settings().compactDownloads()); + ui->showMetaBox->setChecked(settings().metaDownloads()); + ui->usePrereleaseBox->setChecked(settings().usePrereleases()); + ui->colorSeparatorsBox->setChecked(settings().colorSeparatorScrollbar()); QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); }); QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); }); @@ -67,30 +67,30 @@ GeneralSettingsTab::GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dia void GeneralSettingsTab::update() { - QString oldLanguage = m_parent->language(); + QString oldLanguage = settings().language(); QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { - m_Settings.setValue("Settings/language", newLanguage); - emit m_parent->languageChanged(newLanguage); + qsettings().setValue("Settings/language", newLanguage); + emit settings().languageChanged(newLanguage); } - QString oldStyle = m_Settings.value("Settings/style", "").toString(); + QString oldStyle = qsettings().value("Settings/style", "").toString(); QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - m_Settings.setValue("Settings/style", newStyle); - emit m_parent->styleChanged(newStyle); - } - - m_Settings.setValue("Settings/overwritingLooseFilesColor", getOverwritingColor()); - m_Settings.setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor()); - m_Settings.setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor()); - m_Settings.setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor()); - m_Settings.setValue("Settings/containsPluginColor", getContainsColor()); - m_Settings.setValue("Settings/containedColor", getContainedColor()); - m_Settings.setValue("Settings/compact_downloads", ui->compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", ui->showMetaBox->isChecked()); - m_Settings.setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked()); - m_Settings.setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); + qsettings().setValue("Settings/style", newStyle); + emit settings().styleChanged(newStyle); + } + + qsettings().setValue("Settings/overwritingLooseFilesColor", getOverwritingColor()); + qsettings().setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor()); + qsettings().setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor()); + qsettings().setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor()); + qsettings().setValue("Settings/containsPluginColor", getContainsColor()); + qsettings().setValue("Settings/containedColor", getContainedColor()); + qsettings().setValue("Settings/compact_downloads", ui->compactBox->isChecked()); + qsettings().setValue("Settings/meta_downloads", ui->showMetaBox->isChecked()); + qsettings().setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked()); + qsettings().setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() @@ -141,7 +141,7 @@ void GeneralSettingsTab::addStyles() void GeneralSettingsTab::resetDialogs() { - m_parent->resetQuestionButtons(); + settings().resetQuestionButtons(); } void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) @@ -163,7 +163,7 @@ void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color void GeneralSettingsTab::on_containsBtn_clicked() { - QColor result = QColorDialog::getColor(m_ContainsColor, parentWidget(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_ContainsColor, &dialog(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_ContainsColor = result; setButtonColor(ui->containsBtn, result); @@ -172,7 +172,7 @@ void GeneralSettingsTab::on_containsBtn_clicked() void GeneralSettingsTab::on_containedBtn_clicked() { - QColor result = QColorDialog::getColor(m_ContainedColor, parentWidget(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_ContainedColor, &dialog(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_ContainedColor = result; setButtonColor(ui->containedBtn, result); @@ -181,7 +181,7 @@ void GeneralSettingsTab::on_containedBtn_clicked() void GeneralSettingsTab::on_overwrittenBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwrittenColor, parentWidget(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwrittenColor, &dialog(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwrittenColor = result; setButtonColor(ui->overwrittenBtn, result); @@ -190,7 +190,7 @@ void GeneralSettingsTab::on_overwrittenBtn_clicked() void GeneralSettingsTab::on_overwritingBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwritingColor, parentWidget(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwritingColor, &dialog(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwritingColor = result; setButtonColor(ui->overwritingBtn, result); @@ -199,7 +199,7 @@ void GeneralSettingsTab::on_overwritingBtn_clicked() void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, parentWidget(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, &dialog(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwrittenArchiveColor = result; setButtonColor(ui->overwrittenArchiveBtn, result); @@ -208,7 +208,7 @@ void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked() void GeneralSettingsTab::on_overwritingArchiveBtn_clicked() { - QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, parentWidget(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); + QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, &dialog(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); if (result.isValid()) { m_OverwritingArchiveColor = result; setButtonColor(ui->overwritingArchiveBtn, result); @@ -234,7 +234,7 @@ void GeneralSettingsTab::on_resetColorsBtn_clicked() void GeneralSettingsTab::on_resetDialogsButton_clicked() { - if (QMessageBox::question(parentWidget(), QObject::tr("Confirm?"), + if (QMessageBox::question(&dialog(), QObject::tr("Confirm?"), QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { resetDialogs(); @@ -243,7 +243,7 @@ void GeneralSettingsTab::on_resetDialogsButton_clicked() void GeneralSettingsTab::on_categoriesBtn_clicked() { - CategoriesDialog dialog(parentWidget()); + CategoriesDialog dialog(&dialog()); if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); } diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index c7fcae36..2038ba31 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -7,7 +7,7 @@ class GeneralSettingsTab : public SettingsTab { public: - GeneralSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + GeneralSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 575f54d0..b1964069 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -69,18 +69,18 @@ private: }; -NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) - : SettingsTab(parent, dialog) +NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { - ui->offlineBox->setChecked(parent->offlineMode()); - ui->proxyBox->setChecked(parent->useProxy()); - ui->endorsementBox->setChecked(parent->endorsementIntegration()); - ui->hideAPICounterBox->setChecked(parent->hideAPICounter()); + ui->offlineBox->setChecked(settings().offlineMode()); + ui->proxyBox->setChecked(settings().useProxy()); + ui->endorsementBox->setChecked(settings().endorsementIntegration()); + ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); // display server preferences - m_Settings.beginGroup("Servers"); - for (const QString &key : m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); + qsettings().beginGroup("Servers"); + for (const QString &key : qsettings().childKeys()) { + QVariantMap val = qsettings().value(key).toMap(); QString descriptor = key; if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { descriptor += QStringLiteral(" (automatic)"); @@ -101,7 +101,7 @@ NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) } ui->preferredServersList->sortItems(Qt::DescendingOrder); } - m_Settings.endGroup(); + qsettings().endGroup(); QObject::connect(ui->nexusConnect, &QPushButton::clicked, [&]{ on_nexusConnect_clicked(); }); QObject::connect(ui->nexusManualKey, &QPushButton::clicked, [&]{ on_nexusManualKey_clicked(); }); @@ -114,27 +114,27 @@ NexusSettingsTab::NexusSettingsTab(Settings *parent, SettingsDialog &dialog) void NexusSettingsTab::update() { - m_Settings.setValue("Settings/offline_mode", ui->offlineBox->isChecked()); - m_Settings.setValue("Settings/use_proxy", ui->proxyBox->isChecked()); - m_Settings.setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked()); - m_Settings.setValue("Settings/hide_api_counter", ui->hideAPICounterBox->isChecked()); + qsettings().setValue("Settings/offline_mode", ui->offlineBox->isChecked()); + qsettings().setValue("Settings/use_proxy", ui->proxyBox->isChecked()); + qsettings().setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked()); + qsettings().setValue("Settings/hide_api_counter", ui->hideAPICounterBox->isChecked()); // store server preference - m_Settings.beginGroup("Servers"); + qsettings().beginGroup("Servers"); for (int i = 0; i < ui->knownServersList->count(); ++i) { QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); + QVariantMap val = qsettings().value(key).toMap(); val["preferred"] = 0; - m_Settings.setValue(key, val); + qsettings().setValue(key, val); } int count = ui->preferredServersList->count(); for (int i = 0; i < count; ++i) { QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); + QVariantMap val = qsettings().value(key).toMap(); val["preferred"] = count - i; - m_Settings.setValue(key, val); + qsettings().setValue(key, val); } - m_Settings.endGroup(); + qsettings().endGroup(); } void NexusSettingsTab::on_nexusConnect_clicked() @@ -168,12 +168,12 @@ void NexusSettingsTab::on_nexusManualKey_clicked() return; } - NexusManualKeyDialog dialog(parentWidget()); - if (dialog.exec() != QDialog::Accepted) { + NexusManualKeyDialog d(&dialog()); + if (d.exec() != QDialog::Accepted) { return; } - const auto key = dialog.key(); + const auto key = d.key(); if (key.isEmpty()) { clearKey(); return; @@ -193,7 +193,7 @@ void NexusSettingsTab::on_nexusDisconnect_clicked() void NexusSettingsTab::on_clearCacheButton_clicked() { QDir(Settings::instance().getCacheDirectory()).removeRecursively(); - NexusInterface::instance(m_dialog.m_PluginContainer)->clearCache(); + NexusInterface::instance(dialog().m_PluginContainer)->clearCache(); } void NexusSettingsTab::on_associateButton_clicked() @@ -205,7 +205,7 @@ void NexusSettingsTab::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager())); + *NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager())); m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ onValidatorStateChanged(s, e); @@ -261,7 +261,7 @@ void NexusSettingsTab::onValidatorStateChanged( void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user) { - NexusInterface::instance(m_dialog.m_PluginContainer)->setUserAccount(user); + NexusInterface::instance(dialog().m_PluginContainer)->setUserAccount(user); if (!user.apiKey().isEmpty()) { if (setKey(user.apiKey())) { @@ -278,18 +278,18 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - m_dialog.m_keyChanged = true; - const bool ret = m_parent->setNexusApiKey(key); + dialog().m_keyChanged = true; + const bool ret = settings().setNexusApiKey(key); updateNexusState(); return ret; } bool NexusSettingsTab::clearKey() { - m_dialog.m_keyChanged = true; - const auto ret = m_parent->clearNexusApiKey(); + dialog().m_keyChanged = true; + const auto ret = settings().clearNexusApiKey(); - NexusInterface::instance(m_dialog.m_PluginContainer)->getAccessManager()->clearApiKey(); + NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey(); updateNexusState(); return ret; @@ -319,7 +319,7 @@ void NexusSettingsTab::updateNexusButtons() ui->nexusManualKey->setText(QObject::tr("Cancel")); ui->nexusManualKey->setEnabled(true); } - else if (m_parent->hasNexusApiKey()) { + else if (settings().hasNexusApiKey()) { // api key is present ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); ui->nexusConnect->setEnabled(false); @@ -338,7 +338,7 @@ void NexusSettingsTab::updateNexusButtons() void NexusSettingsTab::updateNexusData() { - const auto user = NexusInterface::instance(m_dialog.m_PluginContainer) + const auto user = NexusInterface::instance(dialog().m_PluginContainer) ->getAPIUserAccount(); if (user.isValid()) { diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index cca2e1b5..89a6618f 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -8,7 +8,7 @@ class NexusSettingsTab : public SettingsTab { public: - NexusSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + NexusSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); private: diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 6e8fe994..290ceeb3 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -3,19 +3,19 @@ #include "appconfig.h" #include -PathsSettingsTab::PathsSettingsTab(Settings *parent, SettingsDialog &dialog) - : SettingsTab(parent, dialog) +PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { - ui->baseDirEdit->setText(m_parent->getBaseDirectory()); - ui->managedGameDirEdit->setText(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName())); - QString basePath = parent->getBaseDirectory(); + ui->baseDirEdit->setText(settings().getBaseDirectory()); + ui->managedGameDirEdit->setText(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); + QString basePath = settings().getBaseDirectory(); QDir baseDir(basePath); for (const auto &dir : { - std::make_pair(ui->downloadDirEdit, m_parent->getDownloadDirectory(false)), - std::make_pair(ui->modDirEdit, m_parent->getModDirectory(false)), - std::make_pair(ui->cacheDirEdit, m_parent->getCacheDirectory(false)), - std::make_pair(ui->profilesDirEdit, m_parent->getProfileDirectory(false)), - std::make_pair(ui->overwriteDirEdit, m_parent->getOverwriteDirectory(false)) + std::make_pair(ui->downloadDirEdit, settings().getDownloadDirectory(false)), + std::make_pair(ui->modDirEdit, settings().getModDirectory(false)), + std::make_pair(ui->cacheDirEdit, settings().getCacheDirectory(false)), + std::make_pair(ui->profilesDirEdit, settings().getProfileDirectory(false)), + std::make_pair(ui->overwriteDirEdit, settings().getOverwriteDirectory(false)) }) { QString storePath = baseDir.relativeFilePath(dir.second); storePath = dir.second; @@ -42,7 +42,7 @@ void PathsSettingsTab::update() { typedef std::tuple Directory; - QString basePath = m_parent->getBaseDirectory(); + QString basePath = settings().getBaseDirectory(); for (const Directory &dir :{ Directory{ui->downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, @@ -71,30 +71,30 @@ void PathsSettingsTab::update() if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - m_Settings.setValue(settingsKey, path); + qsettings().setValue(settingsKey, path); } else { - m_Settings.remove(settingsKey); + qsettings().remove(settingsKey); } } if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { - m_Settings.setValue("Settings/base_directory", ui->baseDirEdit->text()); + qsettings().setValue("Settings/base_directory", ui->baseDirEdit->text()); } else { - m_Settings.remove("Settings/base_directory"); + qsettings().remove("Settings/base_directory"); } - QFileInfo oldGameExe(m_parent->gamePlugin()->gameDirectory().absoluteFilePath(m_parent->gamePlugin()->binaryName())); + QFileInfo oldGameExe(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); QFileInfo newGameExe(ui->managedGameDirEdit->text()); if (oldGameExe != newGameExe) { - m_Settings.setValue("gamePath", newGameExe.absolutePath()); + qsettings().setValue("gamePath", newGameExe.absolutePath()); } } void PathsSettingsTab::on_browseBaseDirBtn_clicked() { QString temp = QFileDialog::getExistingDirectory( - parentWidget(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); + &dialog(), QObject::tr("Select base directory"), ui->baseDirEdit->text()); if (!temp.isEmpty()) { ui->baseDirEdit->setText(temp); } @@ -105,7 +105,7 @@ void PathsSettingsTab::on_browseDownloadDirBtn_clicked() QString searchPath = ui->downloadDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select download directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select download directory"), searchPath); if (!temp.isEmpty()) { ui->downloadDirEdit->setText(temp); } @@ -116,7 +116,7 @@ void PathsSettingsTab::on_browseModDirBtn_clicked() QString searchPath = ui->modDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select mod directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select mod directory"), searchPath); if (!temp.isEmpty()) { ui->modDirEdit->setText(temp); } @@ -127,7 +127,7 @@ void PathsSettingsTab::on_browseCacheDirBtn_clicked() QString searchPath = ui->cacheDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select cache directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select cache directory"), searchPath); if (!temp.isEmpty()) { ui->cacheDirEdit->setText(temp); } @@ -138,7 +138,7 @@ void PathsSettingsTab::on_browseProfilesDirBtn_clicked() QString searchPath = ui->profilesDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select profiles directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select profiles directory"), searchPath); if (!temp.isEmpty()) { ui->profilesDirEdit->setText(temp); } @@ -149,7 +149,7 @@ void PathsSettingsTab::on_browseOverwriteDirBtn_clicked() QString searchPath = ui->overwriteDirEdit->text(); searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(parentWidget(), QObject::tr("Select overwrite directory"), searchPath); + QString temp = QFileDialog::getExistingDirectory(&dialog(), QObject::tr("Select overwrite directory"), searchPath); if (!temp.isEmpty()) { ui->overwriteDirEdit->setText(temp); } @@ -159,7 +159,7 @@ void PathsSettingsTab::on_browseGameDirBtn_clicked() { QFileInfo oldGameExe(ui->managedGameDirEdit->text()); - QString temp = QFileDialog::getOpenFileName(parentWidget(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName()); + QString temp = QFileDialog::getOpenFileName(&dialog(), QObject::tr("Select game executable"), oldGameExe.absolutePath(), oldGameExe.fileName()); if (!temp.isEmpty()) { ui->managedGameDirEdit->setText(temp); } diff --git a/src/settingsdialogpaths.h b/src/settingsdialogpaths.h index f661b624..a2073188 100644 --- a/src/settingsdialogpaths.h +++ b/src/settingsdialogpaths.h @@ -7,8 +7,7 @@ class PathsSettingsTab : public SettingsTab { public: - PathsSettingsTab(Settings *parent, SettingsDialog &dialog); - + PathsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); private: diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 53b28fcc..329ba301 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -5,26 +5,26 @@ using MOBase::IPlugin; -PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +PluginsSettingsTab::PluginsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}"); // display plugin settings QSet handledNames; - for (IPlugin *plugin : m_parent->plugins()) { + for (IPlugin *plugin : settings().plugins()) { if (handledNames.contains(plugin->name())) continue; QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), ui->pluginsList); listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, m_parent->m_PluginSettings[plugin->name()]); - listItem->setData(Qt::UserRole + 2, m_parent->m_PluginDescriptions[plugin->name()]); + listItem->setData(Qt::UserRole + 1, settings().m_PluginSettings[plugin->name()]); + listItem->setData(Qt::UserRole + 2, settings().m_PluginDescriptions[plugin->name()]); ui->pluginsList->addItem(listItem); handledNames.insert(plugin->name()); } // display plugin blacklist - for (const QString &pluginName : m_parent->m_PluginBlacklist) { + for (const QString &pluginName : settings().m_PluginBlacklist) { ui->pluginBlacklist->addItem(pluginName); } @@ -34,7 +34,7 @@ PluginsSettingsTab::PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dia QShortcut *delShortcut = new QShortcut( QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - QObject::connect(delShortcut, &QShortcut::activated, parentWidget(), [&]{ deleteBlacklistItem(); }); + QObject::connect(delShortcut, &QShortcut::activated, &dialog(), [&]{ deleteBlacklistItem(); }); } void PluginsSettingsTab::update() @@ -42,21 +42,21 @@ void PluginsSettingsTab::update() // transfer plugin settings to in-memory structure for (int i = 0; i < ui->pluginsList->count(); ++i) { QListWidgetItem *item = ui->pluginsList->item(i); - m_parent->m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); + settings().m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); } // store plugin settings on disc - for (auto iterPlugins = m_parent->m_PluginSettings.begin(); iterPlugins != m_parent->m_PluginSettings.end(); ++iterPlugins) { + for (auto iterPlugins = settings().m_PluginSettings.begin(); iterPlugins != settings().m_PluginSettings.end(); ++iterPlugins) { for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { - m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); + qsettings().setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); } } // store plugin blacklist - m_parent->m_PluginBlacklist.clear(); + settings().m_PluginBlacklist.clear(); for (QListWidgetItem *item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { - m_parent->m_PluginBlacklist.insert(item->text()); + settings().m_PluginBlacklist.insert(item->text()); } - m_parent->writePluginBlacklist(); + settings().writePluginBlacklist(); } void PluginsSettingsTab::closing() diff --git a/src/settingsdialogplugins.h b/src/settingsdialogplugins.h index 9d21daa6..8e2dae2a 100644 --- a/src/settingsdialogplugins.h +++ b/src/settingsdialogplugins.h @@ -7,7 +7,7 @@ class PluginsSettingsTab : public SettingsTab { public: - PluginsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + PluginsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); void closing() override; diff --git a/src/settingsdialogsteam.cpp b/src/settingsdialogsteam.cpp index 34c2d76b..9ed93e47 100644 --- a/src/settingsdialogsteam.cpp +++ b/src/settingsdialogsteam.cpp @@ -1,11 +1,11 @@ #include "settingsdialogsteam.h" #include "ui_settingsdialog.h" -SteamSettingsTab::SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { QString username, password; - m_parent->getSteamLogin(username, password); + settings().getSteamLogin(username, password); ui->steamUserEdit->setText(username); ui->steamPassEdit->setText(password); @@ -13,5 +13,5 @@ SteamSettingsTab::SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) void SteamSettingsTab::update() { - m_parent->setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); + settings().setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); } diff --git a/src/settingsdialogsteam.h b/src/settingsdialogsteam.h index dbd85151..6a3d75f4 100644 --- a/src/settingsdialogsteam.h +++ b/src/settingsdialogsteam.h @@ -7,11 +7,8 @@ class SteamSettingsTab : public SettingsTab { public: - SteamSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); - + SteamSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); - -private: }; #endif // SETTINGSDIALOGSTEAM_H diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index fc859289..443ba54e 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -3,15 +3,15 @@ #include "helper.h" #include -WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog) - : SettingsTab(m_parent, m_dialog) +WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) { - ui->appIDEdit->setText(m_parent->getSteamAppID()); + ui->appIDEdit->setText(settings().getSteamAppID()); - LoadMechanism::EMechanism mechanismID = m_parent->getLoadMechanism(); + LoadMechanism::EMechanism mechanismID = settings().getLoadMechanism(); int index = 0; - if (m_parent->loadMechanism().isDirectLoadingSupported()) { + if (settings().loadMechanism().isDirectLoadingSupported()) { ui->mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { index = ui->mechanismBox->count() - 1; @@ -20,13 +20,13 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo ui->mechanismBox->setCurrentIndex(index); - ui->hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins()); - ui->forceEnableBox->setChecked(m_parent->forceEnableCoreFiles()); - ui->displayForeignBox->setChecked(m_parent->displayForeign()); - ui->lockGUIBox->setChecked(m_parent->lockGUI()); - ui->enableArchiveParsingBox->setChecked(m_parent->archiveParsing()); + ui->hideUncheckedBox->setChecked(settings().hideUncheckedPlugins()); + ui->forceEnableBox->setChecked(settings().forceEnableCoreFiles()); + ui->displayForeignBox->setChecked(settings().displayForeign()); + ui->lockGUIBox->setChecked(settings().lockGUI()); + ui->enableArchiveParsingBox->setChecked(settings().archiveParsing()); - setExecutableBlacklist(m_parent->executablesBlacklist()); + setExecutableBlacklist(settings().executablesBlacklist()); QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); }); @@ -35,26 +35,26 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings *m_parent, SettingsDialo void WorkaroundsSettingsTab::update() { - if (ui->appIDEdit->text() != m_parent->gamePlugin()->steamAPPId()) { - m_Settings.setValue("Settings/app_id", ui->appIDEdit->text()); + if (ui->appIDEdit->text() != settings().gamePlugin()->steamAPPId()) { + qsettings().setValue("Settings/app_id", ui->appIDEdit->text()); } else { - m_Settings.remove("Settings/app_id"); + qsettings().remove("Settings/app_id"); } - m_Settings.setValue("Settings/load_mechanism", ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt()); - m_Settings.setValue("Settings/hide_unchecked_plugins", ui->hideUncheckedBox->isChecked()); - m_Settings.setValue("Settings/force_enable_core_files", ui->forceEnableBox->isChecked()); - m_Settings.setValue("Settings/display_foreign", ui->displayForeignBox->isChecked()); - m_Settings.setValue("Settings/lock_gui", ui->lockGUIBox->isChecked()); - m_Settings.setValue("Settings/archive_parsing_experimental", ui->enableArchiveParsingBox->isChecked()); + qsettings().setValue("Settings/load_mechanism", ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt()); + qsettings().setValue("Settings/hide_unchecked_plugins", ui->hideUncheckedBox->isChecked()); + qsettings().setValue("Settings/force_enable_core_files", ui->forceEnableBox->isChecked()); + qsettings().setValue("Settings/display_foreign", ui->displayForeignBox->isChecked()); + qsettings().setValue("Settings/lock_gui", ui->lockGUIBox->isChecked()); + qsettings().setValue("Settings/archive_parsing_experimental", ui->enableArchiveParsingBox->isChecked()); - m_Settings.setValue("Settings/executable_blacklist", getExecutableBlacklist()); + qsettings().setValue("Settings/executable_blacklist", getExecutableBlacklist()); } void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() { bool ok = false; QString result = QInputDialog::getMultiLineText( - parentWidget(), + &dialog(), QObject::tr("Executables Blacklist"), QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n" "Mods and other virtualized files will not be visible to these executables and\n" @@ -96,7 +96,7 @@ void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel); if (res == QMessageBox::Yes) { - m_parent->geometry().requestReset(); + settings().geometry().requestReset(); qApp->exit(INT_MAX); } } diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h index 1687624b..d5d6815f 100644 --- a/src/settingsdialogworkarounds.h +++ b/src/settingsdialogworkarounds.h @@ -7,8 +7,7 @@ class WorkaroundsSettingsTab : public SettingsTab { public: - WorkaroundsSettingsTab(Settings *m_parent, SettingsDialog &m_dialog); - + WorkaroundsSettingsTab(Settings& settings, SettingsDialog& dialog); void update(); private: -- cgit v1.3.1 From 0f712305c840bc509fa8f00eebf2a2a4bbf28bfd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 24 Aug 2019 13:04:10 -0400 Subject: added settings for QTabWidget, checkable QAbstractButton and ExpanderWidget removed directInterface() from mod info conflicts --- src/expanderwidget.cpp | 27 +++++++++++ src/expanderwidget.h | 5 ++ src/modinfodialogconflicts.cpp | 102 +++++++++-------------------------------- src/settings.cpp | 74 +++++++++++++++++++++++++++++- src/settings.h | 14 +++++- 5 files changed, 140 insertions(+), 82 deletions(-) (limited to 'src') diff --git a/src/expanderwidget.cpp b/src/expanderwidget.cpp index 2f47da5b..a9d045a5 100644 --- a/src/expanderwidget.cpp +++ b/src/expanderwidget.cpp @@ -52,3 +52,30 @@ bool ExpanderWidget::opened() const { return opened_; } + +QByteArray ExpanderWidget::saveState() const +{ + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + + stream << opened(); + + return result; +} + +void ExpanderWidget::restoreState(const QByteArray& a) +{ + QDataStream stream(a); + + bool opened = false; + stream >> opened; + + if (stream.status() == QDataStream::Ok) { + toggle(opened); + } +} + +QToolButton* ExpanderWidget::button() const +{ + return m_button; +} diff --git a/src/expanderwidget.h b/src/expanderwidget.h index da3eb9d6..99b2d303 100644 --- a/src/expanderwidget.h +++ b/src/expanderwidget.h @@ -37,6 +37,11 @@ public: **/ bool opened() const; + QByteArray saveState() const; + void restoreState(const QByteArray& a); + + QToolButton* button() const; + private: QToolButton* m_button; QWidget* m_content; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 03b490a2..7840269d 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -409,8 +409,7 @@ void ConflictsTab::clear() void ConflictsTab::saveState(Settings& s) { - s.directInterface().setValue( - "mod_info_conflicts_tab", ui->tabConflictsTabs->currentIndex()); + s.saveIndex(ui->tabConflictsTabs); m_general.saveState(s); m_advanced.saveState(s); @@ -418,8 +417,7 @@ void ConflictsTab::saveState(Settings& s) void ConflictsTab::restoreState(const Settings& s) { - ui->tabConflictsTabs->setCurrentIndex( - s.directInterface().value("mod_info_conflicts_tab", 0).toInt()); + s.restoreIndex(ui->tabConflictsTabs, 0); m_general.restoreState(s); m_advanced.restoreState(s); @@ -817,55 +815,22 @@ void GeneralConflictsTab::clear() void GeneralConflictsTab::saveState(Settings& s) { - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - - stream - << m_expanders.overwrite.opened() - << m_expanders.overwritten.opened() - << m_expanders.nonconflict.opened(); - - s.directInterface().setValue( - "mod_info_conflicts_general_expanders", result); - - s.directInterface().setValue( - "mod_info_conflicts_general_overwrite", - ui->overwriteTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_conflicts_general_noconflict", - ui->noConflictTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_conflicts_general_overwritten", - ui->overwrittenTree->header()->saveState()); + s.geometry().saveState(&m_expanders.overwrite); + s.geometry().saveState(&m_expanders.overwritten); + s.geometry().saveState(&m_expanders.nonconflict); + s.geometry().saveState(ui->overwriteTree->header()); + s.geometry().saveState(ui->noConflictTree->header()); + s.geometry().saveState(ui->overwrittenTree->header()); } void GeneralConflictsTab::restoreState(const Settings& s) { - QDataStream stream(s.directInterface() - .value("mod_info_conflicts_general_expanders").toByteArray()); - - bool overwriteExpanded = false; - bool overwrittenExpanded = false; - bool noConflictExpanded = false; - - stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded; - - if (stream.status() == QDataStream::Ok) { - m_expanders.overwrite.toggle(overwriteExpanded); - m_expanders.overwritten.toggle(overwrittenExpanded); - m_expanders.nonconflict.toggle(noConflictExpanded); - } - - ui->overwriteTree->header()->restoreState(s.directInterface() - .value("mod_info_conflicts_general_overwrite").toByteArray()); - - ui->noConflictTree->header()->restoreState(s.directInterface() - .value("mod_info_conflicts_general_noconflict").toByteArray()); - - ui->overwrittenTree->header()->restoreState(s.directInterface() - .value("mod_info_conflicts_general_overwritten").toByteArray()); + s.geometry().restoreState(&m_expanders.overwrite); + s.geometry().restoreState(&m_expanders.overwritten); + s.geometry().restoreState(&m_expanders.nonconflict); + s.geometry().restoreState(ui->overwriteTree->header()); + s.geometry().restoreState(ui->noConflictTree->header()); + s.geometry().restoreState(ui->overwrittenTree->header()); } bool GeneralConflictsTab::update() @@ -1048,41 +1013,18 @@ void AdvancedConflictsTab::clear() void AdvancedConflictsTab::saveState(Settings& s) { - s.directInterface().setValue( - "mod_info_conflicts_advanced_list", - ui->conflictsAdvancedList->header()->saveState()); - - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - - stream - << ui->conflictsAdvancedShowNoConflict->isChecked() - << ui->conflictsAdvancedShowAll->isChecked() - << ui->conflictsAdvancedShowNearest->isChecked(); - - s.directInterface().setValue( - "mod_info_conflicts_advanced_options", result); + s.geometry().saveState(ui->conflictsAdvancedList->header()); + s.saveChecked(ui->conflictsAdvancedShowNoConflict); + s.saveChecked(ui->conflictsAdvancedShowAll); + s.saveChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::restoreState(const Settings& s) { - ui->conflictsAdvancedList->header()->restoreState( - s.directInterface().value("mod_info_conflicts_advanced_list").toByteArray()); - - QDataStream stream(s.directInterface() - .value("mod_info_conflicts_advanced_options").toByteArray()); - - bool noConflictChecked = false; - bool showAllChecked = false; - bool showNearestChecked = false; - - stream >> noConflictChecked >> showAllChecked >> showNearestChecked; - - if (stream.status() == QDataStream::Ok) { - ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked); - ui->conflictsAdvancedShowAll->setChecked(showAllChecked); - ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked); - } + s.geometry().restoreState(ui->conflictsAdvancedList->header()); + s.restoreChecked(ui->conflictsAdvancedShowNoConflict); + s.restoreChecked(ui->conflictsAdvancedShowAll); + s.restoreChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::update() diff --git a/src/settings.cpp b/src/settings.cpp index 9001ac65..844ee81e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "serverinfo.h" #include "executableslist.h" #include "appconfig.h" +#include "expanderwidget.h" #include #include #include @@ -107,6 +108,11 @@ QString widgetName(const QHeaderView* w) return widgetNameWithTopLevel(w->parentWidget()); } +QString widgetName(const ExpanderWidget* w) +{ + return widgetNameWithTopLevel(w->button()); +} + QString widgetName(const QWidget* w) { return widgetNameWithTopLevel(w); @@ -140,6 +146,19 @@ QString indexSettingName(const QWidget* widget) return widgetNameWithTopLevel(widget) + "_index"; } +QString checkedSettingName(const QAbstractButton* b) +{ + return widgetNameWithTopLevel(b) + "_checked"; +} + +void warnIfNotCheckable(const QAbstractButton* b) +{ + if (!b->isCheckable()) { + log::warn( + "button '{}' used in the settings as a checkbox or radio button " + "but is not checkable", b->objectName()); + } +} Settings *Settings::s_Instance = nullptr; @@ -1048,7 +1067,7 @@ void Settings::resetQuestionButtons() m_Settings.endGroup(); } -std::optional Settings::getIndex(QComboBox* cb) const +std::optional Settings::getIndex(const QComboBox* cb) const { return getOptional(m_Settings, indexSettingName(cb)); } @@ -1065,6 +1084,44 @@ void Settings::restoreIndex(QComboBox* cb, std::optional def) const } } +std::optional Settings::getIndex(const QTabWidget* w) const +{ + return getOptional(m_Settings, indexSettingName(w)); +} + +void Settings::saveIndex(const QTabWidget* w) +{ + m_Settings.setValue(indexSettingName(w), w->currentIndex()); +} + +void Settings::restoreIndex(QTabWidget* w, std::optional def) const +{ + if (auto v=getOptional(m_Settings, indexSettingName(w), def)) { + w->setCurrentIndex(*v); + } +} + +std::optional Settings::getChecked(const QAbstractButton* w) const +{ + warnIfNotCheckable(w); + return getOptional(m_Settings, checkedSettingName(w)); +} + +void Settings::saveChecked(const QAbstractButton* w) +{ + warnIfNotCheckable(w); + m_Settings.setValue(checkedSettingName(w), w->isChecked()); +} + +void Settings::restoreChecked(QAbstractButton* w, std::optional def) const +{ + warnIfNotCheckable(w); + + if (auto v=getOptional(m_Settings, checkedSettingName(w), def)) { + w->setChecked(*v); + } +} + GeometrySettings& Settings::geometry() { return m_Geometry; @@ -1184,6 +1241,21 @@ bool GeometrySettings::restoreState(QSplitter* w) const return false; } +void GeometrySettings::saveState(const ExpanderWidget* expander) +{ + m_Settings.setValue(stateSettingName(expander), expander->saveState()); +} + +bool GeometrySettings::restoreState(ExpanderWidget* expander) const +{ + if (auto v=getOptional(m_Settings, stateSettingName(expander))) { + expander->restoreState(*v); + return true; + } + + return false; +} + void GeometrySettings::saveVisibility(const QWidget* w) { m_Settings.setValue(visibilitySettingName(w), w->isVisible()); diff --git a/src/settings.h b/src/settings.h index d46c358c..b25af15f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -34,6 +34,7 @@ class QSplitter; class PluginContainer; struct ServerInfo; class Settings; +class ExpanderWidget; class GeometrySaver { @@ -71,6 +72,9 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; + void saveState(const ExpanderWidget* expander); + bool restoreState(ExpanderWidget* expander) const; + void saveVisibility(const QWidget* w); bool restoreVisibility(QWidget* w, std::optional def={}) const; @@ -249,10 +253,18 @@ public: void resetQuestionButtons(); - std::optional getIndex(QComboBox* cb) const; + std::optional getIndex(const QComboBox* cb) const; void saveIndex(const QComboBox* cb); void restoreIndex(QComboBox* cb, std::optional def={}) const; + std::optional getIndex(const QTabWidget* w) const; + void saveIndex(const QTabWidget* w); + void restoreIndex(QTabWidget* w, std::optional def={}) const; + + std::optional getChecked(const QAbstractButton* w) const; + void saveChecked(const QAbstractButton* w); + void restoreChecked(QAbstractButton* w, std::optional def={}) const; + GeometrySettings& geometry(); const GeometrySettings& geometry() const; -- cgit v1.3.1 From baa32b99f53399cd3ea7c9990a2b6312ee83e8a4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 24 Aug 2019 13:10:50 -0400 Subject: finished moving mod info dialog directinterface to settings --- src/modinfodialogesps.cpp | 4 ++-- src/modinfodialogimages.cpp | 12 ++++-------- src/modinfodialogtab.h | 34 ---------------------------------- src/modinfodialogtextfiles.cpp | 4 ++-- 4 files changed, 8 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 3130b4bd..e5f1637f 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -277,12 +277,12 @@ void ESPsTab::update() void ESPsTab::saveState(Settings& s) { - saveWidgetState(s.directInterface(), ui->ESPsSplitter); + s.geometry().saveState(ui->ESPsSplitter); } void ESPsTab::restoreState(const Settings& s) { - restoreWidgetState(s.directInterface(), ui->ESPsSplitter); + s.geometry().restoreState(ui->ESPsSplitter); } void ESPsTab::onActivate() diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 10362058..38c12d8a 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -130,18 +130,14 @@ void ImagesTab::update() void ImagesTab::saveState(Settings& s) { - s.directInterface().setValue( - "mod_info_dialog_images_show_dds", m_ddsEnabled); - - saveWidgetState(s.directInterface(), ui->tabImagesSplitter); + s.saveChecked(ui->imagesShowDDS); + s.geometry().saveState(ui->tabImagesSplitter); } void ImagesTab::restoreState(const Settings& s) { - ui->imagesShowDDS->setChecked(s.directInterface() - .value("mod_info_dialog_images_show_dds", false).toBool()); - - restoreWidgetState(s.directInterface(), ui->tabImagesSplitter); + s.restoreChecked(ui->imagesShowDDS); + s.geometry().restoreState(ui->tabImagesSplitter); } void ImagesTab::checkFiltering() diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index c43fa076..d0a58574 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -253,32 +253,6 @@ protected: // void setFocus(); - - // saves the sate of the given widget in geometry/modinfodialog_[objectname] - // - // this needs to be a template because saveState() and restoreState() are - // not in QWidget, but they're in various widgets - // - template - void saveWidgetState(QSettings& s, Widget* w) - { - s.setValue(settingName(w), w->saveState()); - } - - // restores the sate of the given widget from - // geometry/modinfodialog_[objectname] - // - // this needs to be a template because saveState() and restoreState() are - // not in QWidget, but they're in various widgets - // - template - void restoreWidgetState(const QSettings& s, Widget* w) - { - if (s.contains(settingName(w))) { - w->restoreState(s.value(settingName(w)).toByteArray()); - } - } - private: // core OrganizerCore& m_core; @@ -303,14 +277,6 @@ private: // true if the tab has never been selected for the current mod bool m_firstActivation; - - - // used by saveWidgetState() and restoreWidgetState() - // - QString settingName(QWidget* w) - { - return "geometry/modinfodialog_" + w->objectName(); - } }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 7a09fa4e..fdf12568 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -176,12 +176,12 @@ void GenericFilesTab::update() void GenericFilesTab::saveState(Settings& s) { - saveWidgetState(s.directInterface(), m_splitter); + s.geometry().saveState(m_splitter); } void GenericFilesTab::restoreState(const Settings& s) { - restoreWidgetState(s.directInterface(), m_splitter); + s.geometry().restoreState(m_splitter); } void GenericFilesTab::onSelection( -- cgit v1.3.1 From 4f1b15f0a1b2e6cbca4b420608d81570af489067 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 24 Aug 2019 16:07:07 -0400 Subject: changed crash dump type to use enum instead of int added ColorSettings settings dialog general and diag tabs don't use qsettings anymore removed logging of setting changes, will be added back to Settings class --- src/modinfo.cpp | 1 - src/modlist.cpp | 10 +-- src/organizercore.cpp | 6 +- src/organizercore.h | 4 +- src/pluginlist.cpp | 2 +- src/settings.cpp | 160 ++++++++++++++++++++++++++++++-------- src/settings.h | 70 ++++++++++------- src/settingsdialog.cpp | 26 +------ src/settingsdialog.ui | 20 ----- src/settingsdialogdiagnostics.cpp | 38 ++++++++- src/settingsdialogdiagnostics.h | 1 + src/settingsdialoggeneral.cpp | 66 ++++++++-------- src/usvfsconnector.cpp | 7 +- src/usvfsconnector.h | 2 +- 14 files changed, 259 insertions(+), 154 deletions(-) (limited to 'src') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 5a05e7ca..e3daa4fd 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -42,7 +42,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include using namespace MOBase; using namespace MOShared; diff --git a/src/modlist.cpp b/src/modlist.cpp index c591c49b..94b4a387 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -416,15 +416,15 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const bool archiveOverwritten = m_ArchiveOverwritten.find(modIndex) != m_ArchiveOverwritten.end(); bool archiveLooseOverwritten = m_ArchiveLooseOverwritten.find(modIndex) != m_ArchiveLooseOverwritten.end(); if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { - return Settings::instance().modlistContainsPluginColor(); + return Settings::instance().colors().modlistContainsPlugin(); } else if (overwritten || archiveLooseOverwritten) { - return Settings::instance().modlistOverwritingLooseColor(); + return Settings::instance().colors().modlistOverwritingLoose(); } else if (overwrite || archiveLooseOverwrite) { - return Settings::instance().modlistOverwrittenLooseColor(); + return Settings::instance().colors().modlistOverwrittenLoose(); } else if (archiveOverwritten) { - return Settings::instance().modlistOverwritingArchiveColor(); + return Settings::instance().colors().modlistOverwritingArchive(); } else if (archiveOverwrite) { - return Settings::instance().modlistOverwrittenArchiveColor(); + return Settings::instance().colors().modlistOverwrittenArchive(); } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid() && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 73d0abac..5a8ee4c2 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -667,7 +667,7 @@ void OrganizerCore::prepareVFS() } void OrganizerCore::updateVFSParams( - log::Levels logLevel, int crashDumpsType, QString executableBlacklist) + log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist) { setGlobalCrashDumpsType(crashDumpsType); m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); @@ -692,8 +692,8 @@ bool OrganizerCore::cycleDiagnostics() { } //static -void OrganizerCore::setGlobalCrashDumpsType(int crashDumpsType) { - m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType); +void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) { + m_globalCrashDumpsType = type; } //static diff --git a/src/organizercore.h b/src/organizercore.h index 4bcfe745..a14d79a9 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -193,7 +193,7 @@ public: void prepareVFS(); void updateVFSParams( - MOBase::log::Levels logLevel, int crashDumpsType, + MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist); void setLogLevel(MOBase::log::Levels level); @@ -201,7 +201,7 @@ public: bool cycleDiagnostics(); static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } - static void setGlobalCrashDumpsType(int crashDumpsType); + static void setGlobalCrashDumpsType(CrashDumpsType crashDumpsType); static std::wstring crashDumpsPath(); public: diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 8637f546..ddfe492e 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -931,7 +931,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const } else if (role == Qt::BackgroundRole || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { if (m_ESPs[index].m_ModSelected) { - return Settings::instance().pluginListContainedColor(); + return Settings::instance().colors().pluginListContained(); } else { return QVariant(); } diff --git a/src/settings.cpp b/src/settings.cpp index 844ee81e..2236fc9d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -24,7 +24,6 @@ along with Mod Organizer. If not, see . #include "expanderwidget.h" #include #include -#include using namespace MOBase; @@ -33,7 +32,13 @@ std::optional getOptional( const QSettings& s, const QString& name, std::optional def={}) { if (s.contains(name)) { - return s.value(name).value(); + const auto v = s.value(name); + + if constexpr (std::is_enum_v) { + return static_cast(v.value>()); + } else { + return v.value(); + } } return def; @@ -160,10 +165,12 @@ void warnIfNotCheckable(const QAbstractButton* b) } } + Settings *Settings::s_Instance = nullptr; -Settings::Settings(const QString& path) - : m_Settings(path, QSettings::IniFormat), m_Geometry(m_Settings) +Settings::Settings(const QString& path) : + m_Settings(path, QSettings::IniFormat), + m_Geometry(m_Settings), m_Colors(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -280,6 +287,11 @@ bool Settings::colorSeparatorScrollbar() const return m_Settings.value("Settings/colorSeparatorScrollbars", true).toBool(); } +void Settings::setColorSeparatorScrollbar(bool b) +{ + m_Settings.setValue("Settings/colorSeparatorScrollbars", b); +} + void Settings::managedGameChanged(IPluginGame const *gamePlugin) { m_GamePlugin = gamePlugin; @@ -397,6 +409,11 @@ bool Settings::usePrereleases() const return m_Settings.value("Settings/use_prereleases", false).toBool(); } +void Settings::setUsePrereleases(bool b) +{ + m_Settings.setValue("Settings/use_prereleases", b); +} + void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) { m_Settings.beginGroup("Servers"); @@ -615,16 +632,27 @@ bool Settings::getSteamLogin(QString &username, QString &password) const return !username.isEmpty() && !password.isEmpty(); } + bool Settings::compactDownloads() const { return m_Settings.value("Settings/compact_downloads", false).toBool(); } +void Settings::setCompactDownloads(bool b) +{ + m_Settings.setValue("Settings/compact_downloads", b); +} + bool Settings::metaDownloads() const { return m_Settings.value("Settings/meta_downloads", false).toBool(); } +void Settings::setMetaDownloads(bool b) +{ + m_Settings.setValue("Settings/meta_downloads", b); +} + bool Settings::offlineMode() const { return m_Settings.value("Settings/offline_mode", false).toBool(); @@ -640,44 +668,25 @@ void Settings::setLogLevel(log::Levels level) m_Settings.setValue("Settings/log_level", static_cast(level)); } -int Settings::crashDumpsType() const +CrashDumpsType Settings::crashDumpsType() const { - return m_Settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt(); + const auto v = getOptional(m_Settings, "Settings/crash_dumps_type"); + return v.value_or(CrashDumpsType::Mini); } -int Settings::crashDumpsMax() const +void Settings::setCrashDumpsType(CrashDumpsType type) { - return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); + m_Settings.setValue("Settings/crash_dumps_type", static_cast(type)); } -QColor Settings::modlistOverwrittenLooseColor() const -{ - return m_Settings.value("Settings/overwrittenLooseFilesColor", QColor(0, 255, 0, 64)).value(); -} - -QColor Settings::modlistOverwritingLooseColor() const -{ - return m_Settings.value("Settings/overwritingLooseFilesColor", QColor(255, 0, 0, 64)).value(); -} - -QColor Settings::modlistOverwrittenArchiveColor() const -{ - return m_Settings.value("Settings/overwrittenArchiveFilesColor", QColor(0, 255, 255, 64)).value(); -} - -QColor Settings::modlistOverwritingArchiveColor() const -{ - return m_Settings.value("Settings/overwritingArchiveFilesColor", QColor(255, 0, 255, 64)).value(); -} - -QColor Settings::modlistContainsPluginColor() const +int Settings::crashDumpsMax() const { - return m_Settings.value("Settings/containsPluginColor", QColor(0, 0, 255, 64)).value(); + return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); } -QColor Settings::pluginListContainedColor() const +void Settings::setCrashDumpsMax(int n) { - return m_Settings.value("Settings/containedColor", QColor(0, 0, 255, 64)).value(); + return m_Settings.setValue("Settings/crash_dumps_max", n); } QString Settings::executablesBlacklist() const @@ -850,6 +859,11 @@ QString Settings::language() return result; } +void Settings::setLanguage(const QString& name) +{ + m_Settings.setValue("Settings/language", name); +} + void Settings::updateServers(const QList &servers) { m_Settings.beginGroup("Servers"); @@ -1132,6 +1146,16 @@ const GeometrySettings& Settings::geometry() const return m_Geometry; } +ColorSettings& Settings::colors() +{ + return m_Colors; +} + +const ColorSettings& Settings::colors() const +{ + return m_Colors; +} + QSettings::Status Settings::sync() const { m_Settings.sync(); @@ -1451,6 +1475,78 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const } +ColorSettings::ColorSettings(QSettings& s) + : m_Settings(s) +{ +} + +QColor ColorSettings::modlistOverwrittenLoose() const +{ + return getOptional(m_Settings, "Settings/overwrittenLooseFilesColor") + .value_or(QColor(0, 255, 0, 64)); +} + +void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +{ + m_Settings.setValue("Settings/overwrittenLooseFilesColor", c); +} + +QColor ColorSettings::modlistOverwritingLoose() const +{ + return getOptional(m_Settings, "Settings/overwritingLooseFilesColor") + .value_or(QColor(255, 0, 0, 64)); +} + +void ColorSettings::setModlistOverwritingLoose(const QColor& c) +{ + m_Settings.setValue("Settings/overwritingLooseFilesColor", c); +} + +QColor ColorSettings::modlistOverwrittenArchive() const +{ + return getOptional(m_Settings, "Settings/overwrittenArchiveFilesColor") + .value_or(QColor(0, 255, 255, 64)); +} + +void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +{ + m_Settings.setValue("Settings/overwrittenArchiveFilesColor", c); +} + +QColor ColorSettings::modlistOverwritingArchive() const +{ + return getOptional(m_Settings, "Settings/overwritingArchiveFilesColor") + .value_or(QColor(255, 0, 255, 64)); +} + +void ColorSettings::setModlistOverwritingArchive(const QColor& c) +{ + m_Settings.setValue("Settings/overwritingArchiveFilesColor", c); +} + +QColor ColorSettings::modlistContainsPlugin() const +{ + return getOptional(m_Settings, "Settings/containsPluginColor") + .value_or(QColor(0, 0, 255, 64)); +} + +void ColorSettings::setModlistContainsPlugin(const QColor& c) +{ + m_Settings.setValue("Settings/containsPluginColor", c); +} + +QColor ColorSettings::pluginListContained() const +{ + return getOptional(m_Settings, "Settings/containedColor") + .value_or(QColor(0, 0, 255, 64)); +} + +void ColorSettings::setPluginListContained(const QColor& c) +{ + m_Settings.setValue("Settings/containedColor", c); +} + + GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) : m_settings(s), m_dialog(dialog) { diff --git a/src/settings.h b/src/settings.h index b25af15f..5b1d7bfc 100644 --- a/src/settings.h +++ b/src/settings.h @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "loadmechanism.h" #include #include +#include namespace MOBase { class IPlugin; @@ -96,6 +97,36 @@ private: }; +class ColorSettings +{ +public: + ColorSettings(QSettings& s); + + void setCrashDumpsMax(int i) const; + + QColor modlistOverwrittenLoose() const; + void setModlistOverwrittenLoose(const QColor& c); + + QColor modlistOverwritingLoose() const; + void setModlistOverwritingLoose(const QColor& c); + + QColor modlistOverwrittenArchive() const; + void setModlistOverwrittenArchive(const QColor& c); + + QColor modlistOverwritingArchive() const; + void setModlistOverwritingArchive(const QColor& c); + + QColor modlistContainsPlugin() const; + void setModlistContainsPlugin(const QColor& c); + + QColor pluginListContained() const; + void setPluginListContained(const QColor& c) ; + +private: + QSettings& m_Settings; +}; + + enum class EndorsementState { Accepted = 1, @@ -268,6 +299,10 @@ public: GeometrySettings& geometry(); const GeometrySettings& geometry() const; + ColorSettings& colors(); + const ColorSettings& colors() const; + + /** * retrieve the directory where profiles stored (with native separators) **/ @@ -329,43 +364,22 @@ public: * @return true if the user chose compact downloads */ bool compactDownloads() const; + void setCompactDownloads(bool b); /** * @return true if the user chose meta downloads */ bool metaDownloads() const; + void setMetaDownloads(bool b); - /** - * @return the configured log level - */ MOBase::log::Levels logLevel() const; - - /** - * sets the log level setting - */ void setLogLevel(MOBase::log::Levels level); - /** - * @return the configured crash dumps type - */ - int crashDumpsType() const; + CrashDumpsType crashDumpsType() const; + void setCrashDumpsType(CrashDumpsType type); - /** - * @return the configured crash dumps max - */ int crashDumpsMax() const; - - QColor modlistOverwrittenLooseColor() const; - - QColor modlistOverwritingLooseColor() const; - - QColor modlistOverwrittenArchiveColor() const; - - QColor modlistOverwritingArchiveColor() const; - - QColor modlistContainsPluginColor() const; - - QColor pluginListContainedColor() const; + void setCrashDumpsMax(int n); QString executablesBlacklist() const; @@ -473,6 +487,7 @@ public: * @return short code of the configured language (corresponding to the translation files) */ QString language(); + void setLanguage(const QString& name); /** * @brief updates the list of known servers @@ -499,6 +514,7 @@ public: std::vector plugins() const { return m_Plugins; } bool usePrereleases() const; + void setUsePrereleases(bool b); /** * @brief register MO as the handler for nxm links @@ -512,6 +528,7 @@ public: * @return the state of the setting */ bool colorSeparatorScrollbar() const; + void setColorSeparatorScrollbar(bool b); static QColor getIdealTextColor(const QColor& rBackgroundColor); @@ -540,6 +557,7 @@ private: MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; GeometrySettings m_Geometry; + ColorSettings m_Colors; LoadMechanism m_LoadMechanism; std::vector m_Plugins; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 097dafc8..9d031785 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -51,43 +51,19 @@ int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); - auto& qsettings = m_settings.directInterface(); auto ret = TutorableDialog::exec(); if (ret == QDialog::Accepted) { - for (auto&& tab : m_tabs) { tab->closing(); } - // remember settings before change - QMap before; - qsettings.beginGroup("Settings"); - for (auto k : qsettings.allKeys()) - before[k] = qsettings.value(k).toString(); - qsettings.endGroup(); - - // transfer modified settings to configuration file + // update settings for each tab for (std::unique_ptr const &tab: m_tabs) { tab->update(); } - - // print "changed" settings - qsettings.beginGroup("Settings"); - bool first_update = true; - for (auto k : qsettings.allKeys()) - if (qsettings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) - { - if (first_update) { - log::debug("Changed settings:"); - first_update = false; - } - log::debug(" {}={}", k, qsettings.value(k).toString()); - } - qsettings.endGroup(); } - // These changes happen regardless of accepted or rejected bool restartNeeded = false; if (getApiKeyChanged()) { restartNeeded = true; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index e7676387..af230bc0 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1346,26 +1346,6 @@ programs you are intentionally running. "Full" Even larger dumps with a full memory dump of the process.
- - - None - - - - - Mini (recommended) - - - - - Data - - - - - Full - -
diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 227d1dfa..278da0bf 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -10,10 +10,13 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { setLevelsBox(); - ui->dumpsTypeBox->setCurrentIndex(settings().crashDumpsType()); + setCrashDumpTypesBox(); + ui->dumpsMaxEdit->setValue(settings().crashDumpsMax()); + QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); + ui->diagnosticsExplainedLabel->setText( ui->diagnosticsExplainedLabel->text() .replace("LOGS_FULL_PATH", logsPath) @@ -40,9 +43,36 @@ void DiagnosticsSettingsTab::setLevelsBox() } } +void DiagnosticsSettingsTab::setCrashDumpTypesBox() +{ + ui->dumpsTypeBox->clear(); + + auto add = [&](auto&& text, auto&& type) { + ui->dumpsTypeBox->addItem(text, static_cast(type)); + }; + + add(QObject::tr("None"), CrashDumpsType::None); + add(QObject::tr("Mini (recommended)"), CrashDumpsType::Mini); + add(QObject::tr("Data"), CrashDumpsType::Data); + add(QObject::tr("Full"), CrashDumpsType::Full); + + const auto current = static_cast(settings().crashDumpsType()); + + for (int i=0; idumpsTypeBox->count(); ++i) { + if (ui->dumpsTypeBox->itemData(i) == current) { + ui->dumpsTypeBox->setCurrentIndex(i); + break; + } + } +} + void DiagnosticsSettingsTab::update() { - qsettings().setValue("Settings/log_level", ui->logLevelBox->currentData().toInt()); - qsettings().setValue("Settings/crash_dumps_type", ui->dumpsTypeBox->currentIndex()); - qsettings().setValue("Settings/crash_dumps_max", ui->dumpsMaxEdit->value()); + settings().setLogLevel( + static_cast(ui->logLevelBox->currentData().toInt())); + + settings().setCrashDumpsType( + static_cast(ui->dumpsTypeBox->currentData().toInt())); + + settings().setCrashDumpsMax(ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h index f20413f8..f0fbf770 100644 --- a/src/settingsdialogdiagnostics.h +++ b/src/settingsdialogdiagnostics.h @@ -13,6 +13,7 @@ public: private: void setLevelsBox(); + void setCrashDumpTypesBox(); }; #endif // SETTINGSDIALOGDIAGNOSTICS_H diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 35012db7..e3d73037 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -26,28 +26,30 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) } addStyles(); + { - int currentID = ui->styleBox->findData( - qsettings().value("Settings/style", "").toString()); + const int currentID = ui->styleBox->findData( + settings().getStyleName().value_or("")); + if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); } } //version with stylesheet - setButtonColor(ui->overwritingBtn, settings().modlistOverwritingLooseColor()); - setButtonColor(ui->overwrittenBtn, settings().modlistOverwrittenLooseColor()); - setButtonColor(ui->overwritingArchiveBtn, settings().modlistOverwritingArchiveColor()); - setButtonColor(ui->overwrittenArchiveBtn, settings().modlistOverwrittenArchiveColor()); - setButtonColor(ui->containsBtn, settings().modlistContainsPluginColor()); - setButtonColor(ui->containedBtn, settings().pluginListContainedColor()); - - setOverwritingColor(settings().modlistOverwritingLooseColor()); - setOverwrittenColor(settings().modlistOverwrittenLooseColor()); - setOverwritingArchiveColor(settings().modlistOverwritingArchiveColor()); - setOverwrittenArchiveColor(settings().modlistOverwrittenArchiveColor()); - setContainsColor(settings().modlistContainsPluginColor()); - setContainedColor(settings().pluginListContainedColor()); + setButtonColor(ui->overwritingBtn, settings().colors().modlistOverwritingLoose()); + setButtonColor(ui->overwrittenBtn, settings().colors().modlistOverwrittenLoose()); + setButtonColor(ui->overwritingArchiveBtn, settings().colors().modlistOverwritingArchive()); + setButtonColor(ui->overwrittenArchiveBtn, settings().colors().modlistOverwrittenArchive()); + setButtonColor(ui->containsBtn, settings().colors().modlistContainsPlugin()); + setButtonColor(ui->containedBtn, settings().colors().pluginListContained()); + + setOverwritingColor(settings().colors().modlistOverwritingLoose()); + setOverwrittenColor(settings().colors().modlistOverwrittenLoose()); + setOverwritingArchiveColor(settings().colors().modlistOverwritingArchive()); + setOverwrittenArchiveColor(settings().colors().modlistOverwrittenArchive()); + setContainsColor(settings().colors().modlistContainsPlugin()); + setContainedColor(settings().colors().pluginListContained()); ui->compactBox->setChecked(settings().compactDownloads()); ui->showMetaBox->setChecked(settings().metaDownloads()); @@ -67,30 +69,32 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) void GeneralSettingsTab::update() { - QString oldLanguage = settings().language(); - QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); + const QString oldLanguage = settings().language(); + const QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); + if (newLanguage != oldLanguage) { - qsettings().setValue("Settings/language", newLanguage); + settings().setLanguage(newLanguage); emit settings().languageChanged(newLanguage); } - QString oldStyle = qsettings().value("Settings/style", "").toString(); - QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); + const QString oldStyle = settings().getStyleName().value_or(""); + const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - qsettings().setValue("Settings/style", newStyle); + settings().setStyleName(newStyle); emit settings().styleChanged(newStyle); } - qsettings().setValue("Settings/overwritingLooseFilesColor", getOverwritingColor()); - qsettings().setValue("Settings/overwrittenLooseFilesColor", getOverwrittenColor()); - qsettings().setValue("Settings/overwritingArchiveFilesColor", getOverwritingArchiveColor()); - qsettings().setValue("Settings/overwrittenArchiveFilesColor", getOverwrittenArchiveColor()); - qsettings().setValue("Settings/containsPluginColor", getContainsColor()); - qsettings().setValue("Settings/containedColor", getContainedColor()); - qsettings().setValue("Settings/compact_downloads", ui->compactBox->isChecked()); - qsettings().setValue("Settings/meta_downloads", ui->showMetaBox->isChecked()); - qsettings().setValue("Settings/use_prereleases", ui->usePrereleaseBox->isChecked()); - qsettings().setValue("Settings/colorSeparatorScrollbars", ui->colorSeparatorsBox->isChecked()); + settings().colors().setModlistOverwritingLoose(getOverwritingColor()); + settings().colors().setModlistOverwrittenLoose(getOverwrittenColor()); + settings().colors().setModlistOverwritingArchive(getOverwritingArchiveColor()); + settings().colors().setModlistOverwrittenArchive(getOverwrittenArchiveColor()); + settings().colors().setModlistContainsPlugin(getContainsColor()); + settings().colors().setPluginListContained(getContainedColor()); + + settings().setCompactDownloads(ui->compactBox->isChecked()); + settings().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); + settings().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index b5e6edb1..41f58308 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -164,7 +164,7 @@ UsvfsConnector::UsvfsConnector() { USVFSParameters params; LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); - CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); + CrashDumpsType dumpType = Settings::instance().crashDumpsType(); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); @@ -249,9 +249,10 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) } void UsvfsConnector::updateParams( - MOBase::log::Levels logLevel, int crashDumpsType, QString executableBlacklist) + MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, + QString executableBlacklist) { - USVFSUpdateParams(toUsvfsLogLevel(logLevel), ::crashDumpsType(crashDumpsType)); + USVFSUpdateParams(toUsvfsLogLevel(logLevel), crashDumpsType); ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index b0bd320c..cd5d56b2 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -87,7 +87,7 @@ public: void updateMapping(const MappingType &mapping); void updateParams( - MOBase::log::Levels logLevel, int crashDumpsType, + MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist); void updateForcedLibraries(const QList &forcedLibraries); -- cgit v1.3.1 From b1687380c5c10342699f95361e30f18a32bad585 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 00:01:07 -0400 Subject: moved more nexus stuff to settings settings tab remembered --- src/settings.cpp | 24 ++++++++++++++++++++++-- src/settings.h | 4 ++++ src/settingsdialog.cpp | 4 ++++ src/settingsdialognexus.cpp | 12 ++++++------ 4 files changed, 36 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 2236fc9d..9c9e4c4d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -545,6 +545,11 @@ std::optional Settings::getUseProxy() const return getOptional(m_Settings, "Settings/use_proxy"); } +void Settings::setUseProxy(bool b) +{ + m_Settings.setValue("Settings/use_proxy", b); +} + std::optional Settings::getVersion() const { if (auto v=getOptional(m_Settings, "version")) { @@ -658,6 +663,11 @@ bool Settings::offlineMode() const return m_Settings.value("Settings/offline_mode", false).toBool(); } +void Settings::setOfflineMode(bool b) +{ + m_Settings.setValue("Settings/offline_mode", b); +} + log::Levels Settings::logLevel() const { return static_cast(m_Settings.value("Settings/log_level").toInt()); @@ -756,6 +766,11 @@ bool Settings::endorsementIntegration() const return m_Settings.value("Settings/endorsement_integration", true).toBool(); } +void Settings::setEndorsementIntegration(bool b) const +{ + m_Settings.setValue("Settings/endorsement_integration", b); +} + EndorsementState Settings::endorsementState() const { const auto v = getOptional(m_Settings, "endorse_state"); @@ -778,6 +793,11 @@ bool Settings::hideAPICounter() const return m_Settings.value("Settings/hide_api_counter", false).toBool(); } +void Settings::setHideAPICounter(bool b) +{ + m_Settings.setValue("Settings/hide_api_counter", b); +} + bool Settings::displayForeign() const { return m_Settings.value("Settings/display_foreign", true).toBool(); @@ -886,8 +906,8 @@ void Settings::updateServers(const QList &servers) data["premium"] = server.premium; m_Settings.setValue(server.name, data); + } } - } // clean up unavailable servers QDate now = QDate::currentDate(); @@ -1166,7 +1186,7 @@ void Settings::dump() const { static const QStringList ignore({ "username", "password", "nexus_api_key" - }); + }); log::debug("settings:"); diff --git a/src/settings.h b/src/settings.h index 5b1d7bfc..21776169 100644 --- a/src/settings.h +++ b/src/settings.h @@ -359,6 +359,7 @@ public: * @return true if the user disabled internet features */ bool offlineMode() const; + void setOfflineMode(bool b); /** * @return true if the user chose compact downloads @@ -405,11 +406,13 @@ public: * @return true if the user configured the use of a network proxy */ bool useProxy() const; + void setUseProxy(bool b); /** * @return true if endorsement integration is enabled */ bool endorsementIntegration() const; + void setEndorsementIntegration(bool b) const; EndorsementState endorsementState() const; void setEndorsementState(EndorsementState s); @@ -419,6 +422,7 @@ public: * @return true if the API counter should be hidden */ bool hideAPICounter() const; + void setHideAPICounter(bool b); /** * @return true if the user wants to see non-official plugins installed outside MO in his mod list diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 9d031785..a24416e9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -51,8 +51,12 @@ int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); + m_settings.restoreIndex(ui->tabWidget); + auto ret = TutorableDialog::exec(); + m_settings.saveIndex(ui->tabWidget); + if (ret == QDialog::Accepted) { for (auto&& tab : m_tabs) { tab->closing(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index b1964069..4d9a18a2 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -114,10 +114,10 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) void NexusSettingsTab::update() { - qsettings().setValue("Settings/offline_mode", ui->offlineBox->isChecked()); - qsettings().setValue("Settings/use_proxy", ui->proxyBox->isChecked()); - qsettings().setValue("Settings/endorsement_integration", ui->endorsementBox->isChecked()); - qsettings().setValue("Settings/hide_api_counter", ui->hideAPICounterBox->isChecked()); + settings().setOfflineMode(ui->offlineBox->isChecked()); + settings().setUseProxy(ui->proxyBox->isChecked()); + settings().setEndorsementIntegration(ui->endorsementBox->isChecked()); + settings().setHideAPICounter(ui->hideAPICounterBox->isChecked()); // store server preference qsettings().beginGroup("Servers"); @@ -126,14 +126,14 @@ void NexusSettingsTab::update() QVariantMap val = qsettings().value(key).toMap(); val["preferred"] = 0; qsettings().setValue(key, val); - } + } int count = ui->preferredServersList->count(); for (int i = 0; i < count; ++i) { QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); QVariantMap val = qsettings().value(key).toMap(); val["preferred"] = count - i; qsettings().setValue(key, val); - } + } qsettings().endGroup(); } -- cgit v1.3.1 From 7395fbb7544740a136884e103cb0829bd10b5655 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 00:21:41 -0400 Subject: made ServerInfo a class moved server functions together in Settings --- src/mainwindow.cpp | 10 +++---- src/serverinfo.cpp | 31 ++++++++++++++++++++ src/serverinfo.h | 20 +++++++++---- src/settings.cpp | 86 +++++++++++++++++++++++++++--------------------------- src/settings.h | 26 ++++++++--------- 5 files changed, 107 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f1a2047f..b6ae59a1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5909,11 +5909,11 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat QList servers; for (const QVariant &server : serverList) { QVariantMap serverInfo = server.toMap(); - ServerInfo info; - info.name = serverInfo["short_name"].toString(); - info.premium = serverInfo["name"].toString().contains("Premium", Qt::CaseInsensitive); - info.lastSeen = QDate::currentDate(); - info.preferred = serverInfo["short_name"].toString().contains("CDN", Qt::CaseInsensitive); + ServerInfo info( + serverInfo["short_name"].toString(), + serverInfo["name"].toString().contains("Premium", Qt::CaseInsensitive), + QDate::currentDate(), + serverInfo["short_name"].toString().contains("CDN", Qt::CaseInsensitive)); servers.append(info); } m_OrganizerCore.settings().updateServers(servers); diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index e96b69d2..5912c226 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -1 +1,32 @@ #include "serverinfo.h" + +ServerInfo::ServerInfo() + : ServerInfo({}, false, {}, false) +{ +} + +ServerInfo::ServerInfo(QString n, bool premium, QDate last, bool preferred) : + m_name(std::move(n)), m_premium(premium), m_lastSeen(std::move(last)), + m_preferred(preferred) +{ +} + +const QString& ServerInfo::name() const +{ + return m_name; +} + +bool ServerInfo::isPremium() const +{ + return m_premium; +} + +const QDate& ServerInfo::lastSeen() const +{ + return m_lastSeen; +} + +bool ServerInfo::isPreferred() const +{ + return m_preferred; +} diff --git a/src/serverinfo.h b/src/serverinfo.h index 8e5e935a..79b90e77 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -5,12 +5,22 @@ #include #include -struct ServerInfo +class ServerInfo { - QString name; - bool premium; - QDate lastSeen; - bool preferred; +public: + ServerInfo(); + ServerInfo(QString name, bool premium, QDate lastSeen, bool preferred); + + const QString& name() const; + bool isPremium() const; + const QDate& lastSeen() const; + bool isPreferred() const; + +private: + QString m_name; + bool m_premium; + QDate m_lastSeen; + bool m_preferred; }; Q_DECLARE_METATYPE(ServerInfo) diff --git a/src/settings.cpp b/src/settings.cpp index 9c9e4c4d..9975642b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -414,40 +414,6 @@ void Settings::setUsePrereleases(bool b) m_Settings.setValue("Settings/use_prereleases", b); } -void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) -{ - m_Settings.beginGroup("Servers"); - - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - if (serverKey == serverName) { - data["downloadCount"] = data["downloadCount"].toInt() + 1; - data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); - m_Settings.setValue(serverKey, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -std::map Settings::getPreferredServers() -{ - std::map result; - m_Settings.beginGroup("Servers"); - - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } - } - m_Settings.endGroup(); - - return result; -} - QString Settings::getConfigurablePath(const QString &key, const QString &def, bool resolve) const @@ -884,28 +850,62 @@ void Settings::setLanguage(const QString& name) m_Settings.setValue("Settings/language", name); } +void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) +{ + m_Settings.beginGroup("Servers"); + + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + if (serverKey == serverName) { + data["downloadCount"] = data["downloadCount"].toInt() + 1; + data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); + m_Settings.setValue(serverKey, data); + } + } + + m_Settings.endGroup(); + m_Settings.sync(); +} + +std::map Settings::getPreferredServers() +{ + std::map result; + m_Settings.beginGroup("Servers"); + + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + int preference = data["preferred"].toInt(); + if (preference > 0) { + result[serverKey] = preference; + } + } + m_Settings.endGroup(); + + return result; +} + void Settings::updateServers(const QList &servers) { m_Settings.beginGroup("Servers"); QStringList oldServerKeys = m_Settings.childKeys(); for (const ServerInfo &server : servers) { - if (!oldServerKeys.contains(server.name)) { + if (!oldServerKeys.contains(server.name())) { // not yet known server QVariantMap newVal; - newVal["premium"] = server.premium; - newVal["preferred"] = server.preferred ? 1 : 0; - newVal["lastSeen"] = server.lastSeen; + newVal["premium"] = server.isPremium(); + newVal["preferred"] = server.isPreferred() ? 1 : 0; + newVal["lastSeen"] = server.lastSeen(); newVal["downloadCount"] = 0; newVal["downloadSpeed"] = 0.0; - m_Settings.setValue(server.name, newVal); + m_Settings.setValue(server.name(), newVal); } else { - QVariantMap data = m_Settings.value(server.name).toMap(); - data["lastSeen"] = server.lastSeen; - data["premium"] = server.premium; + QVariantMap data = m_Settings.value(server.name()).toMap(); + data["lastSeen"] = server.lastSeen(); + data["premium"] = server.isPremium(); - m_Settings.setValue(server.name, data); + m_Settings.setValue(server.name(), data); } } diff --git a/src/settings.h b/src/settings.h index 21776169..4662ed19 100644 --- a/src/settings.h +++ b/src/settings.h @@ -33,7 +33,7 @@ namespace MOBase { class QSplitter; class PluginContainer; -struct ServerInfo; +class ServerInfo; class Settings; class ExpanderWidget; @@ -190,13 +190,6 @@ public: */ bool lockGUI() const; - /** - * @brief register download speed - * @param url complete download url - * @param bytesPerSecond download size in bytes per second - */ - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - /** * the steam appid is assigned by the steam platform to each product sold there. * The appid may differ between different versions of a game so it may be impossible @@ -216,11 +209,6 @@ public: **/ QString getDownloadDirectory(bool resolve = true) const; - /** - * retrieve a sorted list of preferred servers - */ - std::map getPreferredServers(); - /** * retrieve the directory where mods are stored (with native separators) **/ @@ -493,6 +481,18 @@ public: QString language(); void setLanguage(const QString& name); + /** + * @brief register download speed + * @param url complete download url + * @param bytesPerSecond download size in bytes per second + */ + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + + /** + * retrieve a sorted list of preferred servers + */ + std::map getPreferredServers(); + /** * @brief updates the list of known servers * @param list of servers from a recent query -- cgit v1.3.1 From 36dbb4bad74b097d44b843a2e934aa4a58ef6492 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 00:48:25 -0400 Subject: ServerList instead of a QList of ServerInfo changed preferred to an int moved all server settings to Settings --- src/mainwindow.cpp | 24 ++++++++------ src/serverinfo.cpp | 63 ++++++++++++++++++++++++++++++++++--- src/serverinfo.h | 35 +++++++++++++++++++-- src/settings.cpp | 36 ++++++++++++++++++--- src/settings.h | 5 ++- src/settingsdialognexus.cpp | 77 ++++++++++++++++++++++++++++++++------------- 6 files changed, 194 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b6ae59a1..9921ad82 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5904,18 +5904,22 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { - QVariantList serverList = resultData.toList(); - - QList servers; - for (const QVariant &server : serverList) { - QVariantMap serverInfo = server.toMap(); - ServerInfo info( - serverInfo["short_name"].toString(), - serverInfo["name"].toString().contains("Premium", Qt::CaseInsensitive), + ServerList servers; + + for (const QVariant &var : resultData.toList()) { + const QVariantMap map = var.toMap(); + + ServerInfo server( + map["short_name"].toString(), + map["name"].toString().contains("Premium", Qt::CaseInsensitive), QDate::currentDate(), - serverInfo["short_name"].toString().contains("CDN", Qt::CaseInsensitive)); - servers.append(info); + map["short_name"].toString().contains("CDN", Qt::CaseInsensitive) ? 1 : 0, + map["downloadCount"].toInt(), + map["downloadSpeed"].toDouble()); + + servers.add(std::move(server)); } + m_OrganizerCore.settings().updateServers(servers); } diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index 5912c226..67a80b9e 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -1,13 +1,15 @@ #include "serverinfo.h" ServerInfo::ServerInfo() - : ServerInfo({}, false, {}, false) + : ServerInfo({}, false, {}, 0, 0, 0.0) { } -ServerInfo::ServerInfo(QString n, bool premium, QDate last, bool preferred) : - m_name(std::move(n)), m_premium(premium), m_lastSeen(std::move(last)), - m_preferred(preferred) +ServerInfo::ServerInfo( + QString name, bool premium, QDate last, int preferred, + int count, double speed) : + m_name(std::move(name)), m_premium(premium), m_lastSeen(std::move(last)), + m_preferred(preferred), m_downloadCount(count), m_downloadSpeed(speed) { } @@ -26,7 +28,58 @@ const QDate& ServerInfo::lastSeen() const return m_lastSeen; } -bool ServerInfo::isPreferred() const +int ServerInfo::preferred() const { return m_preferred; } + +int ServerInfo::downloadCount() const +{ + return m_downloadCount; +} + +double ServerInfo::downloadSpeed() const +{ + return m_downloadSpeed; +} + +void ServerInfo::setPreferred(int i) +{ + m_preferred = i; +} + + +void ServerList::add(ServerInfo s) +{ + m_servers.push_back(std::move(s)); +} + +ServerList::iterator ServerList::begin() +{ + return m_servers.begin(); +} + +ServerList::const_iterator ServerList::begin() const +{ + return m_servers.begin(); +} + +ServerList::iterator ServerList::end() +{ + return m_servers.end(); +} + +ServerList::const_iterator ServerList::end() const +{ + return m_servers.end(); +} + +std::size_t ServerList::size() const +{ + return m_servers.size(); +} + +bool ServerList::empty() const +{ + return m_servers.empty(); +} diff --git a/src/serverinfo.h b/src/serverinfo.h index 79b90e77..0a8a5028 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -9,20 +9,49 @@ class ServerInfo { public: ServerInfo(); - ServerInfo(QString name, bool premium, QDate lastSeen, bool preferred); + ServerInfo( + QString name, bool premium, QDate lastSeen, int preferred, + int downloadCount, double downloadSpeed); const QString& name() const; bool isPremium() const; const QDate& lastSeen() const; - bool isPreferred() const; + int preferred() const; + int downloadCount() const; + double downloadSpeed() const; + + void setPreferred(int i); private: QString m_name; bool m_premium; QDate m_lastSeen; - bool m_preferred; + int m_preferred; + int m_downloadCount; + double m_downloadSpeed; }; Q_DECLARE_METATYPE(ServerInfo) + +class ServerList +{ +public: + using container = QList; + using iterator = container::iterator; + using const_iterator = container::const_iterator; + + void add(ServerInfo s); + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + std::size_t size() const; + bool empty() const; + +private: + container m_servers; +}; + #endif // SERVERINFO_H diff --git a/src/settings.cpp b/src/settings.cpp index 9975642b..5ddffd8f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -884,17 +884,42 @@ std::map Settings::getPreferredServers() return result; } -void Settings::updateServers(const QList &servers) +ServerList Settings::getServers() const +{ + ServerList list; + + m_Settings.beginGroup("Servers"); + + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + + ServerInfo server( + serverKey, + data["premium"].toBool(), + data["lastSeen"].toDate(), + data["preferred"].toInt(), + data["downloadCount"].toInt(), + data["downloadSpeed"].toDouble()); + + list.add(std::move(server)); + } + + m_Settings.endGroup(); + + return list; +} + +void Settings::updateServers(const ServerList& servers) { m_Settings.beginGroup("Servers"); QStringList oldServerKeys = m_Settings.childKeys(); - for (const ServerInfo &server : servers) { + for (const auto& server : servers) { if (!oldServerKeys.contains(server.name())) { // not yet known server QVariantMap newVal; newVal["premium"] = server.isPremium(); - newVal["preferred"] = server.isPreferred() ? 1 : 0; + newVal["preferred"] = server.preferred(); newVal["lastSeen"] = server.lastSeen(); newVal["downloadCount"] = 0; newVal["downloadSpeed"] = 0.0; @@ -902,12 +927,13 @@ void Settings::updateServers(const QList &servers) m_Settings.setValue(server.name(), newVal); } else { QVariantMap data = m_Settings.value(server.name()).toMap(); - data["lastSeen"] = server.lastSeen(); data["premium"] = server.isPremium(); + data["lastSeen"] = server.lastSeen(); + data["preferred"] = server.preferred(); m_Settings.setValue(server.name(), data); - } } + } // clean up unavailable servers QDate now = QDate::currentDate(); diff --git a/src/settings.h b/src/settings.h index 4662ed19..31dbf85c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -34,6 +34,7 @@ class QSplitter; class PluginContainer; class ServerInfo; +class ServerList; class Settings; class ExpanderWidget; @@ -493,11 +494,13 @@ public: */ std::map getPreferredServers(); + ServerList getServers() const; + /** * @brief updates the list of known servers * @param list of servers from a recent query */ - void updateServers(const QList &servers); + void updateServers(const ServerList& servers); /** * @brief add a plugin that is to be blacklisted diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 4d9a18a2..926ea9a6 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -2,9 +2,11 @@ #include "ui_settingsdialog.h" #include "ui_nexusmanualkey.h" #include "nexusinterface.h" +#include "serverinfo.h" +#include "log.h" #include -namespace shell = MOBase::shell; +using namespace MOBase; template class ServerItem : public QListWidgetItem { @@ -78,30 +80,31 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); // display server preferences - qsettings().beginGroup("Servers"); - for (const QString &key : qsettings().childKeys()) { - QVariantMap val = qsettings().value(key).toMap(); - QString descriptor = key; + for (const auto& server : s.getServers()) { + QString descriptor = server.name(); + if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { descriptor += QStringLiteral(" (automatic)"); } - if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { - int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); + + if (server.downloadSpeed() > 0 && server.downloadCount() > 0) { + const int bps = static_cast(server.downloadSpeed() / server.downloadCount()); descriptor += QString(" (%1 kbps)").arg(bps / 1024); } QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); - newItem->setData(Qt::UserRole, key); - newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); - if (val["preferred"].toInt() > 0) { + newItem->setData(Qt::UserRole, server.name()); + newItem->setData(Qt::UserRole + 1, server.preferred()); + + if (server.preferred() > 0) { ui->preferredServersList->addItem(newItem); } else { ui->knownServersList->addItem(newItem); } + ui->preferredServersList->sortItems(Qt::DescendingOrder); } - qsettings().endGroup(); QObject::connect(ui->nexusConnect, &QPushButton::clicked, [&]{ on_nexusConnect_clicked(); }); QObject::connect(ui->nexusManualKey, &QPushButton::clicked, [&]{ on_nexusManualKey_clicked(); }); @@ -119,22 +122,52 @@ void NexusSettingsTab::update() settings().setEndorsementIntegration(ui->endorsementBox->isChecked()); settings().setHideAPICounter(ui->hideAPICounterBox->isChecked()); + auto servers = settings().getServers(); + // store server preference - qsettings().beginGroup("Servers"); for (int i = 0; i < ui->knownServersList->count(); ++i) { - QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = qsettings().value(key).toMap(); - val["preferred"] = 0; - qsettings().setValue(key, val); + const QString key = ui->knownServersList->item(i)->data(Qt::UserRole).toString(); + + bool found = false; + + for (auto& server : servers) { + if (server.name() == key) { + server.setPreferred(0); + found = true; + break; + } } - int count = ui->preferredServersList->count(); + + if (!found) { + log::error("while setting preferred to 0, server '{}' not found", key); + } + } + + const int count = ui->preferredServersList->count(); + for (int i = 0; i < count; ++i) { - QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = qsettings().value(key).toMap(); - val["preferred"] = count - i; - qsettings().setValue(key, val); + const QString key = ui->preferredServersList->item(i)->data(Qt::UserRole).toString(); + const int newPreferred = count - i; + + bool found = false; + + for (auto& server : servers) { + + if (server.name() == key) { + server.setPreferred(newPreferred); + found = true; + break; } - qsettings().endGroup(); + } + + if (!found) { + log::error( + "while setting preference to {}, server '{}' not found", + newPreferred, key); + } + } + + settings().updateServers(servers); } void NexusSettingsTab::on_nexusConnect_clicked() -- cgit v1.3.1 From aff3ee8fcf427c9ff8c554a179222eabec3a95e2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 01:07:03 -0400 Subject: moved preferred servers into ServerList --- src/downloadmanager.cpp | 40 +++++++++++++++++++++++++++++----------- src/downloadmanager.h | 11 +++++++---- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 2 +- src/serverinfo.cpp | 17 +++++++++++++++++ src/serverinfo.h | 2 ++ src/settings.cpp | 17 ----------------- src/settings.h | 16 ---------------- 8 files changed, 57 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 3b084b83..45cfdeed 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -288,9 +288,9 @@ void DownloadManager::setOutputDirectory(const QString &outputDirectory) } -void DownloadManager::setPreferredServers(const std::map &preferredServers) +void DownloadManager::setServers(const ServerList& servers) { - m_PreferredServers = preferredServers; + m_Servers = servers; } @@ -1667,23 +1667,38 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(info->gameName, info->modID, info->fileID, this, qVariantFromValue(test), QString())); } -static int evaluateFileInfoMap(const QVariantMap &map, const std::map &preferredServers) +static int evaluateFileInfoMap( + const QVariantMap &map, + const QList& preferredServers) { - int result = 0; + int preference = 0; + bool found = false; + const auto name = map["short_name"].toString(); - auto preference = preferredServers.find(map["short_name"].toString()); + for (const auto& server : preferredServers) { + if (server.name() == name) { + preference = server.preferred(); + found = true; + break; + } + } - if (preference != preferredServers.end()) { - result += 100 + preference->second * 20; + if (!found) { + log::error("server '{}' not found while sorting by preference", name); + return 0; } - return result; + return 100 + preference * 20; } // sort function to sort by best download server -bool DownloadManager::ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS) +bool DownloadManager::ServerByPreference( + const QList& preferredServers, + const QVariant &LHS, const QVariant &RHS) { - return evaluateFileInfoMap(LHS.toMap(), preferredServers) > evaluateFileInfoMap(RHS.toMap(), preferredServers); + const auto a = evaluateFileInfoMap(LHS.toMap(), preferredServers); + const auto b = evaluateFileInfoMap(RHS.toMap(), preferredServers); + return (a > b); } int DownloadManager::startDownloadURLs(const QStringList &urls) @@ -1732,7 +1747,10 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int return; } - std::sort(resultList.begin(), resultList.end(), boost::bind(&DownloadManager::ServerByPreference, m_PreferredServers, _1, _2)); + std::sort( + resultList.begin(), + resultList.end(), + boost::bind(&DownloadManager::ServerByPreference, m_Servers.getPreferred(), _1, _2)); info->userData["downloadMap"] = resultList; diff --git a/src/downloadmanager.h b/src/downloadmanager.h index feef0eaa..f739f4f0 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #ifndef DOWNLOADMANAGER_H #define DOWNLOADMANAGER_H +#include "serverinfo.h" #include #include #include @@ -174,9 +175,9 @@ public: QString getOutputDirectory() const { return m_OutputDirectory; } /** - * @brief setPreferredServers set the list of preferred servers + * @brief sets the list of servers */ - void setPreferredServers(const std::map &preferredServers); + void setServers(const ServerList& servers); /** * @brief set the list of supported extensions @@ -366,7 +367,9 @@ public: * @param RHS * @return */ - static bool ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS); + static bool ServerByPreference( + const QList& preferredServers, + const QVariant &LHS, const QVariant &RHS); virtual int startDownloadURLs(const QStringList &urls); @@ -548,7 +551,7 @@ private: QVector m_ActiveDownloads; QString m_OutputDirectory; - std::map m_PreferredServers; + ServerList m_Servers; QStringList m_SupportedExtensions; std::set m_RequestIDs; QVector m_AlphabeticalTranslation; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9921ad82..79203d29 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5050,7 +5050,7 @@ void MainWindow::on_actionSettings_triggered() dlManager->setOutputDirectory(settings.getDownloadDirectory()); } } - dlManager->setPreferredServers(settings.getPreferredServers()); + dlManager->setServers(settings.getServers()); if ((settings.getModDirectory() != oldModDirectory) || (settings.displayForeign() != oldDisplayForeign)) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5a8ee4c2..522d28be 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -276,7 +276,7 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); + m_DownloadManager.setServers(m_Settings.getServers()); NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index 67a80b9e..70cdec6d 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -52,6 +52,10 @@ void ServerInfo::setPreferred(int i) void ServerList::add(ServerInfo s) { m_servers.push_back(std::move(s)); + + std::sort(m_servers.begin(), m_servers.end(), [](auto&& a, auto&& b){ + return (a.preferred() < b.preferred()); + }); } ServerList::iterator ServerList::begin() @@ -83,3 +87,16 @@ bool ServerList::empty() const { return m_servers.empty(); } + +ServerList::container ServerList::getPreferred() const +{ + container v; + + for (const auto& server : m_servers) { + if (server.preferred() > 0) { + v.push_back(server); + } + } + + return v; +} diff --git a/src/serverinfo.h b/src/serverinfo.h index 0a8a5028..2e5682fc 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -50,6 +50,8 @@ public: std::size_t size() const; bool empty() const; + container getPreferred() const; + private: container m_servers; }; diff --git a/src/settings.cpp b/src/settings.cpp index 5ddffd8f..c57530e4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -867,23 +867,6 @@ void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) m_Settings.sync(); } -std::map Settings::getPreferredServers() -{ - std::map result; - m_Settings.beginGroup("Servers"); - - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } - } - m_Settings.endGroup(); - - return result; -} - ServerList Settings::getServers() const { ServerList list; diff --git a/src/settings.h b/src/settings.h index 31dbf85c..e7337301 100644 --- a/src/settings.h +++ b/src/settings.h @@ -482,24 +482,8 @@ public: QString language(); void setLanguage(const QString& name); - /** - * @brief register download speed - * @param url complete download url - * @param bytesPerSecond download size in bytes per second - */ void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - - /** - * retrieve a sorted list of preferred servers - */ - std::map getPreferredServers(); - ServerList getServers() const; - - /** - * @brief updates the list of known servers - * @param list of servers from a recent query - */ void updateServers(const ServerList& servers); /** -- cgit v1.3.1 From 896d80d02ccef746ba6598534ce444da2755ae04 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 01:31:56 -0400 Subject: server settings converted to array instead of byte array map moved cleanup to ServerList --- src/serverinfo.cpp | 22 +++++++++++++ src/serverinfo.h | 4 +++ src/settings.cpp | 94 +++++++++++++++++++++++++++++++++++------------------- src/settings.h | 3 +- 4 files changed, 90 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index 70cdec6d..16e65f52 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -1,4 +1,7 @@ #include "serverinfo.h" +#include "log.h" + +using namespace MOBase; ServerInfo::ServerInfo() : ServerInfo({}, false, {}, 0, 0, 0.0) @@ -100,3 +103,22 @@ ServerList::container ServerList::getPreferred() const return v; } + +void ServerList::cleanup() +{ + QDate now = QDate::currentDate(); + + for (auto itor=m_servers.begin(); itor!=m_servers.end(); ) { + const QDate lastSeen = itor->lastSeen(); + + if (lastSeen.daysTo(now) > 30) { + log::debug( + "removing server {} since it hasn't been available for downloads " + "in over a month", itor->name()); + + itor = m_servers.erase(itor); + } else { + ++itor; + } + } +} diff --git a/src/serverinfo.h b/src/serverinfo.h index 2e5682fc..c6e3b640 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -52,6 +52,10 @@ public: container getPreferred() const; + // removes servers that haven't been seen in a while + // + void cleanup(); + private: container m_servers; }; diff --git a/src/settings.cpp b/src/settings.cpp index c57530e4..3a7bda75 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -869,6 +869,50 @@ void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) ServerList Settings::getServers() const { + // servers used to be a map of byte arrays until 2.2.1, it's now an array of + // individual values instead + // + // so post 2.2.1, only one key is returned: "size", the size of the arrays; + // in 2.2.1, one key per server is returned + + // getting the keys + m_Settings.beginGroup("Servers"); + const auto keys = m_Settings.childKeys(); + m_Settings.endGroup(); + + if (!keys.empty() && keys[0] != "size") { + // old format + return getServersFromOldMap(); + } + + + ServerList list; + + const int size = m_Settings.beginReadArray("Servers"); + + for (int i=0; i 30) { - log::debug("removing server {} since it hasn't been available for downloads in over a month", key); - m_Settings.remove(key); - } + ++i; } - m_Settings.endGroup(); - - m_Settings.sync(); + m_Settings.endArray(); } void Settings::addBlacklistPlugin(const QString &fileName) diff --git a/src/settings.h b/src/settings.h index e7337301..810daac2 100644 --- a/src/settings.h +++ b/src/settings.h @@ -484,7 +484,8 @@ public: void setDownloadSpeed(const QString &serverName, int bytesPerSecond); ServerList getServers() const; - void updateServers(const ServerList& servers); + ServerList getServersFromOldMap() const; + void updateServers(ServerList servers); /** * @brief add a plugin that is to be blacklisted -- cgit v1.3.1 From c42e5fb2fec9b20fe6956d8798b85874b2eff73e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 03:01:55 -0400 Subject: changed total speed and count to a list of the last 5 downloads existing servers now merged when retrieving the download links download manager doesn't store the servers any more, queries the settings every time --- src/downloadmanager.cpp | 17 +++++------ src/downloadmanager.h | 17 ----------- src/mainwindow.cpp | 31 ++++++++++++------- src/organizercore.cpp | 1 - src/serverinfo.cpp | 72 +++++++++++++++++++++++++++++++++++++++------ src/serverinfo.h | 21 ++++++++----- src/settings.cpp | 51 ++++++++++++++++++++++---------- src/settingsdialognexus.cpp | 6 ++-- 8 files changed, 143 insertions(+), 73 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 45cfdeed..93ca1608 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -288,12 +288,6 @@ void DownloadManager::setOutputDirectory(const QString &outputDirectory) } -void DownloadManager::setServers(const ServerList& servers) -{ - m_Servers = servers; -} - - void DownloadManager::setSupportedExtensions(const QStringList &extensions) { m_SupportedExtensions = extensions; @@ -1669,7 +1663,7 @@ void DownloadManager::nxmFileInfoAvailable(QString gameName, int modID, int file static int evaluateFileInfoMap( const QVariantMap &map, - const QList& preferredServers) + const ServerList::container& preferredServers) { int preference = 0; bool found = false; @@ -1692,8 +1686,9 @@ static int evaluateFileInfoMap( } // sort function to sort by best download server -bool DownloadManager::ServerByPreference( - const QList& preferredServers, +// +bool ServerByPreference( + const ServerList::container& preferredServers, const QVariant &LHS, const QVariant &RHS) { const auto a = evaluateFileInfoMap(LHS.toMap(), preferredServers); @@ -1747,10 +1742,12 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int return; } + const auto servers = m_OrganizerCore->settings().getServers(); + std::sort( resultList.begin(), resultList.end(), - boost::bind(&DownloadManager::ServerByPreference, m_Servers.getPreferred(), _1, _2)); + boost::bind(&ServerByPreference, servers.getPreferred(), _1, _2)); info->userData["downloadMap"] = resultList; diff --git a/src/downloadmanager.h b/src/downloadmanager.h index f739f4f0..bed1b3cc 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -174,11 +174,6 @@ public: **/ QString getOutputDirectory() const { return m_OutputDirectory; } - /** - * @brief sets the list of servers - */ - void setServers(const ServerList& servers); - /** * @brief set the list of supported extensions * @param extensions list of supported extensions @@ -361,17 +356,6 @@ public: */ void refreshList(); - /** - * @brief Sort function for download servers - * @param LHS - * @param RHS - * @return - */ - static bool ServerByPreference( - const QList& preferredServers, - const QVariant &LHS, const QVariant &RHS); - - virtual int startDownloadURLs(const QStringList &urls); virtual int startDownloadNexusFile(int modID, int fileID); @@ -551,7 +535,6 @@ private: QVector m_ActiveDownloads; QString m_OutputDirectory; - ServerList m_Servers; QStringList m_SupportedExtensions; std::set m_RequestIDs; QVector m_AlphabeticalTranslation; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 79203d29..4c2594b8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5050,7 +5050,6 @@ void MainWindow::on_actionSettings_triggered() dlManager->setOutputDirectory(settings.getDownloadDirectory()); } } - dlManager->setServers(settings.getServers()); if ((settings.getModDirectory() != oldModDirectory) || (settings.displayForeign() != oldDisplayForeign)) { @@ -5904,20 +5903,32 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { - ServerList servers; + auto servers = m_OrganizerCore.settings().getServers(); for (const QVariant &var : resultData.toList()) { const QVariantMap map = var.toMap(); - ServerInfo server( - map["short_name"].toString(), - map["name"].toString().contains("Premium", Qt::CaseInsensitive), - QDate::currentDate(), - map["short_name"].toString().contains("CDN", Qt::CaseInsensitive) ? 1 : 0, - map["downloadCount"].toInt(), - map["downloadSpeed"].toDouble()); + const auto name = map["short_name"].toString(); + const auto isPremium = map["name"].toString().contains("Premium", Qt::CaseInsensitive); + const auto isCDN = map["short_name"].toString().contains("CDN", Qt::CaseInsensitive); - servers.add(std::move(server)); + bool found = false; + + for (auto& server : servers) { + if (server.name() == name) { + // already exists, update + server.setPremium(isPremium); + server.updateLastSeen(); + found = true; + break; + } + } + + if (!found) { + // new server + ServerInfo server(name, isPremium, QDate::currentDate(), isCDN ? 1 : 0, {}); + servers.add(std::move(server)); + } } m_OrganizerCore.settings().updateServers(servers); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 522d28be..ec13ca9c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -276,7 +276,6 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setServers(m_Settings.getServers()); NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); diff --git a/src/serverinfo.cpp b/src/serverinfo.cpp index 16e65f52..aece61da 100644 --- a/src/serverinfo.cpp +++ b/src/serverinfo.cpp @@ -3,17 +3,23 @@ using namespace MOBase; +const std::size_t MaxDownloadCount = 5; + + ServerInfo::ServerInfo() - : ServerInfo({}, false, {}, 0, 0, 0.0) + : ServerInfo({}, false, {}, 0, {}) { } ServerInfo::ServerInfo( QString name, bool premium, QDate last, int preferred, - int count, double speed) : + SpeedList lastDownloads) : m_name(std::move(name)), m_premium(premium), m_lastSeen(std::move(last)), - m_preferred(preferred), m_downloadCount(count), m_downloadSpeed(speed) + m_preferred(preferred), m_lastDownloads(std::move(lastDownloads)) { + if (m_lastDownloads.size() > MaxDownloadCount) { + m_lastDownloads.resize(MaxDownloadCount); + } } const QString& ServerInfo::name() const @@ -26,29 +32,77 @@ bool ServerInfo::isPremium() const return m_premium; } +void ServerInfo::setPremium(bool b) +{ + m_premium = b; +} + const QDate& ServerInfo::lastSeen() const { return m_lastSeen; } +void ServerInfo::updateLastSeen() +{ + m_lastSeen = QDate::currentDate(); +} + int ServerInfo::preferred() const { return m_preferred; } -int ServerInfo::downloadCount() const +void ServerInfo::setPreferred(int i) { - return m_downloadCount; + m_preferred = i; } -double ServerInfo::downloadSpeed() const +const ServerInfo::SpeedList& ServerInfo::lastDownloads() const { - return m_downloadSpeed; + return m_lastDownloads; } -void ServerInfo::setPreferred(int i) +int ServerInfo::averageSpeed() const { - m_preferred = i; + int count = 0; + int total = 0; + + for (const auto& s : m_lastDownloads) { + if (s > 0) { + ++count; + total += s; + } + } + + if (count > 0) { + return static_cast(total) / count; + } + + return 0; +} + +void ServerInfo::addDownload(int bytesPerSecond) +{ + if (bytesPerSecond <= 0) { + log::error( + "trying to add download with {} B/s to server '{}'; ignoring", + bytesPerSecond, m_name); + + return; + } + + if (m_lastDownloads.size() == MaxDownloadCount) { + std::rotate( + m_lastDownloads.begin(), + m_lastDownloads.begin() + 1, + m_lastDownloads.end()); + + m_lastDownloads.back() = bytesPerSecond; + } else { + m_lastDownloads.push_back(bytesPerSecond); + } + + log::debug("added download at {} B/s to server '{}'", bytesPerSecond, m_name); } diff --git a/src/serverinfo.h b/src/serverinfo.h index c6e3b640..af8f77c8 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -8,27 +8,34 @@ class ServerInfo { public: + using SpeedList = std::vector; + ServerInfo(); ServerInfo( QString name, bool premium, QDate lastSeen, int preferred, - int downloadCount, double downloadSpeed); + SpeedList lastDownloads); const QString& name() const; + bool isPremium() const; + void setPremium(bool b); + const QDate& lastSeen() const; - int preferred() const; - int downloadCount() const; - double downloadSpeed() const; + void updateLastSeen(); + int preferred() const; void setPreferred(int i); + const SpeedList& lastDownloads() const; + int averageSpeed() const; + void addDownload(int bytesPerSecond); + private: QString m_name; bool m_premium; QDate m_lastSeen; int m_preferred; - int m_downloadCount; - double m_downloadSpeed; + SpeedList m_lastDownloads; }; Q_DECLARE_METATYPE(ServerInfo) @@ -37,7 +44,7 @@ Q_DECLARE_METATYPE(ServerInfo) class ServerList { public: - using container = QList; + using container = std::vector; using iterator = container::iterator; using const_iterator = container::const_iterator; diff --git a/src/settings.cpp b/src/settings.cpp index 3a7bda75..b11bc61c 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -850,21 +850,21 @@ void Settings::setLanguage(const QString& name) m_Settings.setValue("Settings/language", name); } -void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) +void Settings::setDownloadSpeed(const QString& name, int bytesPerSecond) { - m_Settings.beginGroup("Servers"); + auto servers = getServers(); - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - if (serverKey == serverName) { - data["downloadCount"] = data["downloadCount"].toInt() + 1; - data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); - m_Settings.setValue(serverKey, data); + for (auto& server : servers) { + if (server.name() == name) { + server.addDownload(bytesPerSecond); + updateServers(servers); + return; } } - m_Settings.endGroup(); - m_Settings.sync(); + log::error( + "server '{}' not found while trying to add a download with bps {}", + name, bytesPerSecond); } ServerList Settings::getServers() const @@ -885,6 +885,7 @@ ServerList Settings::getServers() const return getServersFromOldMap(); } + // post 2.2.1 format, array of values ServerList list; @@ -893,13 +894,22 @@ ServerList Settings::getServers() const for (int i=0; i 0) { + lastDownloads.push_back(bytesPerSecond); + } + } + ServerInfo server( m_Settings.value("name").toString(), m_Settings.value("premium").toBool(), QDate::fromString(m_Settings.value("lastSeen").toString(), Qt::ISODate), m_Settings.value("preferred").toInt(), - m_Settings.value("downloadCount").toInt(), - m_Settings.value("downloadSpeed").toDouble()); + lastDownloads); list.add(std::move(server)); } @@ -925,8 +935,10 @@ ServerList Settings::getServersFromOldMap() const data["premium"].toBool(), data["lastSeen"].toDate(), data["preferred"].toInt(), - data["downloadCount"].toInt(), - data["downloadSpeed"].toDouble()); + {}); + + // ignoring download count and speed, it's now a list of values instead of + // a total list.add(std::move(server)); } @@ -955,8 +967,15 @@ void Settings::updateServers(ServerList servers) m_Settings.setValue("premium", server.isPremium()); m_Settings.setValue("lastSeen", server.lastSeen().toString(Qt::ISODate)); m_Settings.setValue("preferred", server.preferred()); - m_Settings.setValue("downloadCount", server.downloadCount()); - m_Settings.setValue("downloadSpeed", server.downloadSpeed()); + + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } + } + + m_Settings.setValue("lastDownloads", lastDownloads.trimmed()); ++i; } diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 926ea9a6..f2bd3ab5 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -87,9 +87,9 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) descriptor += QStringLiteral(" (automatic)"); } - if (server.downloadSpeed() > 0 && server.downloadCount() > 0) { - const int bps = static_cast(server.downloadSpeed() / server.downloadCount()); - descriptor += QString(" (%1 kbps)").arg(bps / 1024); + const auto averageSpeed = server.averageSpeed(); + if (averageSpeed > 0) { + descriptor += QString(" (%1 kbps)").arg(averageSpeed / 1024); } QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); -- cgit v1.3.1 From 2eee72da6815f9d5c643b58c95f633e69da5150a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 04:21:06 -0400 Subject: moved code for byte sizes and speed to uibase added scoped classes for QSettings groups and arrays servers logged on startup --- src/downloadlist.cpp | 21 +--- src/downloadlist.h | 2 - src/downloadmanager.cpp | 19 +--- src/settings.cpp | 270 +++++++++++++++++++++++++++++--------------- src/settingsdialognexus.cpp | 2 +- 5 files changed, 184 insertions(+), 130 deletions(-) (limited to 'src') diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 36bc2b7f..6957f270 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "downloadlist.h" #include "downloadmanager.h" +#include #include #include #include @@ -121,7 +122,7 @@ QVariant DownloadList::data(const QModelIndex &index, int role) const return QString("%1").arg(m_Manager->getModID(index.row())); } } - case COL_SIZE: return sizeFormat(m_Manager->getFileSize(index.row())); + case COL_SIZE: return MOBase::localizedByteSize(m_Manager->getFileSize(index.row())); case COL_FILETIME: return m_Manager->getFileTime(index.row()); case COL_STATUS: switch (m_Manager->getState(index.row())) { @@ -195,21 +196,3 @@ void DownloadList::update(int row) else log::error("invalid row {} in download list, update failed", row); } - -QString DownloadList::sizeFormat(quint64 size) const -{ - qreal calc = size; - QStringList list; - list << "MB" << "GB" << "TB"; - - QStringListIterator i(list); - QString unit("KB"); - - calc /= 1024.0; - while (calc >= 1024.0 && i.hasNext()) { - unit = i.next(); - calc /= 1024.0; - } - - return QString().setNum(calc, 'f', 2) + " " + unit; -} diff --git a/src/downloadlist.h b/src/downloadlist.h index 6f63f0c8..51ab4541 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -99,8 +99,6 @@ private: DownloadManager *m_Manager; bool m_MetaDisplay; - - QString sizeFormat(quint64 size) const; }; #endif // DOWNLOADLIST_H diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 93ca1608..a5dc164c 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1441,22 +1441,11 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) std::get<4>(info->m_SpeedDiff) = ((calc*0.5) + (std::get<4>(info->m_SpeedDiff)*1.5)) / 2; // calculate the download speed - double speed = (std::get<4>(info->m_SpeedDiff) * 1000.0) / (5 * 1000); + const double speed = (std::get<4>(info->m_SpeedDiff) * 1000.0) / (5 * 1000); - QString unit; - if (speed < 1000) { - unit = "B/s"; - } - else if (speed < 1000*1024) { - speed /= 1024; - unit = "KB/s"; - } - else { - speed /= 1024 * 1024; - unit = "MB/s"; - } - - info->m_Progress.second = QString::fromLatin1("%1% - %2 %3").arg(info->m_Progress.first).arg(QString::number(speed, 'f', 1)).arg(unit); + info->m_Progress.second = QString::fromLatin1("%1% - %2") + .arg(info->m_Progress.first) + .arg(MOBase::localizedByteSpeed(speed)); TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal); emit update(index); diff --git a/src/settings.cpp b/src/settings.cpp index b11bc61c..406544f7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -27,6 +27,78 @@ along with Mod Organizer. If not, see . using namespace MOBase; +class ScopedGroup +{ +public: + ScopedGroup(QSettings& s, const QString& name) + : m_settings(s) + { + m_settings.beginGroup(name); + } + + ~ScopedGroup() + { + m_settings.endGroup(); + } + + ScopedGroup(const ScopedGroup&) = delete; + ScopedGroup& operator=(const ScopedGroup&) = delete; + +private: + QSettings& m_settings; +}; + + +class ScopedReadArray +{ +public: + ScopedReadArray(QSettings& s, const QString& name) + : m_settings(s), m_count(0) + { + m_count = m_settings.beginReadArray(name); + } + + ~ScopedReadArray() + { + m_settings.endArray(); + } + + ScopedReadArray(const ScopedReadArray&) = delete; + ScopedReadArray& operator=(const ScopedReadArray&) = delete; + + int count() const + { + return m_count; + } + +private: + QSettings& m_settings; + int m_count; +}; + + +class ScopedWriteArray +{ +public: + ScopedWriteArray(QSettings& s, const QString& name) + : m_settings(s) + { + m_settings.beginWriteArray(name); + } + + ~ScopedWriteArray() + { + m_settings.endArray(); + } + + ScopedWriteArray(const ScopedWriteArray&) = delete; + ScopedWriteArray& operator=(const ScopedWriteArray&) = delete; + +private: + QSettings& m_settings; +}; + + template std::optional getOptional( const QSettings& s, const QString& name, std::optional def={}) @@ -206,19 +278,21 @@ void Settings::processUpdates( } if (lastVersion < QVersionNumber(2, 2, 0)) { - m_Settings.beginGroup("Settings"); - m_Settings.remove("steam_password"); - m_Settings.remove("nexus_username"); - m_Settings.remove("nexus_password"); - m_Settings.remove("nexus_login"); - m_Settings.remove("nexus_api_key"); - m_Settings.remove("ask_for_nexuspw"); - m_Settings.remove("nmm_version"); - m_Settings.endGroup(); - - m_Settings.beginGroup("Servers"); - m_Settings.remove(""); - m_Settings.endGroup(); + { + ScopedGroup sg(m_Settings, "Settings"); + m_Settings.remove("steam_password"); + m_Settings.remove("nexus_username"); + m_Settings.remove("nexus_password"); + m_Settings.remove("nexus_login"); + m_Settings.remove("nexus_api_key"); + m_Settings.remove("ask_for_nexuspw"); + m_Settings.remove("nmm_version"); + } + + { + ScopedGroup sg(m_Settings, "Servers"); + m_Settings.remove(""); + } } if (lastVersion < QVersionNumber(2, 2, 1)) { @@ -251,12 +325,12 @@ void Settings::clearPlugins() m_PluginSettings.clear(); m_PluginBlacklist.clear(); - int count = m_Settings.beginReadArray("pluginBlacklist"); - for (int i = 0; i < count; ++i) { + + ScopedReadArray sra(m_Settings, "pluginBlacklist"); + for (int i = 0; i < sra.count(); ++i) { m_Settings.setArrayIndex(i); m_PluginBlacklist.insert(m_Settings.value("name").toString()); } - m_Settings.endArray(); } bool Settings::pluginBlacklisted(const QString &fileName) const @@ -876,46 +950,50 @@ ServerList Settings::getServers() const // in 2.2.1, one key per server is returned // getting the keys - m_Settings.beginGroup("Servers"); - const auto keys = m_Settings.childKeys(); - m_Settings.endGroup(); + QStringList keys; + + { + ScopedGroup sg(m_Settings, "Servers"); + keys = m_Settings.childKeys(); + } if (!keys.empty() && keys[0] != "size") { // old format return getServersFromOldMap(); } + // post 2.2.1 format, array of values ServerList list; - const int size = m_Settings.beginReadArray("Servers"); + { + ScopedReadArray sra(m_Settings, "Servers"); - for (int i=0; i 0) { - lastDownloads.push_back(bytesPerSecond); + const auto lastDownloadsString = m_Settings.value("lastDownloads").toString(); + for (const auto& s : lastDownloadsString.split(" ")) { + const auto bytesPerSecond = s.toInt(); + if (bytesPerSecond > 0) { + lastDownloads.push_back(bytesPerSecond); + } } - } - ServerInfo server( - m_Settings.value("name").toString(), - m_Settings.value("premium").toBool(), - QDate::fromString(m_Settings.value("lastSeen").toString(), Qt::ISODate), - m_Settings.value("preferred").toInt(), - lastDownloads); + ServerInfo server( + m_Settings.value("name").toString(), + m_Settings.value("premium").toBool(), + QDate::fromString(m_Settings.value("lastSeen").toString(), Qt::ISODate), + m_Settings.value("preferred").toInt(), + lastDownloads); - list.add(std::move(server)); + list.add(std::move(server)); + } } - m_Settings.endArray(); - return list; } @@ -924,8 +1002,7 @@ ServerList Settings::getServersFromOldMap() const // for 2.2.1 and before ServerList list; - - m_Settings.beginGroup("Servers"); + ScopedGroup sg(m_Settings, "Servers"); for (const QString &serverKey : m_Settings.childKeys()) { QVariantMap data = m_Settings.value(serverKey).toMap(); @@ -943,8 +1020,6 @@ ServerList Settings::getServersFromOldMap() const list.add(std::move(server)); } - m_Settings.endGroup(); - return list; } @@ -953,34 +1028,35 @@ void Settings::updateServers(ServerList servers) // clean up unavailable servers servers.cleanup(); - m_Settings.beginGroup("Servers"); - m_Settings.remove(""); - m_Settings.endGroup(); - - m_Settings.beginWriteArray("Servers"); - - int i=0; - for (const auto& server : servers) { - m_Settings.setArrayIndex(i); - - m_Settings.setValue("name", server.name()); - m_Settings.setValue("premium", server.isPremium()); - m_Settings.setValue("lastSeen", server.lastSeen().toString(Qt::ISODate)); - m_Settings.setValue("preferred", server.preferred()); + { + ScopedGroup sg(m_Settings, "Servers"); + m_Settings.remove(""); + } - QString lastDownloads; - for (const auto& speed : server.lastDownloads()) { - if (speed > 0) { - lastDownloads += QString("%1 ").arg(speed); + { + ScopedWriteArray swa(m_Settings, "Servers"); + + int i=0; + for (const auto& server : servers) { + m_Settings.setArrayIndex(i); + + m_Settings.setValue("name", server.name()); + m_Settings.setValue("premium", server.isPremium()); + m_Settings.setValue("lastSeen", server.lastSeen().toString(Qt::ISODate)); + m_Settings.setValue("preferred", server.preferred()); + + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } } - } - m_Settings.setValue("lastDownloads", lastDownloads.trimmed()); + m_Settings.setValue("lastDownloads", lastDownloads.trimmed()); - ++i; + ++i; + } } - - m_Settings.endArray(); } void Settings::addBlacklistPlugin(const QString &fileName) @@ -992,23 +1068,22 @@ void Settings::addBlacklistPlugin(const QString &fileName) void Settings::writePluginBlacklist() { m_Settings.remove("pluginBlacklist"); - m_Settings.beginWriteArray("pluginBlacklist"); + + ScopedWriteArray swa(m_Settings, "pluginBlacklist"); int idx = 0; for (const QString &plugin : m_PluginBlacklist) { m_Settings.setArrayIndex(idx++); m_Settings.setValue("name", plugin); } - - m_Settings.endArray(); } std::map Settings::getRecentDirectories() const { std::map map; - const int size = m_Settings.beginReadArray("recentDirectories"); + ScopedReadArray sra(m_Settings, "recentDirectories"); - for (int i=0; i Settings::getRecentDirectories() const } } - m_Settings.endArray(); - return map; } void Settings::setRecentDirectories(const std::map& map) { m_Settings.remove("recentDirectories"); - m_Settings.beginWriteArray("recentDirectories"); + + ScopedWriteArray swa(m_Settings, "recentDirectories"); int index = 0; for (auto&& p : map) { @@ -1037,16 +1111,14 @@ void Settings::setRecentDirectories(const std::map& map) ++index; } - - m_Settings.endArray(); } std::vector> Settings::getExecutables() const { - const int count = m_Settings.beginReadArray("customExecutables"); + ScopedReadArray sra(m_Settings, "customExecutables"); std::vector> v; - for (int i=0; i map; @@ -1059,15 +1131,14 @@ std::vector> Settings::getExecutables() const v.push_back(map); } - m_Settings.endArray(); - return v; } void Settings::setExecutables(const std::vector>& v) { m_Settings.remove("customExecutables"); - m_Settings.beginWriteArray("customExecutables"); + + ScopedWriteArray swa(m_Settings, "customExecutables"); int i = 0; @@ -1080,8 +1151,6 @@ void Settings::setExecutables(const std::vector>& v) ++i; } - - m_Settings.endArray(); } bool Settings::isTutorialCompleted(const QString& windowName) const @@ -1154,9 +1223,8 @@ void Settings::setQuestionFileButton( void Settings::resetQuestionButtons() { - m_Settings.beginGroup("DialogChoices"); + ScopedGroup sg(m_Settings, "DialogChoices"); m_Settings.remove(""); - m_Settings.endGroup(); } std::optional Settings::getIndex(const QComboBox* cb) const @@ -1248,17 +1316,34 @@ void Settings::dump() const log::debug("settings:"); - m_Settings.beginGroup("Settings"); + { + ScopedGroup sg(m_Settings, "Settings"); - for (auto k : m_Settings.allKeys()) { - if (ignore.contains(k, Qt::CaseInsensitive)) { - continue; - } + for (auto k : m_Settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } - log::debug(" . {}={}", k, m_Settings.value(k).toString()); + log::debug(" . {}={}", k, m_Settings.value(k).toString()); + } } - m_Settings.endGroup(); + log::debug("servers:"); + + for (const auto& server : getServers()) { + QString lastDownloads; + for (auto speed : server.lastDownloads()) { + lastDownloads += QString("%1 ").arg(speed); + } + + log::debug( + " . {} premium={} lastSeen={} preferred={} lastDownloads={}", + server.name(), + server.isPremium() ? "yes" : "no", + server.lastSeen().toString(Qt::ISODate), + server.preferred(), + lastDownloads.trimmed()); + } } @@ -1278,9 +1363,8 @@ void GeometrySettings::resetIfNeeded() return; } - m_Settings.beginGroup("geometry"); + ScopedGroup sg(m_Settings, "geometry"); m_Settings.remove(""); - m_Settings.endGroup(); } void GeometrySettings::saveGeometry(const QWidget* w) diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index f2bd3ab5..3de1a6ba 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -89,7 +89,7 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) const auto averageSpeed = server.averageSpeed(); if (averageSpeed > 0) { - descriptor += QString(" (%1 kbps)").arg(averageSpeed / 1024); + descriptor += QString(" (%1)").arg(MOBase::localizedByteSpeed(averageSpeed)); } QListWidgetItem *newItem = new ServerItem(descriptor, Qt::UserRole + 1); -- cgit v1.3.1 From 8708265f69491b807b1dc56d0804230b80ffbdb8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 26 Aug 2019 04:52:15 -0400 Subject: moved setting paths to Settings --- src/settings.cpp | 97 +++++++++++++++++++++++++++++++++++++-------- src/settings.h | 37 +++++------------ src/settingsdialogpaths.cpp | 36 ++++++++--------- 3 files changed, 107 insertions(+), 63 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 406544f7..34b1b4ac 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -492,34 +492,108 @@ QString Settings::getConfigurablePath(const QString &key, const QString &def, bool resolve) const { + const QString settingName = "Settings/" + key; + QString result = QDir::fromNativeSeparators( - m_Settings.value(QString("settings/") + key, QString("%BASE_DIR%/") + def) - .toString()); + m_Settings.value(settingName, QString("%BASE_DIR%/") + def).toString()); + if (resolve) { result.replace("%BASE_DIR%", getBaseDirectory()); } + return result; } +void Settings::setConfigurablePath(const QString &key, const QString& path) +{ + const QString settingName = "Settings/" + key; + + if (path.isEmpty()) { + m_Settings.remove(settingName); + } else { + m_Settings.setValue(settingName, path); + } +} + QString Settings::getBaseDirectory() const { return QDir::fromNativeSeparators(m_Settings.value( - "settings/base_directory", qApp->property("dataPath").toString()).toString()); + "settings/base_directory", + qApp->property("dataPath").toString()).toString()); } QString Settings::getDownloadDirectory(bool resolve) const { - return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), resolve); + return getConfigurablePath( + "download_directory", + ToQString(AppConfig::downloadPath()), + resolve); } QString Settings::getCacheDirectory(bool resolve) const { - return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), resolve); + return getConfigurablePath( + "cache_directory", + ToQString(AppConfig::cachePath()), + resolve); } QString Settings::getModDirectory(bool resolve) const { - return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); + return getConfigurablePath( + "mod_directory", + ToQString(AppConfig::modsPath()), + resolve); +} + +QString Settings::getProfileDirectory(bool resolve) const +{ + return getConfigurablePath( + "profiles_directory", + ToQString(AppConfig::profilesPath()), + resolve); +} + +QString Settings::getOverwriteDirectory(bool resolve) const +{ + return getConfigurablePath( + "overwrite_directory", + ToQString(AppConfig::overwritePath()), + resolve); +} + +void Settings::setBaseDirectory(const QString& path) +{ + if (path.isEmpty()) { + m_Settings.remove("Settings/base_directory"); + } else { + m_Settings.setValue("Settings/base_directory", path); + } +} + +void Settings::setDownloadDirectory(const QString& path) +{ + setConfigurablePath("download_directory", path); +} + +void Settings::setModDirectory(const QString& path) +{ + setConfigurablePath("mod_directory", path); +} + +void Settings::setCacheDirectory(const QString& path) +{ + setConfigurablePath("cache_directory", path); +} + +void Settings::setProfileDirectory(const QString& path) +{ + setConfigurablePath("profiles_directory", path); +} + +void Settings::setOverwriteDirectory(const QString& path) +{ + setConfigurablePath("overwrite_directory", path); } std::optional Settings::getManagedGameDirectory() const @@ -629,17 +703,6 @@ void Settings::removePreviousSeparatorColor() m_Settings.remove("previousSeparatorColor"); } -QString Settings::getProfileDirectory(bool resolve) const -{ - return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); -} - -QString Settings::getOverwriteDirectory(bool resolve) const -{ - return getConfigurablePath("overwrite_directory", - ToQString(AppConfig::overwritePath()), resolve); -} - bool Settings::getNexusApiKey(QString &apiKey) const { QString tempKey = deObfuscate("APIKEY"); diff --git a/src/settings.h b/src/settings.h index 810daac2..698cfe21 100644 --- a/src/settings.h +++ b/src/settings.h @@ -199,26 +199,19 @@ public: **/ QString getSteamAppID() const; - /** - * retrieves the base directory under which the other directories usually - * reside - */ QString getBaseDirectory() const; - - /** - * retrieve the directory where downloads are stored (with native separators) - **/ QString getDownloadDirectory(bool resolve = true) const; - - /** - * retrieve the directory where mods are stored (with native separators) - **/ QString getModDirectory(bool resolve = true) const; - - /** - * retrieve the directory where the web cache is stored (with native separators) - **/ QString getCacheDirectory(bool resolve = true) const; + QString getProfileDirectory(bool resolve = true) const; + QString getOverwriteDirectory(bool resolve = true) const; + + void setBaseDirectory(const QString& path); + void setDownloadDirectory(const QString& path); + void setModDirectory(const QString& path); + void setCacheDirectory(const QString& path); + void setProfileDirectory(const QString& path); + void setOverwriteDirectory(const QString& path); /** * retrieve the directory where the managed game is stored (with native separators) @@ -292,17 +285,6 @@ public: const ColorSettings& colors() const; - /** - * retrieve the directory where profiles stored (with native separators) - **/ - QString getProfileDirectory(bool resolve = true) const; - - /** - * retrieve the directory were new files are stored that can't be assigned - * to a mod (with native separators) - */ - QString getOverwriteDirectory(bool resolve = true) const; - /** * @return true if the user has set up automatic login to nexus **/ @@ -558,6 +540,7 @@ private: void readPluginBlacklist(); QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; + void setConfigurablePath(const QString &key, const QString& path); }; #endif // SETTINGS_H diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 290ceeb3..32aaf4bf 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -40,22 +40,22 @@ PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) void PathsSettingsTab::update() { - typedef std::tuple Directory; + using Setter = void (Settings::*)(const QString&); + using Directory = std::tuple; QString basePath = settings().getBaseDirectory(); for (const Directory &dir :{ - Directory{ui->downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, - Directory{ui->cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()}, - Directory{ui->modDirEdit->text(), "mod_directory", AppConfig::modsPath()}, - Directory{ui->overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()}, - Directory{ui->profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()} + Directory{ui->downloadDirEdit->text(), &Settings::setDownloadDirectory, AppConfig::downloadPath()}, + Directory{ui->cacheDirEdit->text(), &Settings::setCacheDirectory, AppConfig::cachePath()}, + Directory{ui->modDirEdit->text(), &Settings::setModDirectory, AppConfig::modsPath()}, + Directory{ui->overwriteDirEdit->text(), &Settings::setOverwriteDirectory, AppConfig::overwritePath()}, + Directory{ui->profilesDirEdit->text(), &Settings::setProfileDirectory, AppConfig::profilesPath()} }) { - QString path, settingsKey; + QString path; + Setter setter; std::wstring defaultName; - std::tie(path, settingsKey, defaultName) = dir; - - settingsKey = QString("Settings/%1").arg(settingsKey); + std::tie(path, setter, defaultName) = dir; QString realPath = path; realPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); @@ -69,25 +69,23 @@ void PathsSettingsTab::update() } } - if (QFileInfo(realPath) - != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - qsettings().setValue(settingsKey, path); + if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { + (settings().*setter)(path); } else { - qsettings().remove(settingsKey); + (settings().*setter)(""); } } - if (QFileInfo(ui->baseDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString())) { - qsettings().setValue("Settings/base_directory", ui->baseDirEdit->text()); + if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { + settings().setBaseDirectory(ui->baseDirEdit->text()); } else { - qsettings().remove("Settings/base_directory"); + settings().setBaseDirectory(""); } QFileInfo oldGameExe(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); QFileInfo newGameExe(ui->managedGameDirEdit->text()); if (oldGameExe != newGameExe) { - qsettings().setValue("gamePath", newGameExe.absolutePath()); + settings().setManagedGameDirectory(newGameExe.absolutePath()); } } -- cgit v1.3.1 From a174d4a2aa3d07c6a3c4bedfdf77471f71ec1dba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 09:22:45 -0400 Subject: moved plugins to settings --- src/organizercore.cpp | 10 +- src/plugincontainer.cpp | 6 +- src/settings.cpp | 264 ++++++++++++++++++++++++++---------------- src/settings.h | 123 +++++++------------- src/settingsdialogplugins.cpp | 28 +++-- 5 files changed, 228 insertions(+), 203 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index ec13ca9c..af0cf969 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -512,7 +512,7 @@ void OrganizerCore::disconnectPlugins() m_DownloadManager.setPluginContainer(nullptr); m_ModList.setPluginContainer(nullptr); - m_Settings.clearPlugins(); + m_Settings.plugins().clearPlugins(); m_GamePlugin = nullptr; m_PluginContainer = nullptr; } @@ -864,26 +864,26 @@ void OrganizerCore::modDataChanged(MOBase::IModInterface *) QVariant OrganizerCore::pluginSetting(const QString &pluginName, const QString &key) const { - return m_Settings.pluginSetting(pluginName, key); + return m_Settings.plugins().pluginSetting(pluginName, key); } void OrganizerCore::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - m_Settings.setPluginSetting(pluginName, key, value); + m_Settings.plugins().setPluginSetting(pluginName, key, value); } QVariant OrganizerCore::persistent(const QString &pluginName, const QString &key, const QVariant &def) const { - return m_Settings.pluginPersistent(pluginName, key, def); + return m_Settings.plugins().pluginPersistent(pluginName, key, def); } void OrganizerCore::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - m_Settings.setPluginPersistent(pluginName, key, value, sync); + m_Settings.plugins().setPluginPersistent(pluginName, key, value, sync); } QString OrganizerCore::pluginDataPath() const diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 62cdff1e..16a77387 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -95,7 +95,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) return false; } plugin->setProperty("filename", fileName); - m_Organizer->settings().registerPlugin(pluginObj); + m_Organizer->settings().plugins().registerPlugin(pluginObj); } { // diagnosis plugin @@ -266,7 +266,7 @@ void PluginContainer::loadPlugins() "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " "The plugin may be able to recover from the problem)").arg(fileName), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Organizer->settings().addBlacklistPlugin(fileName); + m_Organizer->settings().plugins().addBlacklistPlugin(fileName); } loadCheck.close(); } @@ -279,7 +279,7 @@ void PluginContainer::loadPlugins() while (iter.hasNext()) { iter.next(); - if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) { + if (m_Organizer->settings().plugins().pluginBlacklisted(iter.fileName())) { log::debug("plugin \"{}\" blacklisted", iter.fileName()); continue; } diff --git a/src/settings.cpp b/src/settings.cpp index 34b1b4ac..072318a2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -242,7 +242,7 @@ Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) : m_Settings(path, QSettings::IniFormat), - m_Geometry(m_Settings), m_Colors(m_Settings) + m_Geometry(m_Settings), m_Colors(m_Settings), m_Plugins(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); @@ -319,25 +319,6 @@ QString Settings::getFilename() const return m_Settings.fileName(); } -void Settings::clearPlugins() -{ - m_Plugins.clear(); - m_PluginSettings.clear(); - - m_PluginBlacklist.clear(); - - ScopedReadArray sra(m_Settings, "pluginBlacklist"); - for (int i = 0; i < sra.count(); ++i) { - m_Settings.setArrayIndex(i); - m_PluginBlacklist.insert(m_Settings.value("name").toString()); - } -} - -bool Settings::pluginBlacklisted(const QString &fileName) const -{ - return m_PluginBlacklist.contains(fileName); -} - void Settings::registerAsNXMHandler(bool force) { const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; @@ -371,24 +352,6 @@ void Settings::managedGameChanged(IPluginGame const *gamePlugin) m_GamePlugin = gamePlugin; } -void Settings::registerPlugin(IPlugin *plugin) -{ - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QVariantMap()); - m_PluginDescriptions.insert(plugin->name(), QVariantMap()); - for (const PluginSetting &setting : plugin->settings()) { - QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); - if (!temp.convert(setting.defaultValue.type())) { - log::warn( - "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", - temp.toString(), setting.key, plugin->name()); - temp = setting.defaultValue; - } - m_PluginSettings[plugin->name()][setting.key] = temp; - m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)").arg(setting.description).arg(setting.defaultValue.toString()); - } -} - bool Settings::obfuscate(const QString key, const QString data) { QString finalKey("ModOrganizer2_" + key); @@ -921,51 +884,6 @@ bool Settings::archiveParsing() const return m_Settings.value("Settings/archive_parsing_experimental", false).toBool(); } -QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } - - return *iterSetting; -} - -void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); -} - -QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const -{ - if (!m_PluginSettings.contains(pluginName)) { - return def; - } - return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); -} - -void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) -{ - if (!m_PluginSettings.contains(pluginName)) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); - if (sync) { - m_Settings.sync(); - } -} - QString Settings::language() { QString result = m_Settings.value("Settings/language", "").toString(); @@ -1122,24 +1040,6 @@ void Settings::updateServers(ServerList servers) } } -void Settings::addBlacklistPlugin(const QString &fileName) -{ - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); -} - -void Settings::writePluginBlacklist() -{ - m_Settings.remove("pluginBlacklist"); - - ScopedWriteArray swa(m_Settings, "pluginBlacklist"); - int idx = 0; - for (const QString &plugin : m_PluginBlacklist) { - m_Settings.setArrayIndex(idx++); - m_Settings.setValue("name", plugin); - } -} - std::map Settings::getRecentDirectories() const { std::map map; @@ -1365,6 +1265,16 @@ const ColorSettings& Settings::colors() const return m_Colors; } +PluginSettings& Settings::plugins() +{ + return m_Plugins; +} + +const PluginSettings& Settings::plugins() const +{ + return m_Plugins; +} + QSettings::Status Settings::sync() const { m_Settings.sync(); @@ -1772,6 +1682,158 @@ void ColorSettings::setPluginListContained(const QColor& c) } +PluginSettings::PluginSettings(QSettings& settings) + : m_Settings(settings) +{ +} + +void PluginSettings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); + + m_PluginBlacklist.clear(); + + ScopedReadArray sra(m_Settings, "pluginBlacklist"); + for (int i = 0; i < sra.count(); ++i) { + m_Settings.setArrayIndex(i); + m_PluginBlacklist.insert(m_Settings.value("name").toString()); + } +} + +void PluginSettings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + m_PluginSettings.insert(plugin->name(), QVariantMap()); + m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + for (const PluginSetting &setting : plugin->settings()) { + QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); + if (!temp.convert(setting.defaultValue.type())) { + log::warn( + "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", + temp.toString(), setting.key, plugin->name()); + temp = setting.defaultValue; + } + m_PluginSettings[plugin->name()][setting.key] = temp; + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)").arg(setting.description).arg(setting.defaultValue.toString()); + } +} + +bool PluginSettings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); +} + +QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + return QVariant(); + } + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + return QVariant(); + } + + return *iterSetting; +} + +void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); +} + +QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); +} + +void PluginSettings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + if (!m_PluginSettings.contains(pluginName)) { + throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); + if (sync) { + m_Settings.sync(); + } +} + +void PluginSettings::addBlacklistPlugin(const QString &fileName) +{ + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); +} + +void PluginSettings::writePluginBlacklist() +{ + m_Settings.remove("pluginBlacklist"); + + ScopedWriteArray swa(m_Settings, "pluginBlacklist"); + int idx = 0; + for (const QString &plugin : m_PluginBlacklist) { + m_Settings.setArrayIndex(idx++); + m_Settings.setValue("name", plugin); + } +} + +QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const +{ + return m_PluginSettings[pluginName]; +} + +void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) +{ + m_PluginSettings[pluginName] = map; +} + +QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const +{ + return m_PluginDescriptions[pluginName]; +} + +void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) +{ + m_PluginDescriptions[pluginName] = map; +} + +const QSet& PluginSettings::pluginBlacklist() const +{ + return m_PluginBlacklist; +} + +void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) +{ + m_PluginBlacklist.clear(); + + for (const auto& name : pluginNames) { + m_PluginBlacklist.insert(name); + } +} + +void PluginSettings::save() +{ + for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { + const auto key = "Plugins/" + iterPlugins.key() + "/" + iterSettings.key(); + m_Settings.setValue(key, iterSettings.value()); + } + } + + writePluginBlacklist(); +} + + GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) : m_settings(s), m_dialog(dialog) { diff --git a/src/settings.h b/src/settings.h index 698cfe21..fc33e0de 100644 --- a/src/settings.h +++ b/src/settings.h @@ -128,6 +128,46 @@ private: }; +class PluginSettings +{ +public: + PluginSettings(QSettings& settings); + + void clearPlugins(); + void registerPlugin(MOBase::IPlugin *plugin); + void addPluginSettings(const std::vector &plugins); + + QVariant pluginSetting(const QString &pluginName, const QString &key) const; + void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; + void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); + void addBlacklistPlugin(const QString &fileName); + bool pluginBlacklisted(const QString &fileName) const; + void setPluginBlacklist(const QStringList& pluginNames); + std::vector plugins() const { return m_Plugins; } + + QVariantMap pluginSettings(const QString &pluginName) const; + void setPluginSettings(const QString &pluginName, const QVariantMap& map); + + QVariantMap pluginDescriptions(const QString &pluginName) const; + void pluginDescriptions(const QString &pluginName, const QVariantMap& map); + + const QSet& pluginBlacklist() const; + + void save(); + +private: + QSettings& m_Settings; + std::vector m_Plugins; + QMap m_PluginSettings; + QMap m_PluginDescriptions; + QSet m_PluginBlacklist; + + void readPluginBlacklist(); + void writePluginBlacklist(); +}; + + enum class EndorsementState { Accepted = 1, @@ -158,23 +198,6 @@ public: QString getFilename() const; - /** - * unregister all plugins from settings - */ - void clearPlugins(); - - /** - * @brief register plugin to be configurable - * @param plugin the plugin to register - * @return true if the plugin may be registered, false if it is blacklisted - */ - void registerPlugin(MOBase::IPlugin *plugin); - - /** - * set up the settings for the specified plugins - **/ - void addPluginSettings(const std::vector &plugins); - /** * @return true if the user wants unchecked plugins (esp, esm) should be hidden from * the virtual dat adirectory @@ -284,6 +307,9 @@ public: ColorSettings& colors(); const ColorSettings& colors() const; + PluginSettings& plugins(); + const PluginSettings& plugins() const; + /** * @return true if the user has set up automatic login to nexus @@ -422,42 +448,6 @@ public: QSettings &directInterface() { return m_Settings; } const QSettings &directInterface() const { return m_Settings; } - /** - * @brief retrieve a setting for one of the installed plugins - * @param pluginName name of the plugin - * @param key name of the setting to retrieve - * @return the requested value as a QVariant - * @note an invalid QVariant is returned if the the plugin/setting is not declared - */ - QVariant pluginSetting(const QString &pluginName, const QString &key) const; - - /** - * @brief set a setting for one of the installed mods - * @param pluginName name of the plugin - * @param key name of the setting to change - * @param value the new value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); - - /** - * @brief retrieve a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param def default value to return if the value is not set - * @return the requested value - */ - QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; - - /** - * @brief set a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param value value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); - /** * @return short code of the configured language (corresponding to the translation files) */ @@ -469,24 +459,6 @@ public: ServerList getServersFromOldMap() const; void updateServers(ServerList servers); - /** - * @brief add a plugin that is to be blacklisted - * @param fileName name of the plugin to blacklist - */ - void addBlacklistPlugin(const QString &fileName); - - /** - * @brief test if a plugin is blacklisted and shouldn't be loaded - * @param fileName name of the plugin - * @return true if the file is blacklisted - */ - bool pluginBlacklisted(const QString &fileName) const; - - /** - * @return all loaded MO plugins - */ - std::vector plugins() const { return m_Plugins; } - bool usePrereleases() const; void setUsePrereleases(bool b); @@ -513,12 +485,6 @@ public: void dump() const; - // temp - QMap m_PluginSettings; - QMap m_PluginDescriptions; - QSet m_PluginBlacklist; - void writePluginBlacklist(); - public slots: void managedGameChanged(MOBase::IPluginGame const *gamePlugin); @@ -532,13 +498,12 @@ private: mutable QSettings m_Settings; GeometrySettings m_Geometry; ColorSettings m_Colors; + PluginSettings m_Plugins; LoadMechanism m_LoadMechanism; - std::vector m_Plugins; static bool obfuscate(const QString key, const QString data); static QString deObfuscate(const QString key); - void readPluginBlacklist(); QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; void setConfigurablePath(const QString &key, const QString& path); }; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 329ba301..956971fe 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -12,19 +12,19 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, SettingsDialog& d) // display plugin settings QSet handledNames; - for (IPlugin *plugin : settings().plugins()) { + for (IPlugin *plugin : settings().plugins().plugins()) { if (handledNames.contains(plugin->name())) continue; QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), ui->pluginsList); listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, settings().m_PluginSettings[plugin->name()]); - listItem->setData(Qt::UserRole + 2, settings().m_PluginDescriptions[plugin->name()]); + listItem->setData(Qt::UserRole + 1, settings().plugins().pluginSettings(plugin->name())); + listItem->setData(Qt::UserRole + 2, settings().plugins().pluginDescriptions(plugin->name())); ui->pluginsList->addItem(listItem); handledNames.insert(plugin->name()); } // display plugin blacklist - for (const QString &pluginName : settings().m_PluginBlacklist) { + for (const QString &pluginName : settings().plugins().pluginBlacklist()) { ui->pluginBlacklist->addItem(pluginName); } @@ -42,21 +42,19 @@ void PluginsSettingsTab::update() // transfer plugin settings to in-memory structure for (int i = 0; i < ui->pluginsList->count(); ++i) { QListWidgetItem *item = ui->pluginsList->item(i); - settings().m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); - } - // store plugin settings on disc - for (auto iterPlugins = settings().m_PluginSettings.begin(); iterPlugins != settings().m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { - qsettings().setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); - } + settings().plugins().setPluginSettings( + item->text(), item->data(Qt::UserRole + 1).toMap()); } - // store plugin blacklist - settings().m_PluginBlacklist.clear(); + // set plugin blacklist + QStringList names; for (QListWidgetItem *item : ui->pluginBlacklist->findItems("*", Qt::MatchWildcard)) { - settings().m_PluginBlacklist.insert(item->text()); + names.push_back(item->text()); } - settings().writePluginBlacklist(); + + settings().plugins().setPluginBlacklist(names); + + settings().plugins().save(); } void PluginsSettingsTab::closing() -- cgit v1.3.1 From ca2a7da3f6534515160d5fbf92f72d6ff2bce3e8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 09:35:11 -0400 Subject: moved workarounds to settings --- src/settings.cpp | 43 +++++++++++++++++++++++++++++++++++++++ src/settings.h | 8 ++++++++ src/settingsdialogworkarounds.cpp | 22 +++++++++++--------- 3 files changed, 63 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 072318a2..f6be8ba0 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -421,16 +421,31 @@ bool Settings::hideUncheckedPlugins() const return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); } +void Settings::setHideUncheckedPlugins(bool b) +{ + m_Settings.setValue("Settings/hide_unchecked_plugins", b); +} + bool Settings::forceEnableCoreFiles() const { return m_Settings.value("Settings/force_enable_core_files", true).toBool(); } +void Settings::setForceEnableCoreFiles(bool b) +{ + m_Settings.setValue("Settings/force_enable_core_files", b); +} + bool Settings::lockGUI() const { return m_Settings.value("Settings/lock_gui", true).toBool(); } +void Settings::setLockGUI(bool b) +{ + m_Settings.setValue("Settings/lock_gui", b); +} + bool Settings::automaticLoginEnabled() const { return m_Settings.value("Settings/nexus_login", false).toBool(); @@ -441,6 +456,15 @@ QString Settings::getSteamAppID() const return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString(); } +void Settings::setSteamAppID(const QString& id) +{ + if (id.isEmpty()) { + m_Settings.remove("Settings/app_id"); + } else { + m_Settings.setValue("Settings/app_id", id); + } +} + bool Settings::usePrereleases() const { return m_Settings.value("Settings/use_prereleases", false).toBool(); @@ -782,6 +806,11 @@ QString Settings::executablesBlacklist() const ).toString(); } +void Settings::setExecutablesBlacklist(const QString& s) +{ + m_Settings.setValue("Settings/executable_blacklist", s); +} + void Settings::setSteamLogin(QString username, QString password) { if (username == "") { @@ -815,6 +844,10 @@ LoadMechanism::EMechanism Settings::getLoadMechanism() const } } +void Settings::setLoadMechanism(LoadMechanism::EMechanism m) +{ + m_Settings.setValue("Settings/load_mechanism", static_cast(m)); +} void Settings::setupLoadMechanism() { @@ -869,6 +902,11 @@ bool Settings::displayForeign() const return m_Settings.value("Settings/display_foreign", true).toBool(); } +void Settings::setDisplayForeign(bool b) +{ + m_Settings.setValue("Settings/display_foreign", b); +} + void Settings::setMotDHash(uint hash) { m_Settings.setValue("motd_hash", hash); @@ -884,6 +922,11 @@ bool Settings::archiveParsing() const return m_Settings.value("Settings/archive_parsing_experimental", false).toBool(); } +void Settings::setArchiveParsing(bool b) +{ + m_Settings.setValue("Settings/archive_parsing_experimental", b); +} + QString Settings::language() { QString result = m_Settings.value("Settings/language", "").toString(); diff --git a/src/settings.h b/src/settings.h index fc33e0de..09141274 100644 --- a/src/settings.h +++ b/src/settings.h @@ -203,16 +203,19 @@ public: * the virtual dat adirectory **/ bool hideUncheckedPlugins() const; + void setHideUncheckedPlugins(bool b); /** * @return true if files of the core game are forced-enabled so the user can't accidentally disable them */ bool forceEnableCoreFiles() const; + void setForceEnableCoreFiles(bool b); /** * @return true if the GUI should be locked when running executables */ bool lockGUI() const; + void setLockGUI(bool b); /** * the steam appid is assigned by the steam platform to each product sold there. @@ -221,6 +224,7 @@ public: * @return the steam appid for the game **/ QString getSteamAppID() const; + void setSteamAppID(const QString& id); QString getBaseDirectory() const; QString getDownloadDirectory(bool resolve = true) const; @@ -380,6 +384,7 @@ public: void setCrashDumpsMax(int n); QString executablesBlacklist() const; + void setExecutablesBlacklist(const QString& s); /** * @brief set the steam login information @@ -393,6 +398,7 @@ public: * @return the load mechanism to be used **/ LoadMechanism::EMechanism getLoadMechanism() const; + void setLoadMechanism(LoadMechanism::EMechanism m); /** * @brief activate the load mechanism selected by the user @@ -425,6 +431,7 @@ public: * @return true if the user wants to see non-official plugins installed outside MO in his mod list */ bool displayForeign() const; + void setDisplayForeign(bool b); /** * @brief sets the new motd hash @@ -435,6 +442,7 @@ public: * @return true if the user wants to have archives being parsed to show conflicts and contents */ bool archiveParsing() const; + void setArchiveParsing(bool b); /** * @return hash of the last displayed message of the day diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 443ba54e..b06bd77c 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -36,18 +36,20 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) void WorkaroundsSettingsTab::update() { if (ui->appIDEdit->text() != settings().gamePlugin()->steamAPPId()) { - qsettings().setValue("Settings/app_id", ui->appIDEdit->text()); + settings().setSteamAppID(ui->appIDEdit->text()); } else { - qsettings().remove("Settings/app_id"); + settings().setSteamAppID(""); } - qsettings().setValue("Settings/load_mechanism", ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt()); - qsettings().setValue("Settings/hide_unchecked_plugins", ui->hideUncheckedBox->isChecked()); - qsettings().setValue("Settings/force_enable_core_files", ui->forceEnableBox->isChecked()); - qsettings().setValue("Settings/display_foreign", ui->displayForeignBox->isChecked()); - qsettings().setValue("Settings/lock_gui", ui->lockGUIBox->isChecked()); - qsettings().setValue("Settings/archive_parsing_experimental", ui->enableArchiveParsingBox->isChecked()); - - qsettings().setValue("Settings/executable_blacklist", getExecutableBlacklist()); + + settings().setLoadMechanism(static_cast( + ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt())); + + settings().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked()); + settings().setForceEnableCoreFiles(ui->forceEnableBox->isChecked()); + settings().setDisplayForeign(ui->displayForeignBox->isChecked()); + settings().setLockGUI(ui->lockGUIBox->isChecked()); + settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked()); + settings().setExecutablesBlacklist(getExecutableBlacklist()); } void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() -- cgit v1.3.1 From 57bef2ab8da5354cc65c3ad0e2cd86d75e1e8b94 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 09:38:21 -0400 Subject: removed directinterface --- src/settings.h | 7 ------- src/settingsdialog.cpp | 7 +------ src/settingsdialog.h | 2 -- 3 files changed, 1 insertion(+), 15 deletions(-) (limited to 'src') diff --git a/src/settings.h b/src/settings.h index 09141274..ae29d788 100644 --- a/src/settings.h +++ b/src/settings.h @@ -449,13 +449,6 @@ public: **/ uint getMotDHash() const; - /** - * @brief allows direct access to the wrapped QSettings object - * @return the wrapped QSettings object - */ - QSettings &directInterface() { return m_Settings; } - const QSettings &directInterface() const { return m_Settings; } - /** * @return short code of the configured language (corresponding to the translation files) */ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index a24416e9..35d14644 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -131,7 +131,7 @@ bool SettingsDialog::getApiKeyChanged() SettingsTab::SettingsTab(Settings& s, SettingsDialog& d) - : ui(d.ui), m_settings(s), m_qsettings(s.directInterface()), m_dialog(d) + : ui(d.ui), m_settings(s), m_dialog(d) { } @@ -142,11 +142,6 @@ Settings& SettingsTab::settings() return m_settings; } -QSettings& SettingsTab::qsettings() -{ - return m_qsettings; -} - SettingsDialog& SettingsTab::dialog() { return m_dialog; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 0aad8863..6a99cb8d 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -41,12 +41,10 @@ protected: Ui::SettingsDialog* ui; Settings& settings(); - QSettings& qsettings(); SettingsDialog& dialog(); private: Settings& m_settings; - QSettings& m_qsettings; SettingsDialog& m_dialog; }; -- cgit v1.3.1 From ec3fb7b3509fb10a8a1392740e209509ae6c092c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 12:56:50 -0400 Subject: removed duplicate useProxy() use dedicated functions to set, get or remove settings, allows for logging --- src/loadmechanism.cpp | 13 + src/loadmechanism.h | 2 + src/mainwindow.cpp | 12 +- src/settings.cpp | 852 +++++++++++++++++++++++++++----------------- src/settings.h | 16 +- src/settingsdialognexus.cpp | 2 +- 6 files changed, 556 insertions(+), 341 deletions(-) (limited to 'src') diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 06e9f201..0c81b7b2 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -49,3 +49,16 @@ void LoadMechanism::activate(EMechanism) { // no-op } + + +QString toString(LoadMechanism::EMechanism e) +{ + switch (e) + { + case LoadMechanism::LOAD_MODORGANIZER: + return "ModOrganizer"; + + default: + return QString("unknown (%1)").arg(static_cast(e)); + } +} diff --git a/src/loadmechanism.h b/src/loadmechanism.h index 49eb0c52..151e804f 100644 --- a/src/loadmechanism.h +++ b/src/loadmechanism.h @@ -56,4 +56,6 @@ private: }; +QString toString(LoadMechanism::EMechanism e); + #endif // LOADMECHANISM_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4c2594b8..42b19cb7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2157,10 +2157,8 @@ void MainWindow::readSettings(const Settings& settings) ui->displayCategoriesBtn->setChecked(v); } - if (auto v=settings.getUseProxy()) { - if (*v) { - activateProxy(true); - } + if (settings.getUseProxy()) { + activateProxy(true); } } @@ -5014,7 +5012,7 @@ void MainWindow::on_actionSettings_triggered() QString oldProfilesDirectory(settings.getProfileDirectory()); QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); bool oldDisplayForeign(settings.displayForeign()); - bool proxy = settings.useProxy(); + bool proxy = settings.getUseProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); @@ -5081,8 +5079,8 @@ void MainWindow::on_actionSettings_triggered() NexusInterface::instance(&m_PluginContainer)->setCacheDirectory(settings.getCacheDirectory()); } - if (proxy != settings.useProxy()) { - activateProxy(settings.useProxy()); + if (proxy != settings.getUseProxy()) { + activateProxy(settings.getUseProxy()); } ui->statusBar->checkSettings(m_OrganizerCore.settings()); diff --git a/src/settings.cpp b/src/settings.cpp index f6be8ba0..71288950 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -27,13 +27,164 @@ along with Mod Organizer. If not, see . using namespace MOBase; +template +struct ValueConverter +{ + static const T& convert(const T& t) + { + return t; + } +}; + +template +struct ValueConverter>> +{ + static QString convert(const T& t) + { + return QString("%1").arg(static_cast>(t)); + } +}; + + +template +void logChange( + const QString& displayName, std::optional oldValue, const T& newValue) +{ + using VC = ValueConverter; + + if (oldValue) { + log::debug( + "setting '{}' changed from '{}' to '{}'", + displayName, VC::convert(*oldValue), VC::convert(newValue)); + } else { + log::debug( + "setting '{}' set to '{}'", + displayName, VC::convert(newValue)); + } +} + +void logRemoval(const QString& name) +{ + log::debug("setting '{}' removed", name); +} + + +QString settingName(const QString& section, const QString& key) +{ + if (section.isEmpty()) { + return key; + } else if (key.isEmpty()) { + return section; + } else { + if (section.compare("General", Qt::CaseInsensitive) == 0) { + return key; + } else { + return section + "/" + key; + } + } +} + +template +void setImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key, const T& value) +{ + const auto current = getOptional(settings, section, key); + + if (current && *current == value) { + // no change + return; + } + + const auto name = settingName(section, key); + + logChange(displayName, current, value); + + if constexpr (std::is_enum_v) { + settings.setValue( + name, static_cast>(value)); + } else { + settings.setValue(name, value); + } +} + +void removeImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key) +{ + if (key.isEmpty()) { + if (!settings.childGroups().contains(section, Qt::CaseInsensitive)) { + // not there + return; + } + } else { + if (!settings.contains(settingName(section, key))) { + // not there + return; + } + } + + logRemoval(displayName); + settings.remove(settingName(section, key)); +} + + +template +std::optional getOptional( + const QSettings& settings, + const QString& section, const QString& key, std::optional def={}) +{ + if (settings.contains(settingName(section, key))) { + const auto v = settings.value(settingName(section, key)); + + if constexpr (std::is_enum_v) { + return static_cast(v.value>()); + } else { + return v.value(); + } + } + + return def; +} + +template +T get( + const QSettings& settings, + const QString& section, const QString& key, T def={}) +{ + if (auto v=getOptional(settings, section, key)) { + return *v; + } else { + return def; + } +} + +template +void set( + QSettings& settings, + const QString& section, const QString& key, const T& value) +{ + setImpl(settings, settingName(section, key), section, key, value); +} + +void remove(QSettings& settings, const QString& section, const QString& key) +{ + removeImpl(settings, settingName(section, key), section, key); +} + +void removeSection(QSettings& settings, const QString& section) +{ + removeImpl(settings, section, section, ""); +} + + class ScopedGroup { public: ScopedGroup(QSettings& s, const QString& name) - : m_settings(s) + : m_settings(s), m_name(name) { - m_settings.beginGroup(name); + m_settings.beginGroup(m_name); } ~ScopedGroup() @@ -44,18 +195,55 @@ public: ScopedGroup(const ScopedGroup&) = delete; ScopedGroup& operator=(const ScopedGroup&) = delete; + template + void set(const QString& key, const T& value) + { + setImpl(m_settings, settingName(m_name, key), "", key, value); + } + + void remove(const QString& key) + { + removeImpl(m_settings, settingName(m_name, key), "", key); + } + + QStringList keys() const + { + return m_settings.childKeys(); + } + + template + void for_each(F&& f) const + { + for (const QString& key : keys()) { + f(key); + } + } + + template + std::optional getOptional(const QString& key, std::optional def={}) const + { + return ::getOptional(m_settings, "", key, def); + } + + template + T get(const QString& key, T def={}) const + { + return ::get(m_settings, "", key, def); + } + private: QSettings& m_settings; + QString m_name; }; class ScopedReadArray { public: - ScopedReadArray(QSettings& s, const QString& name) + ScopedReadArray(QSettings& s, const QString& section) : m_settings(s), m_count(0) { - m_count = m_settings.beginReadArray(name); + m_count = m_settings.beginReadArray(section); } ~ScopedReadArray() @@ -66,11 +254,37 @@ public: ScopedReadArray(const ScopedReadArray&) = delete; ScopedReadArray& operator=(const ScopedReadArray&) = delete; + template + void for_each(F&& f) const + { + for (int i=0; i + std::optional getOptional(const QString& key, std::optional def={}) const + { + return ::getOptional(m_settings, "", key, def); + } + + template + T get(const QString& key, T def={}) const + { + return ::get(m_settings, "", key, def); + } + int count() const { return m_count; } + QStringList keys() const + { + return m_settings.childKeys(); + } + private: QSettings& m_settings; int m_count; @@ -80,10 +294,10 @@ private: class ScopedWriteArray { public: - ScopedWriteArray(QSettings& s, const QString& name) - : m_settings(s) + ScopedWriteArray(QSettings& s, const QString& section) + : m_settings(s), m_section(section), m_i(0) { - m_settings.beginWriteArray(name); + m_settings.beginWriteArray(section); } ~ScopedWriteArray() @@ -94,27 +308,28 @@ public: ScopedWriteArray(const ScopedWriteArray&) = delete; ScopedWriteArray& operator=(const ScopedWriteArray&) = delete; -private: - QSettings& m_settings; -}; - + void next() + { + m_settings.setArrayIndex(m_i); + ++m_i; + } -template -std::optional getOptional( - const QSettings& s, const QString& name, std::optional def={}) -{ - if (s.contains(name)) { - const auto v = s.value(name); + template + void set(const QString& key, const T& value) + { + const auto displayName = QString("%1/%2\\%3") + .arg(m_section) + .arg(m_i) + .arg(key); - if constexpr (std::is_enum_v) { - return static_cast(v.value>()); - } else { - return v.value(); - } + setImpl(m_settings, displayName, "", key, value); } - return def; -} +private: + QSettings& m_settings; + QString m_section; + int m_i; +}; EndorsementState endorsementStateFromString(const QString& s) @@ -132,15 +347,15 @@ QString toString(EndorsementState s) { switch (s) { - case EndorsementState::Accepted: - return "Endorsed"; + case EndorsementState::Accepted: + return "Endorsed"; - case EndorsementState::Refused: - return "Abstained"; + case EndorsementState::Refused: + return "Abstained"; - case EndorsementState::NoDecision: // fall-through - default: - return {}; + case EndorsementState::NoDecision: // fall-through + default: + return {}; } } @@ -198,24 +413,24 @@ QString widgetName(const QWidget* w) template QString geoSettingName(const Widget* widget) { - return "geometry/" + widgetName(widget) + "_geometry"; + return widgetName(widget) + "_geometry"; } template QString stateSettingName(const Widget* widget) { - return "geometry/" + widgetName(widget) + "_state"; + return widgetName(widget) + "_state"; } template QString visibilitySettingName(const Widget* widget) { - return "geometry/" + widgetName(widget) + "_visibility"; + return widgetName(widget) + "_visibility"; } QString dockSettingName(const QDockWidget* dock) { - return "geometry/MainWindow_docks_" + dock->objectName() + "_size"; + return "MainWindow_docks_" + dock->objectName() + "_size"; } QString indexSettingName(const QWidget* widget) @@ -278,40 +493,34 @@ void Settings::processUpdates( } if (lastVersion < QVersionNumber(2, 2, 0)) { - { - ScopedGroup sg(m_Settings, "Settings"); - m_Settings.remove("steam_password"); - m_Settings.remove("nexus_username"); - m_Settings.remove("nexus_password"); - m_Settings.remove("nexus_login"); - m_Settings.remove("nexus_api_key"); - m_Settings.remove("ask_for_nexuspw"); - m_Settings.remove("nmm_version"); - } + remove(m_Settings, "Settings", "steam_password"); + remove(m_Settings, "Settings", "nexus_username"); + remove(m_Settings, "Settings", "nexus_password"); + remove(m_Settings, "Settings", "nexus_login"); + remove(m_Settings, "Settings", "nexus_api_key"); + remove(m_Settings, "Settings", "ask_for_nexuspw"); + remove(m_Settings, "Settings", "nmm_version"); - { - ScopedGroup sg(m_Settings, "Servers"); - m_Settings.remove(""); - } + removeSection(m_Settings, "Servers"); } if (lastVersion < QVersionNumber(2, 2, 1)) { - m_Settings.remove("mod_info_tabs"); - m_Settings.remove("mod_info_conflict_expanders"); - m_Settings.remove("mod_info_conflicts"); - m_Settings.remove("mod_info_advanced_conflicts"); - m_Settings.remove("mod_info_conflicts_overwrite"); - m_Settings.remove("mod_info_conflicts_noconflict"); - m_Settings.remove("mod_info_conflicts_overwritten"); + remove(m_Settings, "General", "mod_info_tabs"); + remove(m_Settings, "General", "mod_info_conflict_expanders"); + remove(m_Settings, "General", "mod_info_conflicts"); + remove(m_Settings, "General", "mod_info_advanced_conflicts"); + remove(m_Settings, "General", "mod_info_conflicts_overwrite"); + remove(m_Settings, "General", "mod_info_conflicts_noconflict"); + remove(m_Settings, "General", "mod_info_conflicts_overwritten"); } if (lastVersion < QVersionNumber(2, 2, 2)) { // log splitter is gone, it's a dock now - m_Settings.remove("log_split"); + remove(m_Settings, "General", "log_split"); } //save version in all case - m_Settings.setValue("version", currentVersion.toString()); + set(m_Settings, "General", "version", currentVersion.toString()); } QString Settings::getFilename() const @@ -339,12 +548,12 @@ void Settings::registerAsNXMHandler(bool force) bool Settings::colorSeparatorScrollbar() const { - return m_Settings.value("Settings/colorSeparatorScrollbars", true).toBool(); + return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); } void Settings::setColorSeparatorScrollbar(bool b) { - m_Settings.setValue("Settings/colorSeparatorScrollbars", b); + set(m_Settings, "Settings", "colorSeparatorScrollbars", b); } void Settings::managedGameChanged(IPluginGame const *gamePlugin) @@ -418,71 +627,69 @@ QColor Settings::getIdealTextColor(const QColor& rBackgroundColor) bool Settings::hideUncheckedPlugins() const { - return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); + return get(m_Settings, "Settings", "hide_unchecked_plugins", false); } void Settings::setHideUncheckedPlugins(bool b) { - m_Settings.setValue("Settings/hide_unchecked_plugins", b); + set(m_Settings, "Settings", "hide_unchecked_plugins", b); } bool Settings::forceEnableCoreFiles() const { - return m_Settings.value("Settings/force_enable_core_files", true).toBool(); + return get(m_Settings, "Settings", "force_enable_core_files", true); } void Settings::setForceEnableCoreFiles(bool b) { - m_Settings.setValue("Settings/force_enable_core_files", b); + set(m_Settings, "Settings", "force_enable_core_files", b); } bool Settings::lockGUI() const { - return m_Settings.value("Settings/lock_gui", true).toBool(); + return get(m_Settings, "Settings", "lock_gui", true); } void Settings::setLockGUI(bool b) { - m_Settings.setValue("Settings/lock_gui", b); + set(m_Settings, "Settings", "lock_gui", b); } bool Settings::automaticLoginEnabled() const { - return m_Settings.value("Settings/nexus_login", false).toBool(); + return get(m_Settings, "Settings", "nexus_login", false); } QString Settings::getSteamAppID() const { - return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString(); + return get(m_Settings, "Settings", "app_id", m_GamePlugin->steamAPPId()); } void Settings::setSteamAppID(const QString& id) { if (id.isEmpty()) { - m_Settings.remove("Settings/app_id"); + remove(m_Settings, "Settings", "app_id"); } else { - m_Settings.setValue("Settings/app_id", id); + set(m_Settings, "Settings", "app_id", id); } } bool Settings::usePrereleases() const { - return m_Settings.value("Settings/use_prereleases", false).toBool(); + return get(m_Settings, "Settings", "use_prereleases", false); } void Settings::setUsePrereleases(bool b) { - m_Settings.setValue("Settings/use_prereleases", b); + set(m_Settings, "Settings", "use_prereleases", b); } QString Settings::getConfigurablePath(const QString &key, - const QString &def, - bool resolve) const + const QString &def, + bool resolve) const { - const QString settingName = "Settings/" + key; - QString result = QDir::fromNativeSeparators( - m_Settings.value(settingName, QString("%BASE_DIR%/") + def).toString()); + get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); if (resolve) { result.replace("%BASE_DIR%", getBaseDirectory()); @@ -493,20 +700,17 @@ QString Settings::getConfigurablePath(const QString &key, void Settings::setConfigurablePath(const QString &key, const QString& path) { - const QString settingName = "Settings/" + key; - if (path.isEmpty()) { - m_Settings.remove(settingName); + remove(m_Settings, "Settings", key); } else { - m_Settings.setValue(settingName, path); + set(m_Settings, "Settings", key, path); } } QString Settings::getBaseDirectory() const { - return QDir::fromNativeSeparators(m_Settings.value( - "settings/base_directory", - qApp->property("dataPath").toString()).toString()); + return QDir::fromNativeSeparators(get(m_Settings, + "Settings", "base_directory", qApp->property("dataPath").toString())); } QString Settings::getDownloadDirectory(bool resolve) const @@ -552,9 +756,9 @@ QString Settings::getOverwriteDirectory(bool resolve) const void Settings::setBaseDirectory(const QString& path) { if (path.isEmpty()) { - m_Settings.remove("Settings/base_directory"); + remove(m_Settings, "Settings", "base_directory"); } else { - m_Settings.setValue("Settings/base_directory", path); + set(m_Settings, "Settings", "base_directory", path); } } @@ -585,7 +789,7 @@ void Settings::setOverwriteDirectory(const QString& path) std::optional Settings::getManagedGameDirectory() const { - if (auto v=getOptional(m_Settings, "gamePath")) { + if (auto v=getOptional(m_Settings, "General", "gamePath")) { return QString::fromUtf8(*v); } @@ -594,32 +798,32 @@ std::optional Settings::getManagedGameDirectory() const void Settings::setManagedGameDirectory(const QString& path) { - m_Settings.setValue("gamePath", QDir::toNativeSeparators(path).toUtf8()); + set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); } std::optional Settings::getManagedGameName() const { - return getOptional(m_Settings, "gameName"); + return getOptional(m_Settings, "General", "gameName"); } void Settings::setManagedGameName(const QString& name) { - m_Settings.setValue("gameName", name); + set(m_Settings, "General", "gameName", name); } std::optional Settings::getManagedGameEdition() const { - return getOptional(m_Settings, "game_edition"); + return getOptional(m_Settings, "General", "game_edition"); } void Settings::setManagedGameEdition(const QString& name) { - m_Settings.setValue("game_edition", name); + set(m_Settings, "General", "game_edition", name); } std::optional Settings::getSelectedProfileName() const { - if (auto v=getOptional(m_Settings, "selected_profile")) { + if (auto v=getOptional(m_Settings, "General", "selected_profile")) { return QString::fromUtf8(*v); } @@ -628,32 +832,32 @@ std::optional Settings::getSelectedProfileName() const void Settings::setSelectedProfileName(const QString& name) { - m_Settings.setValue("selected_profile", name.toUtf8()); + set(m_Settings, "General", "selected_profile", name.toUtf8()); } std::optional Settings::getStyleName() const { - return getOptional(m_Settings, "Settings/style"); + return getOptional(m_Settings, "Settings", "style"); } void Settings::setStyleName(const QString& name) { - m_Settings.setValue("Settings/style", name); + set(m_Settings, "Settings", "style", name); } -std::optional Settings::getUseProxy() const +bool Settings::getUseProxy() const { - return getOptional(m_Settings, "Settings/use_proxy"); + return get(m_Settings, "Settings", "use_proxy", false); } void Settings::setUseProxy(bool b) { - m_Settings.setValue("Settings/use_proxy", b); + set(m_Settings, "Settings", "use_proxy", b); } std::optional Settings::getVersion() const { - if (auto v=getOptional(m_Settings, "version")) { + if (auto v=getOptional(m_Settings, "General", "version")) { return QVersionNumber::fromString(*v).normalized(); } @@ -662,17 +866,17 @@ std::optional Settings::getVersion() const bool Settings::getFirstStart() const { - return getOptional(m_Settings, "first_start").value_or(true); + return get(m_Settings, "General", "first_start", true); } void Settings::setFirstStart(bool b) { - m_Settings.setValue("first_start", b); + set(m_Settings, "General", "first_start", b); } std::optional Settings::getPreviousSeparatorColor() const { - const auto c = getOptional(m_Settings, "previousSeparatorColor"); + const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); if (c && c->isValid()) { return c; } @@ -682,12 +886,12 @@ std::optional Settings::getPreviousSeparatorColor() const void Settings::setPreviousSeparatorColor(const QColor& c) const { - m_Settings.setValue("previousSeparatorColor", c); + set(m_Settings, "General", "previousSeparatorColor", c); } void Settings::removePreviousSeparatorColor() { - m_Settings.remove("previousSeparatorColor"); + remove(m_Settings, "General", "previousSeparatorColor"); } bool Settings::getNexusApiKey(QString &apiKey) const @@ -695,6 +899,7 @@ bool Settings::getNexusApiKey(QString &apiKey) const QString tempKey = deObfuscate("APIKEY"); if (tempKey.isEmpty()) return false; + apiKey = tempKey; return true; } @@ -722,7 +927,7 @@ bool Settings::hasNexusApiKey() const bool Settings::getSteamLogin(QString &username, QString &password) const { - username = m_Settings.value("Settings/steam_username", "").toString(); + username = get(m_Settings, "Settings", "steam_username", ""); password = deObfuscate("steam_password"); return !username.isEmpty() && !password.isEmpty(); @@ -730,95 +935,96 @@ bool Settings::getSteamLogin(QString &username, QString &password) const bool Settings::compactDownloads() const { - return m_Settings.value("Settings/compact_downloads", false).toBool(); + return get(m_Settings, "Settings", "compact_downloads", false); } void Settings::setCompactDownloads(bool b) { - m_Settings.setValue("Settings/compact_downloads", b); + set(m_Settings, "Settings", "compact_downloads", b); } bool Settings::metaDownloads() const { - return m_Settings.value("Settings/meta_downloads", false).toBool(); + return get(m_Settings, "Settings", "meta_downloads", false); } void Settings::setMetaDownloads(bool b) { - m_Settings.setValue("Settings/meta_downloads", b); + set(m_Settings, "Settings", "meta_downloads", b); } bool Settings::offlineMode() const { - return m_Settings.value("Settings/offline_mode", false).toBool(); + return get(m_Settings, "Settings/offline_mode", false); } void Settings::setOfflineMode(bool b) { - m_Settings.setValue("Settings/offline_mode", b); + set(m_Settings, "Settings", "offline_mode", b); } log::Levels Settings::logLevel() const { - return static_cast(m_Settings.value("Settings/log_level").toInt()); + return get(m_Settings, "Settings", "log_level", log::Levels::Info); } void Settings::setLogLevel(log::Levels level) { - m_Settings.setValue("Settings/log_level", static_cast(level)); + set(m_Settings, "Settings", "log_level", level); } CrashDumpsType Settings::crashDumpsType() const { - const auto v = getOptional(m_Settings, "Settings/crash_dumps_type"); - return v.value_or(CrashDumpsType::Mini); + return get(m_Settings, + "Settings", "crash_dumps_type", CrashDumpsType::Mini); } void Settings::setCrashDumpsType(CrashDumpsType type) { - m_Settings.setValue("Settings/crash_dumps_type", static_cast(type)); + set(m_Settings, "Settings", "crash_dumps_type", type); } int Settings::crashDumpsMax() const { - return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); + return get(m_Settings, "Settings", "crash_dumps_max", 5); } void Settings::setCrashDumpsMax(int n) { - return m_Settings.setValue("Settings/crash_dumps_max", n); + set(m_Settings, "Settings", "crash_dumps_max", n); } QString Settings::executablesBlacklist() const { - return m_Settings.value("Settings/executable_blacklist", ( - QStringList() - << "Chrome.exe" - << "Firefox.exe" - << "TSVNCache.exe" - << "TGitCache.exe" - << "Steam.exe" - << "GameOverlayUI.exe" - << "Discord.exe" - << "GalaxyClient.exe" - << "Spotify.exe" - ).join(";") - ).toString(); + static const QString def = (QStringList() + << "Chrome.exe" + << "Firefox.exe" + << "TSVNCache.exe" + << "TGitCache.exe" + << "Steam.exe" + << "GameOverlayUI.exe" + << "Discord.exe" + << "GalaxyClient.exe" + << "Spotify.exe" + ).join(";"); + + return get(m_Settings, "Settings", "executable_blacklist", def); } void Settings::setExecutablesBlacklist(const QString& s) { - m_Settings.setValue("Settings/executable_blacklist", s); + set(m_Settings, "Settings", "executable_blacklist", s); } void Settings::setSteamLogin(QString username, QString password) { if (username == "") { - m_Settings.remove("Settings/steam_username"); + remove(m_Settings, "Settings", "steam_username"); password = ""; } else { - m_Settings.setValue("Settings/steam_username", username); + set(m_Settings, "Settings", "steam_username", username); } + if (!obfuscate("steam_password", password)) { const auto e = GetLastError(); log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); @@ -827,26 +1033,37 @@ void Settings::setSteamLogin(QString username, QString password) LoadMechanism::EMechanism Settings::getLoadMechanism() const { - const auto i = m_Settings.value("Settings/load_mechanism").toInt(); + const auto def = LoadMechanism::LOAD_MODORGANIZER; + + const auto i = get(m_Settings, + "Settings", "load_mechanism", def); switch (i) { - case LoadMechanism::LOAD_MODORGANIZER: - return LoadMechanism::LOAD_MODORGANIZER; + // ok + case LoadMechanism::LOAD_MODORGANIZER: // fall-through + { + break; + } - default: - qCritical().nospace().noquote() - << "invalid load mechanism " << i << ", reverting to modorganizer"; + default: + { + log::error( + "invalid load mechanism {}, reverting to {}", + static_cast(i), toString(def)); - m_Settings.setValue("Settings/load_mechanism", LoadMechanism::LOAD_MODORGANIZER); + set(m_Settings, "Settings", "load_mechanism", def); - return LoadMechanism::LOAD_MODORGANIZER; + return def; } + } + + return i; } void Settings::setLoadMechanism(LoadMechanism::EMechanism m) { - m_Settings.setValue("Settings/load_mechanism", static_cast(m)); + set(m_Settings, "Settings", "load_mechanism", m); } void Settings::setupLoadMechanism() @@ -854,26 +1071,20 @@ void Settings::setupLoadMechanism() m_LoadMechanism.activate(getLoadMechanism()); } - -bool Settings::useProxy() const -{ - return m_Settings.value("Settings/use_proxy", false).toBool(); -} - bool Settings::endorsementIntegration() const { - return m_Settings.value("Settings/endorsement_integration", true).toBool(); + return get(m_Settings, "Settings", "endorsement_integration", true); } void Settings::setEndorsementIntegration(bool b) const { - m_Settings.setValue("Settings/endorsement_integration", b); + set(m_Settings, "Settings", "endorsement_integration", b); } EndorsementState Settings::endorsementState() const { - const auto v = getOptional(m_Settings, "endorse_state"); - return endorsementStateFromString(v.value_or("")); + return endorsementStateFromString( + get(m_Settings, "General", "endorse_state", "")); } void Settings::setEndorsementState(EndorsementState s) @@ -881,57 +1092,59 @@ void Settings::setEndorsementState(EndorsementState s) const auto v = toString(s); if (v.isEmpty()) { - m_Settings.remove("endorse_state"); + remove(m_Settings, "General", "endorse_state"); } else { - m_Settings.setValue("endorse_state", v); + set(m_Settings, "General", "endorse_state", v); } } bool Settings::hideAPICounter() const { - return m_Settings.value("Settings/hide_api_counter", false).toBool(); + return get(m_Settings, "Settings", "hide_api_counter", false); } void Settings::setHideAPICounter(bool b) { - m_Settings.setValue("Settings/hide_api_counter", b); + set(m_Settings, "Settings", "hide_api_counter", b); } bool Settings::displayForeign() const { - return m_Settings.value("Settings/display_foreign", true).toBool(); + return get(m_Settings, "Settings", "display_foreign", true); } void Settings::setDisplayForeign(bool b) { - m_Settings.setValue("Settings/display_foreign", b); + set(m_Settings, "Settings", "display_foreign", b); } void Settings::setMotDHash(uint hash) { - m_Settings.setValue("motd_hash", hash); + set(m_Settings, "General", "motd_hash", hash); } -uint Settings::getMotDHash() const +unsigned int Settings::getMotDHash() const { - return m_Settings.value("motd_hash", 0).toUInt(); + return get(m_Settings, "motd_hash", 0); } bool Settings::archiveParsing() const { - return m_Settings.value("Settings/archive_parsing_experimental", false).toBool(); + return get(m_Settings, "Settings", "archive_parsing_experimental", false); } void Settings::setArchiveParsing(bool b) { - m_Settings.setValue("Settings/archive_parsing_experimental", b); + set(m_Settings, "Settings", "archive_parsing_experimental", b); } QString Settings::language() { - QString result = m_Settings.value("Settings/language", "").toString(); + QString result = get(m_Settings, "Settings", "language", ""); + if (result.isEmpty()) { QStringList languagePreferences = QLocale::system().uiLanguages(); + if (languagePreferences.length() > 0) { // the users most favoritest language result = languagePreferences.at(0); @@ -940,12 +1153,13 @@ QString Settings::language() result = QLocale::system().name(); } } + return result; } void Settings::setLanguage(const QString& name) { - m_Settings.setValue("Settings/language", name); + set(m_Settings, "Settings", "language", name); } void Settings::setDownloadSpeed(const QString& name, int bytesPerSecond) @@ -972,18 +1186,13 @@ ServerList Settings::getServers() const // // so post 2.2.1, only one key is returned: "size", the size of the arrays; // in 2.2.1, one key per server is returned - - // getting the keys - QStringList keys; - { - ScopedGroup sg(m_Settings, "Servers"); - keys = m_Settings.childKeys(); - } + const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - if (!keys.empty() && keys[0] != "size") { - // old format - return getServersFromOldMap(); + if (!keys.empty() && keys[0] != "size") { + // old format + return getServersFromOldMap(); + } } @@ -994,12 +1203,11 @@ ServerList Settings::getServers() const { ScopedReadArray sra(m_Settings, "Servers"); - for (int i=0; i("lastDownloads", ""); + for (const auto& s : lastDownloadsString.split(" ")) { const auto bytesPerSecond = s.toInt(); if (bytesPerSecond > 0) { @@ -1008,14 +1216,14 @@ ServerList Settings::getServers() const } ServerInfo server( - m_Settings.value("name").toString(), - m_Settings.value("premium").toBool(), - QDate::fromString(m_Settings.value("lastSeen").toString(), Qt::ISODate), - m_Settings.value("preferred").toInt(), + sra.get("name", ""), + sra.get("premium", false), + QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), + sra.get("preferred", 0), lastDownloads); list.add(std::move(server)); - } + }); } return list; @@ -1026,10 +1234,10 @@ ServerList Settings::getServersFromOldMap() const // for 2.2.1 and before ServerList list; - ScopedGroup sg(m_Settings, "Servers"); + const ScopedGroup sg(m_Settings, "Servers"); - for (const QString &serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); + sg.for_each([&](auto&& serverKey) { + QVariantMap data = sg.get(serverKey); ServerInfo server( serverKey, @@ -1042,7 +1250,7 @@ ServerList Settings::getServersFromOldMap() const // a total list.add(std::move(server)); - } + }); return list; } @@ -1052,22 +1260,18 @@ void Settings::updateServers(ServerList servers) // clean up unavailable servers servers.cleanup(); - { - ScopedGroup sg(m_Settings, "Servers"); - m_Settings.remove(""); - } + removeSection(m_Settings, "Servers"); { ScopedWriteArray swa(m_Settings, "Servers"); - int i=0; for (const auto& server : servers) { - m_Settings.setArrayIndex(i); + swa.next(); - m_Settings.setValue("name", server.name()); - m_Settings.setValue("premium", server.isPremium()); - m_Settings.setValue("lastSeen", server.lastSeen().toString(Qt::ISODate)); - m_Settings.setValue("preferred", server.preferred()); + swa.set("name", server.name()); + swa.set("premium", server.isPremium()); + swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); + swa.set("preferred", server.preferred()); QString lastDownloads; for (const auto& speed : server.lastDownloads()) { @@ -1076,9 +1280,7 @@ void Settings::updateServers(ServerList servers) } } - m_Settings.setValue("lastDownloads", lastDownloads.trimmed()); - - ++i; + swa.set("lastDownloads", lastDownloads.trimmed()); } } } @@ -1087,35 +1289,31 @@ std::map Settings::getRecentDirectories() const { std::map map; - ScopedReadArray sra(m_Settings, "recentDirectories"); - - for (int i=0; i("name"); + const QVariant dir = sra.get("directory"); if (name.isValid() && dir.isValid()) { map.emplace(name.toString(), dir.toString()); } - } + }); return map; } void Settings::setRecentDirectories(const std::map& map) { - m_Settings.remove("recentDirectories"); + removeSection(m_Settings, "RecentDirectories"); ScopedWriteArray swa(m_Settings, "recentDirectories"); - int index = 0; for (auto&& p : map) { - m_Settings.setArrayIndex(index); - m_Settings.setValue("name", p.first); - m_Settings.setValue("directory", p.second); + swa.next(); - ++index; + swa.set("name", p.first); + swa.set("directory", p.second); } } @@ -1124,78 +1322,67 @@ std::vector> Settings::getExecutables() const ScopedReadArray sra(m_Settings, "customExecutables"); std::vector> v; - for (int i=0; i map; - const auto keys = m_Settings.childKeys(); - for (auto&& key : keys) { + for (auto&& key : sra.keys()) { map[key] = m_Settings.value(key); } v.push_back(map); - } + }); return v; } void Settings::setExecutables(const std::vector>& v) { - m_Settings.remove("customExecutables"); + removeSection(m_Settings, "customExecutables"); ScopedWriteArray swa(m_Settings, "customExecutables"); - int i = 0; - for (const auto& map : v) { - m_Settings.setArrayIndex(i); + swa.next(); for (auto&& p : map) { - m_Settings.setValue(p.first, p.second); + swa.set(p.first, p.second); } - - ++i; } } bool Settings::isTutorialCompleted(const QString& windowName) const { - const auto v = getOptional( - m_Settings, "CompletedWindowTutorials/" + windowName); - - return v.value_or(false); + return get(m_Settings, "CompletedWindowTutorials", windowName, false); } void Settings::setTutorialCompleted(const QString& windowName, bool b) { - m_Settings.setValue("CompletedWindowTutorials/" + windowName, true); + set(m_Settings, "CompletedWindowTutorials", windowName, b); } bool Settings::keepBackupOnInstall() const { - return getOptional(m_Settings, "backup_install").value_or(false); + return get(m_Settings, "backup_install", false); } void Settings::setKeepBackupOnInstall(bool b) { - m_Settings.setValue("backup_install", b); + set(m_Settings, "General", "backup_install", b); } QuestionBoxMemory::Button Settings::getQuestionButton( const QString& windowName, const QString& filename) const { - const QString windowSetting("DialogChoices/" + windowName); + const QString sectionName("DialogChoices"); if (!filename.isEmpty()) { - const auto fileSetting = windowSetting + "/" + filename; - - if (auto v=getOptional(m_Settings, fileSetting)) { + const auto fileSetting = windowName + "/" + filename; + if (auto v=getOptional(m_Settings, sectionName, filename)) { return static_cast(*v); } } - if (auto v=getOptional(m_Settings, windowSetting)) { + if (auto v=getOptional(m_Settings, sectionName, windowName)) { return static_cast(*v); } @@ -1205,12 +1392,12 @@ QuestionBoxMemory::Button Settings::getQuestionButton( void Settings::setQuestionWindowButton( const QString& windowName, QuestionBoxMemory::Button button) { - const QString settingName("DialogChoices/" + windowName); + const QString sectionName("DialogChoices/"); if (button == QuestionBoxMemory::NoButton) { - m_Settings.remove(settingName); + remove(m_Settings, sectionName, windowName); } else { - m_Settings.setValue(settingName, static_cast(button)); + set(m_Settings, sectionName, windowName, button); } } @@ -1218,51 +1405,51 @@ void Settings::setQuestionFileButton( const QString& windowName, const QString& filename, QuestionBoxMemory::Button button) { - const QString settingName("DialogChoices/" + windowName + "/" + filename); + const QString sectionName("DialogChoices"); + const QString settingName(windowName + "/" + filename); if (button == QuestionBoxMemory::NoButton) { - m_Settings.remove(settingName); + remove(m_Settings, sectionName, settingName); } else { - m_Settings.setValue(settingName, static_cast(button)); + set(m_Settings, sectionName, settingName, button); } } void Settings::resetQuestionButtons() { - ScopedGroup sg(m_Settings, "DialogChoices"); - m_Settings.remove(""); + removeSection(m_Settings, "DialogChoices"); } std::optional Settings::getIndex(const QComboBox* cb) const { - return getOptional(m_Settings, indexSettingName(cb)); + return getOptional(m_Settings, "Widgets", indexSettingName(cb)); } void Settings::saveIndex(const QComboBox* cb) { - m_Settings.setValue(indexSettingName(cb), cb->currentIndex()); + set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); } void Settings::restoreIndex(QComboBox* cb, std::optional def) const { - if (auto v=getOptional(m_Settings, indexSettingName(cb), def)) { + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { cb->setCurrentIndex(*v); } } std::optional Settings::getIndex(const QTabWidget* w) const { - return getOptional(m_Settings, indexSettingName(w)); + return getOptional(m_Settings, "Widgets", indexSettingName(w)); } void Settings::saveIndex(const QTabWidget* w) { - m_Settings.setValue(indexSettingName(w), w->currentIndex()); + set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); } void Settings::restoreIndex(QTabWidget* w, std::optional def) const { - if (auto v=getOptional(m_Settings, indexSettingName(w), def)) { + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { w->setCurrentIndex(*v); } } @@ -1270,20 +1457,20 @@ void Settings::restoreIndex(QTabWidget* w, std::optional def) const std::optional Settings::getChecked(const QAbstractButton* w) const { warnIfNotCheckable(w); - return getOptional(m_Settings, checkedSettingName(w)); + return getOptional(m_Settings, "Widgets", checkedSettingName(w)); } void Settings::saveChecked(const QAbstractButton* w) { warnIfNotCheckable(w); - m_Settings.setValue(checkedSettingName(w), w->isChecked()); + set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); } void Settings::restoreChecked(QAbstractButton* w, std::optional def) const { warnIfNotCheckable(w); - if (auto v=getOptional(m_Settings, checkedSettingName(w), def)) { + if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { w->setChecked(*v); } } @@ -1328,7 +1515,7 @@ void Settings::dump() const { static const QStringList ignore({ "username", "password", "nexus_api_key" - }); + }); log::debug("settings:"); @@ -1379,18 +1566,17 @@ void GeometrySettings::resetIfNeeded() return; } - ScopedGroup sg(m_Settings, "geometry"); - m_Settings.remove(""); + removeSection(m_Settings, "Geometry"); } void GeometrySettings::saveGeometry(const QWidget* w) { - m_Settings.setValue(geoSettingName(w), w->saveGeometry()); + set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); } bool GeometrySettings::restoreGeometry(QWidget* w) const { - if (auto v=getOptional(m_Settings, geoSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { w->restoreGeometry(*v); return true; } @@ -1400,12 +1586,12 @@ bool GeometrySettings::restoreGeometry(QWidget* w) const void GeometrySettings::saveState(const QMainWindow* w) { - m_Settings.setValue(stateSettingName(w), w->saveState()); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } bool GeometrySettings::restoreState(QMainWindow* w) const { - if (auto v=getOptional(m_Settings, stateSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -1415,12 +1601,12 @@ bool GeometrySettings::restoreState(QMainWindow* w) const void GeometrySettings::saveState(const QHeaderView* w) { - m_Settings.setValue(stateSettingName(w), w->saveState()); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } bool GeometrySettings::restoreState(QHeaderView* w) const { - if (auto v=getOptional(m_Settings, stateSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -1430,12 +1616,12 @@ bool GeometrySettings::restoreState(QHeaderView* w) const void GeometrySettings::saveState(const QSplitter* w) { - m_Settings.setValue(stateSettingName(w), w->saveState()); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } bool GeometrySettings::restoreState(QSplitter* w) const { - if (auto v=getOptional(m_Settings, stateSettingName(w))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { w->restoreState(*v); return true; } @@ -1445,12 +1631,12 @@ bool GeometrySettings::restoreState(QSplitter* w) const void GeometrySettings::saveState(const ExpanderWidget* expander) { - m_Settings.setValue(stateSettingName(expander), expander->saveState()); + set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); } bool GeometrySettings::restoreState(ExpanderWidget* expander) const { - if (auto v=getOptional(m_Settings, stateSettingName(expander))) { + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { expander->restoreState(*v); return true; } @@ -1460,12 +1646,12 @@ bool GeometrySettings::restoreState(ExpanderWidget* expander) const void GeometrySettings::saveVisibility(const QWidget* w) { - m_Settings.setValue(visibilitySettingName(w), w->isVisible()); + set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); } bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - if (auto v=getOptional(m_Settings, visibilitySettingName(w), def)) { + if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { w->setVisible(*v); return true; } @@ -1476,8 +1662,8 @@ bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) co void GeometrySettings::restoreToolbars(QMainWindow* w) const { // all toolbars have the same size and button style settings - const auto size = getOptional(m_Settings, "toolbar_size"); - const auto style = getOptional(m_Settings, "toolbar_button_style"); + const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); + const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); for (auto* tb : w->findChildren()) { if (size) { @@ -1506,8 +1692,8 @@ void GeometrySettings::saveToolbars(const QMainWindow* w) if (!tbs.isEmpty()) { const auto* tb = tbs[0]; - m_Settings.setValue("toolbar_size", tb->iconSize()); - m_Settings.setValue("toolbar_button_style", static_cast(tb->toolButtonStyle())); + set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); + set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); } } @@ -1544,12 +1730,13 @@ QStringList GeometrySettings::getModInfoTabOrder() const void GeometrySettings::setModInfoTabOrder(const QString& names) { - m_Settings.setValue("mod_info_tab_order", names); + set(m_Settings, "Geometry", "mod_info_tab_order", names); } void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { - const auto monitor = getOptional(m_Settings, "geometry/MainWindow_monitor"); + const auto monitor = getOptional( + m_Settings, "Geometry", "MainWindow_monitor"); QPoint center; @@ -1567,7 +1754,7 @@ void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) if (auto* handle=w->windowHandle()) { if (auto* screen = handle->screen()) { const int screenId = QGuiApplication::screens().indexOf(screen); - m_Settings.setValue("geometry/MainWindow_monitor", screenId); + set(m_Settings, "Geometry", "MainWindow_monitor", screenId); } } } @@ -1617,7 +1804,7 @@ void GeometrySettings::saveDocks(const QMainWindow* mw) size = dock->size().height(); } - m_Settings.setValue(dockSettingName(dock), size); + set(m_Settings, "Geometry", dockSettingName(dock), size); } } @@ -1634,7 +1821,7 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const // for each dock for (auto* dock : mw->findChildren()) { - if (auto size=getOptional(m_Settings, dockSettingName(dock))) { + if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { // remember this dock, its size and orientation dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); } @@ -1649,7 +1836,7 @@ void GeometrySettings::restoreDocks(QMainWindow* mw) const for (const auto& info : dockInfos) { mw->resizeDocks({info.d}, {info.size}, info.ori); } - }); + }); } @@ -1660,68 +1847,74 @@ ColorSettings::ColorSettings(QSettings& s) QColor ColorSettings::modlistOverwrittenLoose() const { - return getOptional(m_Settings, "Settings/overwrittenLooseFilesColor") - .value_or(QColor(0, 255, 0, 64)); + return get( + m_Settings, "Settings", "overwrittenLooseFilesColor", + QColor(0, 255, 0, 64)); } void ColorSettings::setModlistOverwrittenLoose(const QColor& c) { - m_Settings.setValue("Settings/overwrittenLooseFilesColor", c); + set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); } QColor ColorSettings::modlistOverwritingLoose() const { - return getOptional(m_Settings, "Settings/overwritingLooseFilesColor") - .value_or(QColor(255, 0, 0, 64)); + return get( + m_Settings, "Settings", "overwritingLooseFilesColor", + QColor(255, 0, 0, 64)); } void ColorSettings::setModlistOverwritingLoose(const QColor& c) { - m_Settings.setValue("Settings/overwritingLooseFilesColor", c); + set(m_Settings, "Settings", "overwritingLooseFilesColor", c); } QColor ColorSettings::modlistOverwrittenArchive() const { - return getOptional(m_Settings, "Settings/overwrittenArchiveFilesColor") - .value_or(QColor(0, 255, 255, 64)); + return get( + m_Settings, "Settings", "overwrittenArchiveFilesColor", + QColor(0, 255, 255, 64)); } void ColorSettings::setModlistOverwrittenArchive(const QColor& c) { - m_Settings.setValue("Settings/overwrittenArchiveFilesColor", c); + set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); } QColor ColorSettings::modlistOverwritingArchive() const { - return getOptional(m_Settings, "Settings/overwritingArchiveFilesColor") - .value_or(QColor(255, 0, 255, 64)); + return get( + m_Settings, "Settings", "overwritingArchiveFilesColor", + QColor(255, 0, 255, 64)); } void ColorSettings::setModlistOverwritingArchive(const QColor& c) { - m_Settings.setValue("Settings/overwritingArchiveFilesColor", c); + set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); } QColor ColorSettings::modlistContainsPlugin() const { - return getOptional(m_Settings, "Settings/containsPluginColor") - .value_or(QColor(0, 0, 255, 64)); + return get( + m_Settings, "Settings", "containsPluginColor", + QColor(0, 0, 255, 64)); } void ColorSettings::setModlistContainsPlugin(const QColor& c) { - m_Settings.setValue("Settings/containsPluginColor", c); + set(m_Settings, "Settings", "containsPluginColor", c); } QColor ColorSettings::pluginListContained() const { - return getOptional(m_Settings, "Settings/containedColor") - .value_or(QColor(0, 0, 255, 64)); + return get( + m_Settings, "Settings", "containedColor", + QColor(0, 0, 255, 64)); } void ColorSettings::setPluginListContained(const QColor& c) { - m_Settings.setValue("Settings/containedColor", c); + set(m_Settings, "Settings", "containedColor", c); } @@ -1738,10 +1931,9 @@ void PluginSettings::clearPlugins() m_PluginBlacklist.clear(); ScopedReadArray sra(m_Settings, "pluginBlacklist"); - for (int i = 0; i < sra.count(); ++i) { - m_Settings.setArrayIndex(i); - m_PluginBlacklist.insert(m_Settings.value("name").toString()); - } + sra.for_each([&]{ + m_PluginBlacklist.insert(sra.get("name")); + }); } void PluginSettings::registerPlugin(IPlugin *plugin) @@ -1749,16 +1941,26 @@ void PluginSettings::registerPlugin(IPlugin *plugin) m_Plugins.push_back(plugin); m_PluginSettings.insert(plugin->name(), QVariantMap()); m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + for (const PluginSetting &setting : plugin->settings()) { - QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); + const QString settingName = plugin->name() + "/" + setting.key; + + QVariant temp = get( + m_Settings, "Plugins", settingName, setting.defaultValue); + if (!temp.convert(setting.defaultValue.type())) { log::warn( "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", temp.toString(), setting.key, plugin->name()); + temp = setting.defaultValue; } + m_PluginSettings[plugin->name()][setting.key] = temp; - m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)").arg(setting.description).arg(setting.defaultValue.toString()); + + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") + .arg(setting.description) + .arg(setting.defaultValue.toString()); } } @@ -1773,6 +1975,7 @@ QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString if (iterPlugin == m_PluginSettings.end()) { return QVariant(); } + auto iterSetting = iterPlugin->find(key); if (iterSetting == iterPlugin->end()) { return QVariant(); @@ -1784,13 +1987,16 @@ QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { - throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } // store the new setting both in memory and in the ini m_PluginSettings[pluginName][key] = value; - m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); + set(m_Settings, "Plugins", pluginName + "/" + key, value); } QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const @@ -1798,15 +2004,21 @@ QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QStri if (!m_PluginSettings.contains(pluginName)) { return def; } - return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); + + return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); } -void PluginSettings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +void PluginSettings::setPluginPersistent( + const QString &pluginName, const QString &key, const QVariant &value, bool sync) { if (!m_PluginSettings.contains(pluginName)) { - throw MyException(QObject::tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } - m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); + + set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + if (sync) { m_Settings.sync(); } @@ -1820,13 +2032,13 @@ void PluginSettings::addBlacklistPlugin(const QString &fileName) void PluginSettings::writePluginBlacklist() { - m_Settings.remove("pluginBlacklist"); + removeSection(m_Settings, "PluginBlacklist"); + + ScopedWriteArray swa(m_Settings, "PluginBlacklist"); - ScopedWriteArray swa(m_Settings, "pluginBlacklist"); - int idx = 0; for (const QString &plugin : m_PluginBlacklist) { - m_Settings.setArrayIndex(idx++); - m_Settings.setValue("name", plugin); + swa.next(); + swa.set("name", plugin); } } @@ -1868,8 +2080,8 @@ void PluginSettings::save() { for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { - const auto key = "Plugins/" + iterPlugins.key() + "/" + iterSettings.key(); - m_Settings.setValue(key, iterSettings.value()); + const auto key = iterPlugins.key() + "/" + iterSettings.key(); + set(m_Settings, "Plugins", key, iterSettings.value()); } } diff --git a/src/settings.h b/src/settings.h index ae29d788..403c2d71 100644 --- a/src/settings.h +++ b/src/settings.h @@ -68,9 +68,6 @@ public: void saveState(const QHeaderView* header); bool restoreState(QHeaderView* header) const; - void saveState(const QToolBar* toolbar); - bool restoreState(QToolBar* toolbar) const; - void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; @@ -258,8 +255,6 @@ public: std::optional getStyleName() const; void setStyleName(const QString& name); - std::optional getUseProxy() const; - std::optional getVersion() const; bool getFirstStart() const; @@ -408,7 +403,7 @@ public: /** * @return true if the user configured the use of a network proxy */ - bool useProxy() const; + bool getUseProxy() const; void setUseProxy(bool b); /** @@ -419,7 +414,6 @@ public: EndorsementState endorsementState() const; void setEndorsementState(EndorsementState s); - void setEndorsementState(const QString& s); /** * @return true if the API counter should be hidden @@ -436,7 +430,8 @@ public: /** * @brief sets the new motd hash **/ - void setMotDHash(uint hash); + unsigned int getMotDHash() const; + void setMotDHash(unsigned int hash); /** * @return true if the user wants to have archives being parsed to show conflicts and contents @@ -444,11 +439,6 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); - /** - * @return hash of the last displayed message of the day - **/ - uint getMotDHash() const; - /** * @return short code of the configured language (corresponding to the translation files) */ diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 3de1a6ba..8822200e 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -75,7 +75,7 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { ui->offlineBox->setChecked(settings().offlineMode()); - ui->proxyBox->setChecked(settings().useProxy()); + ui->proxyBox->setChecked(settings().getUseProxy()); ui->endorsementBox->setChecked(settings().endorsementIntegration()); ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); -- cgit v1.3.1 From e9dba260cb9548dd5863ac66da18c295f6499b92 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 14:52:02 -0400 Subject: split settings into a bunch of classes removed "get" from the getters that had it --- src/browserdialog.cpp | 2 +- src/downloadlistsortproxy.cpp | 2 +- src/downloadmanager.cpp | 2 +- src/executableslist.cpp | 2 +- src/filedialogmemory.cpp | 4 +- src/main.cpp | 26 +- src/mainwindow.cpp | 121 +-- src/modinfodialog.cpp | 2 +- src/modinfodialogconflicts.cpp | 16 +- src/modinfodialogimages.cpp | 4 +- src/modinfodialognexus.cpp | 2 +- src/modinfooverwrite.cpp | 2 +- src/modinforegular.cpp | 2 +- src/modlist.cpp | 8 +- src/nxmaccessmanager.cpp | 2 +- src/organizercore.cpp | 112 +- src/pluginlist.cpp | 2 +- src/profile.cpp | 8 +- src/profilesdialog.cpp | 4 +- src/settings.cpp | 2075 ++++++++++++++++++++----------------- src/settings.h | 569 ++++++---- src/settingsdialog.cpp | 6 +- src/settingsdialogdiagnostics.cpp | 13 +- src/settingsdialoggeneral.cpp | 28 +- src/settingsdialognexus.cpp | 32 +- src/settingsdialogpaths.cpp | 52 +- src/settingsdialogsteam.cpp | 4 +- src/settingsdialogworkarounds.cpp | 30 +- src/statusbar.cpp | 2 +- src/usvfsconnector.cpp | 4 +- 30 files changed, 1712 insertions(+), 1426 deletions(-) (limited to 'src') diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 70da0b9c..72cb8862 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -49,7 +49,7 @@ BrowserDialog::BrowserDialog(QWidget *parent) ui->setupUi(this); m_AccessManager->setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/cookies.dat"))); + QDir::fromNativeSeparators(Settings::instance().paths().cache() + "/cookies.dat"))); Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index 7bda139b..a69993c0 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -110,7 +110,7 @@ bool DownloadListSortProxy::filterAcceptsRow(int sourceRow, const QModelIndex&) if (m_CurrentFilter.length() == 0) { return true; } else if (sourceRow < m_Manager->numTotalDownloads()) { - QString displayedName = Settings::instance().metaDownloads() + QString displayedName = Settings::instance().interface().metaDownloads() ? m_Manager->getDisplayName(sourceRow) : m_Manager->getFileName(sourceRow); return displayedName.contains(m_CurrentFilter, Qt::CaseInsensitive); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index a5dc164c..56238ef3 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1731,7 +1731,7 @@ void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int return; } - const auto servers = m_OrganizerCore->settings().getServers(); + const auto servers = m_OrganizerCore->settings().network().servers(); std::sort( resultList.begin(), diff --git a/src/executableslist.cpp b/src/executableslist.cpp index f2df2d6d..dce9181b 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -75,7 +75,7 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) // executables from 2.2.0, see upgradeFromCustom() bool needsUpgrade = false; - for (auto& map : s.getExecutables()) { + for (auto& map : s.executables()) { Executable::Flags flags; if (map["toolbar"].toBool()) diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 96587ac7..8cfeb6b5 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -25,12 +25,12 @@ static std::map g_Cache; void FileDialogMemory::save(Settings& s) { - s.setRecentDirectories(g_Cache); + s.paths().setRecent(g_Cache); } void FileDialogMemory::restore(const Settings& s) { - g_Cache = s.getRecentDirectories(); + g_Cache = s.paths().recent(); } QString FileDialogMemory::getOpenFileName( diff --git a/src/main.cpp b/src/main.cpp index aa781c19..b5568fec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -246,7 +246,7 @@ static bool HaveWriteAccess(const std::wstring &path) QString determineProfile(QStringList &arguments, const Settings &settings) { - auto selectedProfileName = settings.getSelectedProfileName(); + auto selectedProfileName = settings.game().selectedProfileName(); { // see if there is a profile on the command line int profileIndex = arguments.indexOf("-p", 1); @@ -271,12 +271,12 @@ QString determineProfile(QStringList &arguments, const Settings &settings) MOBase::IPluginGame *selectGame( Settings &settings, QDir const &gamePath, MOBase::IPluginGame *game) { - settings.setManagedGameName(game->gameName()); + settings.game().setName(game->gameName()); QString gameDir = gamePath.absolutePath(); game->setGamePath(gameDir); - settings.setManagedGameDirectory(gameDir); + settings.game().setDirectory(gameDir); return game; } @@ -289,7 +289,7 @@ MOBase::IPluginGame *determineCurrentGame( //user has done something odd. //If the game name has been set up, try to use that. - const auto gameName = settings.getManagedGameName(); + const auto gameName = settings.game().name(); const bool gameConfigured = (gameName.has_value() && *gameName != ""); if (gameConfigured) { @@ -299,7 +299,7 @@ MOBase::IPluginGame *determineCurrentGame( return nullptr; } - auto gamePath = settings.getManagedGameDirectory(); + auto gamePath = settings.game().directory(); if (!gamePath || *gamePath == "") { gamePath = game->gameDirectory().absolutePath(); } @@ -320,7 +320,7 @@ MOBase::IPluginGame *determineCurrentGame( //If we've made it this far and the instance is already configured for a game, something has gone wrong. //Tell the user about it. if (gameConfigured) { - const auto gamePath = settings.getManagedGameDirectory(); + const auto gamePath = settings.game().directory(); reportError( QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") @@ -570,11 +570,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, log::info("working directory: {}", QDir::currentPath()); Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); - log::getDefault().setLevel(settings.logLevel()); + log::getDefault().setLevel(settings.diagnostics().logLevel()); // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime - OrganizerCore::setGlobalCrashDumpsType(settings.crashDumpsType()); + OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); env::Environment env; @@ -621,7 +621,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, QString edition; - if (auto v=settings.getManagedGameEdition()) { + if (auto v=settings.game().edition()) { edition = *v; } else { QStringList editions = game->gameVariants(); @@ -640,7 +640,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, return 1; } else { edition = selection.getChoiceString(); - settings.setManagedGameEdition(edition); + settings.game().setEdition(edition); } } } @@ -702,7 +702,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.activateWindow(); QString apiKey; - if (settings.getNexusApiKey(apiKey)) { + if (settings.nexus().apiKey(apiKey)) { NexusInterface::instance(&pluginContainer)->getAccessManager()->apiCheck(apiKey); } @@ -712,9 +712,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); - if (!application.setStyleFile(settings.getStyleName().value_or(""))) { + if (!application.setStyleFile(settings.interface().styleName().value_or(""))) { // disable invalid stylesheet - settings.setStyleName(""); + settings.interface().setStyleName(""); } int res = 1; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42b19cb7..657c1a27 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -222,8 +222,8 @@ MainWindow::MainWindow(Settings &settings { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); - QWebEngineProfile::defaultProfile()->setCachePath(settings.getCacheDirectory()); - QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setCachePath(settings.paths().cache()); + QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); ui->setupUi(this); ui->statusBar->setup(ui); @@ -253,7 +253,7 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } - languageChange(settings.language()); + languageChange(settings.interface().language()); m_CategoryFactory.loadCategories(); @@ -1194,7 +1194,7 @@ void MainWindow::hookUpWindowTutorials() QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//WIN")) { QString windowName = firstLine.mid(6).trimmed(); - if (!m_OrganizerCore.settings().isTutorialCompleted(windowName)) { + if (!m_OrganizerCore.settings().interface().isTutorialCompleted(windowName)) { TutorialManager::instance().activateTutorial(windowName, fileName); } } @@ -1225,7 +1225,7 @@ void MainWindow::showEvent(QShowEvent *event) hookUpWindowTutorials(); - if (m_OrganizerCore.settings().getFirstStart()) { + if (m_OrganizerCore.settings().firstStart()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { if (QMessageBox::question(this, tr("Show tutorial?"), @@ -1247,11 +1247,11 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().setFirstStart(false); } - m_OrganizerCore.settings().restoreIndex(ui->groupCombo); + m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo); allowListResize(); - m_OrganizerCore.settings().registerAsNXMHandler(false); + m_OrganizerCore.settings().nexus().registerAsNXMHandler(false); m_WasVisible = true; updateProblemsButton(); } @@ -1751,7 +1751,7 @@ bool MainWindow::refreshProfiles(bool selectProfile) profileBox->clear(); profileBox->addItem(QObject::tr("")); - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); @@ -1990,7 +1990,7 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString newItem->setFlags(newItem->flags() & ~(Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable)); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); newItem->setData(0, Qt::UserRole, false); - if (m_OrganizerCore.settings().forceEnableCoreFiles() + if (m_OrganizerCore.settings().game().forceEnableCoreFiles() && defaultArchives.contains(fileInfo.fileName())) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); @@ -2140,7 +2140,7 @@ void MainWindow::readSettings(const Settings& settings) { // special case in case someone puts 0 in the INI - auto v = settings.getIndex(ui->executablesListBox); + auto v = settings.widgets().index(ui->executablesListBox); if (!v || v == 0) { v = 1; } @@ -2148,7 +2148,7 @@ void MainWindow::readSettings(const Settings& settings) ui->executablesListBox->setCurrentIndex(*v); } - settings.restoreIndex(ui->groupCombo); + settings.widgets().restoreIndex(ui->groupCombo); { settings.geometry().restoreVisibility(ui->categoriesGroup, false); @@ -2157,7 +2157,7 @@ void MainWindow::readSettings(const Settings& settings) ui->displayCategoriesBtn->setChecked(v); } - if (settings.getUseProxy()) { + if (settings.network().useProxy()) { activateProxy(true); } } @@ -2165,12 +2165,12 @@ void MainWindow::readSettings(const Settings& settings) void MainWindow::processUpdates(Settings& settings) { const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); - const auto lastVersion = settings.getVersion().value_or(earliest); + const auto lastVersion = settings.version().value_or(earliest); const auto currentVersion = m_OrganizerCore.getVersion().asQVersionNumber(); settings.processUpdates(currentVersion, lastVersion); - if (!settings.getFirstStart()) { + if (!settings.firstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { @@ -2222,8 +2222,8 @@ void MainWindow::storeSettings(Settings& s) s.geometry().saveState(ui->downloadView->header()); s.geometry().saveState(ui->modList->header()); - s.saveIndex(ui->groupCombo); - s.saveIndex(ui->executablesListBox); + s.widgets().saveIndex(ui->groupCombo); + s.widgets().saveIndex(ui->executablesListBox); } ILockedWaitingForProcess* MainWindow::lock() @@ -2751,7 +2751,7 @@ void MainWindow::restoreBackup_clicked() ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); if (backupRegEx.indexIn(modInfo->name()) != -1) { QString regName = backupRegEx.cap(1); - QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory())); + QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods())); if (!modDir.exists(regName) || (QMessageBox::question(this, tr("Overwrite?"), tr("This will replace the existing mod \"%1\". Continue?").arg(regName), @@ -2759,7 +2759,7 @@ void MainWindow::restoreBackup_clicked() if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { reportError(tr("failed to remove mod \"%1\"").arg(regName)); } else { - QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory()) + "/" + regName; + QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().paths().mods()) + "/" + regName; if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); } @@ -3015,7 +3015,7 @@ void MainWindow::untrack_clicked() void MainWindow::windowTutorialFinished(const QString &windowName) { - m_OrganizerCore.settings().setTutorialCompleted(windowName); + m_OrganizerCore.settings().interface().setTutorialCompleted(windowName); } void MainWindow::overwriteClosed(int) @@ -3645,7 +3645,7 @@ void MainWindow::createSeparator_clicked() m_OrganizerCore.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); } - if (auto c=m_OrganizerCore.settings().getPreviousSeparatorColor()) { + if (auto c=m_OrganizerCore.settings().colors().previousSeparatorColor()) { ModInfo::getByIndex(ModInfo::getIndex(name))->setColor(*c); } } @@ -3662,7 +3662,7 @@ void MainWindow::setColor_clicked() if (currentColor.isValid()) { dialog.setCurrentColor(currentColor); } - else if (auto c=settings.getPreviousSeparatorColor()) { + else if (auto c=settings.colors().previousSeparatorColor()) { dialog.setCurrentColor(*c); } @@ -3673,7 +3673,7 @@ void MainWindow::setColor_clicked() if (!currentColor.isValid()) return; - settings.setPreviousSeparatorColor(currentColor); + settings.colors().setPreviousSeparatorColor(currentColor); QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { @@ -3710,7 +3710,7 @@ void MainWindow::resetColor_clicked() modInfo->setColor(color); } - m_OrganizerCore.settings().removePreviousSeparatorColor(); + m_OrganizerCore.settings().colors().removePreviousSeparatorColor(); } void MainWindow::createModFromOverwrite() @@ -4184,7 +4184,7 @@ void MainWindow::checkModsForUpdates() NexusInterface::instance(&m_PluginContainer)->requestTrackingInfo(this, QVariant(), QString()); } else { QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { @@ -4387,12 +4387,12 @@ void MainWindow::openIniFolder() void MainWindow::openDownloadsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().getDownloadDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().paths().downloads()); } void MainWindow::openModsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().getModDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().paths().mods()); } void MainWindow::openGameFolder() @@ -4758,7 +4758,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.addSeparator(); - if (info->getNexusID() > 0 && Settings::instance().endorsementIntegration()) { + if (info->getNexusID() > 0 && Settings::instance().nexus().endorsementIntegration()) { switch (info->endorsedState()) { case ModInfo::ENDORSED_TRUE: { menu.addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); @@ -5007,19 +5007,19 @@ void MainWindow::on_actionSettings_triggered() { Settings &settings = m_OrganizerCore.settings(); - QString oldModDirectory(settings.getModDirectory()); - QString oldCacheDirectory(settings.getCacheDirectory()); - QString oldProfilesDirectory(settings.getProfileDirectory()); - QString oldManagedGameDirectory(settings.getManagedGameDirectory().value_or("")); - bool oldDisplayForeign(settings.displayForeign()); - bool proxy = settings.getUseProxy(); + QString oldModDirectory(settings.paths().mods()); + QString oldCacheDirectory(settings.paths().cache()); + QString oldProfilesDirectory(settings.paths().profiles()); + QString oldManagedGameDirectory(settings.game().directory().value_or("")); + bool oldDisplayForeign(settings.interface().displayForeign()); + bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); - if (oldManagedGameDirectory != settings.getManagedGameDirectory()) { + if (oldManagedGameDirectory != settings.game().directory()) { QMessageBox::about(this, tr("Restarting MO"), tr("Changing the managed game directory requires restarting MO.\n" "Any pending downloads will be paused.\n\n" @@ -5029,28 +5029,28 @@ void MainWindow::on_actionSettings_triggered() } InstallationManager *instManager = m_OrganizerCore.installationManager(); - instManager->setModsDirectory(settings.getModDirectory()); - instManager->setDownloadDirectory(settings.getDownloadDirectory()); + instManager->setModsDirectory(settings.paths().mods()); + instManager->setDownloadDirectory(settings.paths().downloads()); fixCategories(); refreshFilters(); - if (settings.getProfileDirectory() != oldProfilesDirectory) { + if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); } - if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) { + if (dlManager->getOutputDirectory() != settings.paths().downloads()) { if (dlManager->downloadsInProgress()) { MessageDialog::showMessage(tr("Can't change download directory while " "downloads are in progress!"), this); } else { - dlManager->setOutputDirectory(settings.getDownloadDirectory()); + dlManager->setOutputDirectory(settings.paths().downloads()); } } - if ((settings.getModDirectory() != oldModDirectory) - || (settings.displayForeign() != oldDisplayForeign)) { + if ((settings.paths().mods() != oldModDirectory) + || (settings.interface().displayForeign() != oldDisplayForeign)) { m_OrganizerCore.profileRefresh(); } @@ -5075,18 +5075,19 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.refreshLists(); } - if (settings.getCacheDirectory() != oldCacheDirectory) { - NexusInterface::instance(&m_PluginContainer)->setCacheDirectory(settings.getCacheDirectory()); + if (settings.paths().cache() != oldCacheDirectory) { + NexusInterface::instance(&m_PluginContainer)->setCacheDirectory( + settings.paths().cache()); } - if (proxy != settings.getUseProxy()) { - activateProxy(settings.getUseProxy()); + if (proxy != settings.network().useProxy()) { + activateProxy(settings.network().useProxy()); } ui->statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); - m_OrganizerCore.setLogLevel(settings.logLevel()); + m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); @@ -5402,10 +5403,10 @@ void MainWindow::motdReceived(const QString &motd) // internet connection is faster next time if (m_StartTime.secsTo(QTime::currentTime()) < 5) { uint hash = qHash(motd); - if (hash != m_OrganizerCore.settings().getMotDHash()) { + if (hash != m_OrganizerCore.settings().motdHash()) { MotDDialog dialog(motd); dialog.exec(); - m_OrganizerCore.settings().setMotDHash(hash); + m_OrganizerCore.settings().setMotdHash(hash); } } } @@ -5528,7 +5529,7 @@ void MainWindow::initDownloadView() void MainWindow::updateDownloadView() { // set the view attribute and default row sizes - if (m_OrganizerCore.settings().compactDownloads()) { + if (m_OrganizerCore.settings().interface().compactDownloads()) { ui->downloadView->setProperty("downloadView", "compact"); setStyleSheet("DownloadListWidget::item { padding: 4px 2px; }"); } else { @@ -5541,7 +5542,7 @@ void MainWindow::updateDownloadView() // reapply global stylesheet on the widget level (!) to override the defaults //ui->downloadView->setStyleSheet(styleSheet()); - ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().metaDownloads()); + ui->downloadView->setMetaDisplay(m_OrganizerCore.settings().interface().metaDownloads()); ui->downloadView->style()->unpolish(ui->downloadView); ui->downloadView->style()->polish(ui->downloadView); qobject_cast(ui->downloadView->header())->customResizeSections(); @@ -5554,7 +5555,7 @@ void MainWindow::modUpdateCheck(std::multimap IDs) ModInfo::manualUpdateCheck(&m_PluginContainer, this, IDs); } else { QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { + if (m_OrganizerCore.settings().nexus().apiKey(apiKey)) { m_OrganizerCore.doAfterLogin([=]() { this->modUpdateCheck(IDs); }); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else @@ -5566,7 +5567,7 @@ void MainWindow::toggleMO2EndorseState() { const auto& s = m_OrganizerCore.settings(); - if (!s.endorsementIntegration()) { + if (!s.nexus().endorsementIntegration()) { ui->actionEndorseMO->setVisible(false); return; } @@ -5576,7 +5577,7 @@ void MainWindow::toggleMO2EndorseState() bool enabled = false; QString text; - switch (s.endorsementState()) + switch (s.nexus().endorsementState()) { case EndorsementState::Accepted: { @@ -5631,9 +5632,9 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData mod->setIsEndorsed(false); } - if (Settings::instance().endorsementIntegration()) { + if (Settings::instance().nexus().endorsementIntegration()) { if (result->first == "skyrimspecialedition" && result->second.first == gamePlugin->nexusModOrganizerID()) { - m_OrganizerCore.settings().setEndorsementState( + m_OrganizerCore.settings().nexus().setEndorsementState( endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); @@ -5642,13 +5643,13 @@ void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData } } - if (!searchedMO2NexusGame && Settings::instance().endorsementIntegration()) { + if (!searchedMO2NexusGame && Settings::instance().nexus().endorsementIntegration()) { auto gamePlugin = m_OrganizerCore.getGame("SkyrimSE"); if (gamePlugin) { auto iter = sorted.equal_range(gamePlugin->gameNexusName()); for (auto result = iter.first; result != iter.second; ++result) { if (result->second.first == gamePlugin->nexusModOrganizerID()) { - m_OrganizerCore.settings().setEndorsementState( + m_OrganizerCore.settings().nexus().setEndorsementState( endorsementStateFromString(result->second.second)); toggleMO2EndorseState(); @@ -5862,7 +5863,7 @@ void MainWindow::nxmEndorsementToggled(QString, int, QVariant, QVariant resultDa } } - m_OrganizerCore.settings().setEndorsementState(s); + m_OrganizerCore.settings().nexus().setEndorsementState(s); toggleMO2EndorseState(); if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), @@ -5901,7 +5902,7 @@ void MainWindow::nxmTrackedModsAvailable(QVariant userData, QVariant resultData, void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultData, int) { - auto servers = m_OrganizerCore.settings().getServers(); + auto servers = m_OrganizerCore.settings().network().servers(); for (const QVariant &var : resultData.toList()) { const QVariantMap map = var.toMap(); @@ -5929,7 +5930,7 @@ void MainWindow::nxmDownloadURLs(QString, int, int, QVariant, QVariant resultDat } } - m_OrganizerCore.settings().updateServers(servers); + m_OrganizerCore.settings().network().updateServers(servers); } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f3840230..2178ef34 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -381,7 +381,7 @@ void ModInfoDialog::reAddTabs( Q_ASSERT(visibility.size() == m_tabs.size()); // ordered tab names from settings - const auto orderedNames = m_core->settings().geometry().getModInfoTabOrder(); + const auto orderedNames = m_core->settings().geometry().modInfoTabOrder(); // whether the tabs can be sorted; if the object name of a tab widget is not // found in orderedNames, the list cannot be sorted safely diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 7840269d..3a71b405 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -409,7 +409,7 @@ void ConflictsTab::clear() void ConflictsTab::saveState(Settings& s) { - s.saveIndex(ui->tabConflictsTabs); + s.widgets().saveIndex(ui->tabConflictsTabs); m_general.saveState(s); m_advanced.saveState(s); @@ -417,7 +417,7 @@ void ConflictsTab::saveState(Settings& s) void ConflictsTab::restoreState(const Settings& s) { - s.restoreIndex(ui->tabConflictsTabs, 0); + s.widgets().restoreIndex(ui->tabConflictsTabs, 0); m_general.restoreState(s); m_advanced.restoreState(s); @@ -1014,17 +1014,17 @@ void AdvancedConflictsTab::clear() void AdvancedConflictsTab::saveState(Settings& s) { s.geometry().saveState(ui->conflictsAdvancedList->header()); - s.saveChecked(ui->conflictsAdvancedShowNoConflict); - s.saveChecked(ui->conflictsAdvancedShowAll); - s.saveChecked(ui->conflictsAdvancedShowNearest); + s.widgets().saveChecked(ui->conflictsAdvancedShowNoConflict); + s.widgets().saveChecked(ui->conflictsAdvancedShowAll); + s.widgets().saveChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::restoreState(const Settings& s) { s.geometry().restoreState(ui->conflictsAdvancedList->header()); - s.restoreChecked(ui->conflictsAdvancedShowNoConflict); - s.restoreChecked(ui->conflictsAdvancedShowAll); - s.restoreChecked(ui->conflictsAdvancedShowNearest); + s.widgets().restoreChecked(ui->conflictsAdvancedShowNoConflict); + s.widgets().restoreChecked(ui->conflictsAdvancedShowAll); + s.widgets().restoreChecked(ui->conflictsAdvancedShowNearest); } void AdvancedConflictsTab::update() diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 38c12d8a..9d347f57 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -130,13 +130,13 @@ void ImagesTab::update() void ImagesTab::saveState(Settings& s) { - s.saveChecked(ui->imagesShowDDS); + s.widgets().saveChecked(ui->imagesShowDDS); s.geometry().saveState(ui->tabImagesSplitter); } void ImagesTab::restoreState(const Settings& s) { - s.restoreChecked(ui->imagesShowDDS); + s.widgets().restoreChecked(ui->imagesShowDDS); s.geometry().restoreState(ui->tabImagesSplitter); } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 6d28cbe3..95e62328 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -19,7 +19,7 @@ NexusTab::NexusTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); - ui->endorse->setVisible(core().settings().endorsementIntegration()); + ui->endorse->setVisible(core().settings().nexus().endorsementIntegration()); connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); connect( diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 37c8c650..fb110abb 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -23,7 +23,7 @@ bool ModInfoOverwrite::isEmpty() const QString ModInfoOverwrite::absolutePath() const { - return Settings::instance().getOverwriteDirectory(); + return Settings::instance().paths().overwrite(); } std::vector ModInfoOverwrite::getFlags() const diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index ce29e11e..3cff914a 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -631,7 +631,7 @@ std::vector ModInfoRegular::getFlags() const std::vector result = ModInfoWithConflictInfo::getFlags(); if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE) && - Settings::instance().endorsementIntegration()) { + Settings::instance().nexus().endorsementIntegration()) { result.push_back(ModInfo::FLAG_NOTENDORSED); } if ((m_NexusID > 0) && diff --git a/src/modlist.cpp b/src/modlist.cpp index 94b4a387..6018d3d4 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -390,7 +390,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QVariant(); } else if (role == Qt::ForegroundRole) { if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid()) { - return Settings::getIdealTextColor(modInfo->getColor()); + return ColorSettings::idealTextColor(modInfo->getColor()); } else if (column == COL_NAME) { int highlight = modInfo->getHighlight(); if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) @@ -428,7 +428,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } else if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR) && modInfo->getColor().isValid() && ((role != ViewMarkingScrollBar::DEFAULT_ROLE) - || Settings::instance().colorSeparatorScrollbar())) { + || Settings::instance().colors().colorSeparatorScrollbar())) { return modInfo->getColor(); } else { return QVariant(); @@ -999,8 +999,8 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa ModInfo::Ptr modInfo = ModInfo::getByIndex(row); QDir modDir = QDir(modInfo->absolutePath()); - QDir allModsDir(Settings::instance().getModDirectory()); - QDir overwriteDir(Settings::instance().getOverwriteDirectory()); + QDir allModsDir(Settings::instance().paths().mods()); + QDir overwriteDir(Settings::instance().paths().overwrite()); QStringList sourceList; QStringList targetList; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 16190ca4..c6ef7bc7 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -572,7 +572,7 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( - Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); + Settings::instance().paths().cache() + "/nexus_cookies.dat"))); if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { // why is this necessary all of a sudden? diff --git a/src/organizercore.cpp b/src/organizercore.cpp index af0cf969..1a89641d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -146,7 +146,7 @@ static void startSteam(QWidget *widget) QStringList args; QString username; QString password; - if (Settings::instance().getSteamLogin(username, password)) { + if (Settings::instance().steam().login(username, password)) { args << "-login"; args << username; if (password != "") { @@ -275,12 +275,13 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_ArchivesInit(false) , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + m_DownloadManager.setOutputDirectory(m_Settings.paths().downloads()); - NexusInterface::instance(m_PluginContainer)->setCacheDirectory(m_Settings.getCacheDirectory()); + NexusInterface::instance(m_PluginContainer)->setCacheDirectory( + m_Settings.paths().cache()); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); + m_InstallationManager.setDownloadDirectory(m_Settings.paths().downloads()); connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this, SLOT(downloadSpeed(QString, int))); @@ -333,7 +334,7 @@ OrganizerCore::~OrganizerCore() void OrganizerCore::storeSettings() { if (m_CurrentProfile != nullptr) { - m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); } m_ExecutablesList.store(m_Settings); @@ -356,7 +357,7 @@ void OrganizerCore::storeSettings() QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), tr("An error occurred trying to write back MO settings to %1: %2") - .arg(m_Settings.getFilename(), reason)); + .arg(m_Settings.filename(), reason)); } } @@ -432,8 +433,9 @@ void OrganizerCore::updateExecutablesList() // TODO this has nothing to do with executables list move to an appropriate // function! - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } void OrganizerCore::setUserInterface(IUserInterface *userInterface, @@ -478,7 +480,7 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, if (userInterface != nullptr) { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (isOnline() && !m_Settings.offlineMode()) { + if (isOnline() && !m_Settings.network().offlineMode()) { m_Updater.testForUpdate(); } else { log::debug("user doesn't seem to be connected to the internet"); @@ -541,7 +543,7 @@ bool OrganizerCore::nexusApi(bool retry) return false; } else { QString apiKey; - if (m_Settings.getNexusApiKey(apiKey)) { + if (m_Settings.nexus().apiKey(apiKey)) { // credentials stored or user entered them manually log::debug("attempt to verify nexus api key"); accessManager->apiCheck(apiKey); @@ -608,7 +610,7 @@ void OrganizerCore::removeOrigin(const QString &name) void OrganizerCore::downloadSpeed(const QString &serverName, int bytesPerSecond) { - m_Settings.setDownloadSpeed(serverName, bytesPerSecond); + m_Settings.network().setDownloadSpeed(serverName, bytesPerSecond); } InstallationManager *OrganizerCore::installationManager() @@ -629,9 +631,9 @@ bool OrganizerCore::createDirectory(const QString &path) { } bool OrganizerCore::checkPathSymlinks() { - bool hasSymlink = (QFileInfo(m_Settings.getProfileDirectory()).isSymLink() || - QFileInfo(m_Settings.getModDirectory()).isSymLink() || - QFileInfo(m_Settings.getOverwriteDirectory()).isSymLink()); + bool hasSymlink = (QFileInfo(m_Settings.paths().profiles()).isSymLink() || + QFileInfo(m_Settings.paths().mods()).isSymLink() || + QFileInfo(m_Settings.paths().overwrite()).isSymLink()); if (hasSymlink) { QMessageBox::critical(nullptr, QObject::tr("Error"), QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) " @@ -643,17 +645,17 @@ bool OrganizerCore::checkPathSymlinks() { } bool OrganizerCore::bootstrap() { - return createDirectory(m_Settings.getProfileDirectory()) && - createDirectory(m_Settings.getModDirectory()) && - createDirectory(m_Settings.getDownloadDirectory()) && - createDirectory(m_Settings.getOverwriteDirectory()) && + return createDirectory(m_Settings.paths().profiles()) && + createDirectory(m_Settings.paths().mods()) && + createDirectory(m_Settings.paths().downloads()) && + createDirectory(m_Settings.paths().overwrite()) && createDirectory(QString::fromStdWString(crashDumpsPath())) && checkPathSymlinks() && cycleDiagnostics(); } void OrganizerCore::createDefaultProfile() { - QString profilesPath = settings().getProfileDirectory(); + QString profilesPath = settings().paths().profiles(); if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { Profile newProf("Default", managedGame(), false); @@ -674,18 +676,18 @@ void OrganizerCore::updateVFSParams( void OrganizerCore::setLogLevel(log::Levels level) { - m_Settings.setLogLevel(level); + m_Settings.diagnostics().setLogLevel(level); updateVFSParams( - m_Settings.logLevel(), - m_Settings.crashDumpsType(), + m_Settings.diagnostics().logLevel(), + m_Settings.diagnostics().crashDumpsType(), m_Settings.executablesBlacklist()); - log::getDefault().setLevel(m_Settings.logLevel()); + log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); } bool OrganizerCore::cycleDiagnostics() { - if (int maxDumps = settings().crashDumpsMax()) + if (int maxDumps = settings().diagnostics().crashDumpsMax()) removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); return true; } @@ -720,7 +722,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) return; } - QDir profileBaseDir(settings().getProfileDirectory()); + QDir profileBaseDir(settings().paths().profiles()); QString profileDir = profileBaseDir.absoluteFilePath(profileName); if (!QDir(profileDir).exists()) { @@ -744,7 +746,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->deactivateInvalidation(); } - m_Settings.setSelectedProfileName(m_CurrentProfile->name()); + m_Settings.game().setSelectedProfileName(m_CurrentProfile->name()); connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); connect(m_CurrentProfile, SIGNAL(modStatusChanged(QList)), this, SLOT(modStatusChanged(QList))); @@ -776,22 +778,22 @@ QString OrganizerCore::profilePath() const QString OrganizerCore::downloadsPath() const { - return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().downloads()); } QString OrganizerCore::overwritePath() const { - return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().overwrite()); } QString OrganizerCore::basePath() const { - return QDir::fromNativeSeparators(m_Settings.getBaseDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().base()); } QString OrganizerCore::modsPath() const { - return QDir::fromNativeSeparators(m_Settings.getModDirectory()); + return QDir::fromNativeSeparators(m_Settings.paths().mods()); } MOBase::VersionInfo OrganizerCore::appVersion() const @@ -821,10 +823,10 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) return nullptr; } - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); QString targetDirectory - = QDir::fromNativeSeparators(m_Settings.getModDirectory()) + = QDir::fromNativeSeparators(m_Settings.paths().mods()) .append("/") .append(name); @@ -912,7 +914,7 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, modName.update(initModName, GUESS_USER); } m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); @@ -977,7 +979,7 @@ void OrganizerCore::installDownload(int index) m_CurrentProfile->writeModlistNow(); bool hasIniTweaks = false; - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); @@ -1270,7 +1272,7 @@ bool OrganizerCore::previewFileWithAlternatives( else { // crude: we search for the next slash after the base mod directory to skip // everything up to the data-relative directory - int offset = settings().getModDirectory().size() + 1; + int offset = settings().paths().mods().size() + 1; offset = fileName.indexOf("/", offset); fileName = fileName.mid(offset + 1); } @@ -1412,7 +1414,7 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, LPDWORD exitCode) { HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries); - if (Settings::instance().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { + if (Settings::instance().interface().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; @@ -1461,7 +1463,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); } else { ::SetEnvironmentVariableW(L"SteamAPPId", - ToWString(m_Settings.getSteamAppID()).c_str()); + ToWString(m_Settings.steam().appID()).c_str()); } QWidget *window = qApp->activeWindow(); @@ -1477,7 +1479,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, || QFileInfo(managedGame()->gameDirectory().absoluteFilePath( "steam_api64.dll")) .exists()) - && (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { + && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { bool steamFound = true; bool steamAccess = true; @@ -1592,7 +1594,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } } - QString modsPath = settings().getModDirectory(); + QString modsPath = settings().paths().mods(); // Check if this a request with either an executable or a working directory under our mods folder // then will start the process in a virtualized "environment" with the appropriate paths fixed: @@ -1749,7 +1751,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { - if (!Settings::instance().lockGUI()) + if (!Settings::instance().interface().lockGUI()) return true; ILockedWaitingForProcess* uilock = nullptr; @@ -1960,8 +1962,10 @@ void OrganizerCore::refreshModList(bool saveChanges) if (saveChanges) { m_CurrentProfile->writeModlistNow(true); } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); m_CurrentProfile->refreshModStatus(); @@ -2130,7 +2134,7 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); @@ -2156,7 +2160,7 @@ void OrganizerCore::loggedInAction(QWidget* parent, std::function f) f(); } else { QString apiKey; - if (settings().getNexusApiKey(apiKey)) { + if (settings().nexus().apiKey(apiKey)) { doAfterLogin([f]{ f(); }); NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { @@ -2295,8 +2299,10 @@ void OrganizerCore::profileRefresh() { // have to refresh mods twice (again in refreshModList), otherwise the refresh // isn't complete. Not sure why - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, - m_PluginContainer, m_Settings.displayForeign(), managedGame()); + ModInfo::updateFromDisc( + m_Settings.paths().mods(), &m_DirectoryStructure, + m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); + m_CurrentProfile->refreshModStatus(); refreshModList(); @@ -2463,7 +2469,7 @@ void OrganizerCore::syncOverwrite() SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, qApp->activeWindow()); if (syncDialog.exec() == QDialog::Accepted) { - syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + syncDialog.apply(QDir::fromNativeSeparators(m_Settings.paths().mods())); modInfo->testValid(); refreshDirectoryStructure(); } @@ -2486,7 +2492,7 @@ std::vector OrganizerCore::activeProblems() const const auto& hookdll = oldMO1HookDll(); if (!hookdll.isEmpty()) { // This warning will now be shown every time the problems are checked, which is a bit - // of a "log spam". But since this is a sevre error which will most likely make the + // of a "log spam". But since this is a sever error which will most likely make the // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it // easier for the user to notice the warning. log::warn("hook.dll found in game folder: {}", hookdll); @@ -2562,7 +2568,7 @@ void OrganizerCore::savePluginList() } m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(), m_CurrentProfile->getDeleterFileName(), - m_Settings.hideUncheckedPlugins()); + m_Settings.game().hideUncheckedPlugins()); m_PluginList.saveLoadOrder(*m_DirectoryStructure); } @@ -2574,7 +2580,7 @@ void OrganizerCore::prepareStart() m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); - m_Settings.setupLoadMechanism(); + m_Settings.game().setupLoadMechanism(); storeSettings(); } @@ -2588,7 +2594,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, } IPluginGame *game = qApp->property("managed_game").value(); - Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName), + Profile profile(QDir(m_Settings.paths().profiles() + "/" + profileName), game); MappingType result; @@ -2634,7 +2640,7 @@ std::vector OrganizerCore::fileMapping(const QString &profileName, } result.insert(result.end(), { - QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()), + QDir::toNativeSeparators(m_Settings.paths().overwrite()), dataPath, true, customOverwrite.isEmpty() diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index ddfe492e..33423225 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -191,7 +191,7 @@ void PluginList::refresh(const QString &profileName continue; } - bool forceEnabled = Settings::instance().forceEnableCoreFiles() && + bool forceEnabled = Settings::instance().game().forceEnableCoreFiles() && primaryPlugins.contains(filename, Qt::CaseInsensitive); //(std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end()); diff --git a/src/profile.cpp b/src/profile.cpp index 7f4ebcaa..e76060b9 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -73,7 +73,7 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) , m_GamePlugin(gamePlugin) { - QString profilesDir = Settings::instance().getProfileDirectory(); + QString profilesDir = Settings::instance().paths().profiles(); QDir profileBase(profilesDir); QString fixedName = name; if (!fixDirectoryName(fixedName)) { @@ -299,7 +299,7 @@ void Profile::createTweakedIniFile() // static void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName) { - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); while (profileIter.hasNext()) { @@ -655,7 +655,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority) Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) { - QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; + QString profileDirectory = Settings::instance().paths().profiles() + "/" + name; reference.copyFilesTo(profileDirectory); return new Profile(QDir(profileDirectory), gamePlugin); } @@ -906,7 +906,7 @@ QString Profile::savePath() const void Profile::rename(const QString &newName) { - QDir profileDir(Settings::instance().getProfileDirectory()); + QDir profileDir(Settings::instance().paths().profiles()); profileDir.rename(name(), newName); m_Directory.setPath(profileDir.absoluteFilePath(newName)); } diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 2f1bd059..c91f48f4 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -57,7 +57,7 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c { ui->setupUi(this); - QDir profilesDir(Settings::instance().getProfileDirectory()); + QDir profilesDir(Settings::instance().paths().profiles()); profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); QDirIterator profileIter(profilesDir); @@ -203,7 +203,7 @@ void ProfilesDialog::on_removeProfileButton_clicked() if (confirmBox.exec() == QMessageBox::Yes) { QString profilePath; if (profileToDelete.get() == nullptr) { - profilePath = Settings::instance().getProfileDirectory() + profilePath = Settings::instance().paths().profiles() + "/" + ui->profilesList->currentItem()->text(); if (QMessageBox::question(this, tr("Profile broken"), tr("This profile you're about to delete seems to be broken or the path is invalid. " diff --git a/src/settings.cpp b/src/settings.cpp index 71288950..8b063efb 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -150,7 +150,7 @@ std::optional getOptional( template T get( const QSettings& settings, - const QString& section, const QString& key, T def={}) + const QString& section, const QString& key, T def) { if (auto v=getOptional(settings, section, key)) { return *v; @@ -453,22 +453,74 @@ void warnIfNotCheckable(const QAbstractButton* b) } +bool setWindowsCredential(const QString key, const QString data) +{ + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + bool result = false; + if (data.isEmpty()) { + result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); + if (!result) + if (GetLastError() == ERROR_NOT_FOUND) + result = true; + } else { + wchar_t* charData = new wchar_t[data.size()]; + data.toWCharArray(charData); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = keyData; + cred.CredentialBlob = (LPBYTE)charData; + cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + result = CredWriteW(&cred, 0); + delete[] charData; + } + delete[] keyData; + return result; +} + +QString getWindowsCredential(const QString key) +{ + QString result; + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + PCREDENTIALW creds; + if (CredReadW(keyData, 1, 0, &creds)) { + wchar_t *charData = (wchar_t *)creds->CredentialBlob; + result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); + CredFree(creds); + } else { + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + } + } + delete[] keyData; + return result; +} + + Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) : m_Settings(path, QSettings::IniFormat), - m_Geometry(m_Settings), m_Colors(m_Settings), m_Plugins(m_Settings) + m_Game(m_Settings), m_Geometry(m_Settings), m_Widgets(m_Settings), + m_Colors(m_Settings), m_Plugins(m_Settings), m_Paths(m_Settings), + m_Network(m_Settings), m_Nexus(*this, m_Settings), m_Steam(*this, m_Settings), + m_Interface(m_Settings), m_Diagnostics(m_Settings) { if (s_Instance != nullptr) { throw std::runtime_error("second instance of \"Settings\" created"); } else { s_Instance = this; } - - MOBase::QuestionBoxMemory::setCallbacks( - [this](auto&& w, auto&& f){ return getQuestionButton(w, f); }, - [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, - [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); } Settings::~Settings() @@ -488,7 +540,7 @@ Settings &Settings::instance() void Settings::processUpdates( const QVersionNumber& currentVersion, const QVersionNumber& lastVersion) { - if (getFirstStart()) { + if (firstStart()) { return; } @@ -523,1569 +575,1670 @@ void Settings::processUpdates( set(m_Settings, "General", "version", currentVersion.toString()); } -QString Settings::getFilename() const +QString Settings::filename() const { return m_Settings.fileName(); } -void Settings::registerAsNXMHandler(bool force) +bool Settings::usePrereleases() const { - const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; - const auto executable = QCoreApplication::applicationFilePath(); - - QString mode = force ? "forcereg" : "reg"; - QString parameters = mode + " " + m_GamePlugin->gameShortName(); - for (const QString& altGame : m_GamePlugin->validShortNames()) { - parameters += "," + altGame; - } - parameters += " \"" + executable + "\""; - - if (!shell::Execute(nxmPath, parameters)) { - QMessageBox::critical( - nullptr, tr("Failed"), tr("Failed to start the helper application")); - } + return get(m_Settings, "Settings", "use_prereleases", false); } -bool Settings::colorSeparatorScrollbar() const +void Settings::setUsePrereleases(bool b) { - return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); + set(m_Settings, "Settings", "use_prereleases", b); } -void Settings::setColorSeparatorScrollbar(bool b) +std::optional Settings::version() const { - set(m_Settings, "Settings", "colorSeparatorScrollbars", b); + if (auto v=getOptional(m_Settings, "General", "version")) { + return QVersionNumber::fromString(*v).normalized(); + } + + return {}; } -void Settings::managedGameChanged(IPluginGame const *gamePlugin) +bool Settings::firstStart() const { - m_GamePlugin = gamePlugin; + return get(m_Settings, "General", "first_start", true); } -bool Settings::obfuscate(const QString key, const QString data) +void Settings::setFirstStart(bool b) { - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - bool result = false; - if (data.isEmpty()) { - result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); - if (!result) - if (GetLastError() == ERROR_NOT_FOUND) - result = true; - } else { - wchar_t* charData = new wchar_t[data.size()]; - data.toWCharArray(charData); + set(m_Settings, "General", "first_start", b); +} - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = keyData; - cred.CredentialBlob = (LPBYTE)charData; - cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; +QString Settings::executablesBlacklist() const +{ + static const QString def = (QStringList() + << "Chrome.exe" + << "Firefox.exe" + << "TSVNCache.exe" + << "TGitCache.exe" + << "Steam.exe" + << "GameOverlayUI.exe" + << "Discord.exe" + << "GalaxyClient.exe" + << "Spotify.exe" + ).join(";"); - result = CredWriteW(&cred, 0); - delete[] charData; - } - delete[] keyData; - return result; + return get(m_Settings, "Settings", "executable_blacklist", def); } -QString Settings::deObfuscate(const QString key) +void Settings::setExecutablesBlacklist(const QString& s) { - QString result; - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - PCREDENTIALW creds; - if (CredReadW(keyData, 1, 0, &creds)) { - wchar_t *charData = (wchar_t *)creds->CredentialBlob; - result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); - CredFree(creds); - } else { - const auto e = GetLastError(); - if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); - } - } - delete[] keyData; - return result; + set(m_Settings, "Settings", "executable_blacklist", s); } -QColor Settings::getIdealTextColor(const QColor& rBackgroundColor) +void Settings::setMotdHash(uint hash) { - if (rBackgroundColor.alpha() == 0) - return QColor(Qt::black); - - const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha(); - int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); - return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white); + set(m_Settings, "General", "motd_hash", hash); } - -bool Settings::hideUncheckedPlugins() const +unsigned int Settings::motdHash() const { - return get(m_Settings, "Settings", "hide_unchecked_plugins", false); + return get(m_Settings, "General", "motd_hash", 0); } -void Settings::setHideUncheckedPlugins(bool b) +bool Settings::archiveParsing() const { - set(m_Settings, "Settings", "hide_unchecked_plugins", b); + return get(m_Settings, "Settings", "archive_parsing_experimental", false); } -bool Settings::forceEnableCoreFiles() const +void Settings::setArchiveParsing(bool b) { - return get(m_Settings, "Settings", "force_enable_core_files", true); + set(m_Settings, "Settings", "archive_parsing_experimental", b); } -void Settings::setForceEnableCoreFiles(bool b) +std::vector> Settings::executables() const { - set(m_Settings, "Settings", "force_enable_core_files", b); + ScopedReadArray sra(m_Settings, "customExecutables"); + std::vector> v; + + sra.for_each([&]{ + std::map map; + + for (auto&& key : sra.keys()) { + map[key] = m_Settings.value(key); + } + + v.push_back(map); + }); + + return v; } -bool Settings::lockGUI() const +void Settings::setExecutables(const std::vector>& v) { - return get(m_Settings, "Settings", "lock_gui", true); + removeSection(m_Settings, "customExecutables"); + + ScopedWriteArray swa(m_Settings, "customExecutables"); + + for (const auto& map : v) { + swa.next(); + + for (auto&& p : map) { + swa.set(p.first, p.second); + } + } } -void Settings::setLockGUI(bool b) +bool Settings::keepBackupOnInstall() const { - set(m_Settings, "Settings", "lock_gui", b); + return get(m_Settings, "General", "backup_install", false); } -bool Settings::automaticLoginEnabled() const +void Settings::setKeepBackupOnInstall(bool b) { - return get(m_Settings, "Settings", "nexus_login", false); + set(m_Settings, "General", "backup_install", b); } -QString Settings::getSteamAppID() const +GameSettings& Settings::game() { - return get(m_Settings, "Settings", "app_id", m_GamePlugin->steamAPPId()); + return m_Game; } -void Settings::setSteamAppID(const QString& id) +const GameSettings& Settings::game() const { - if (id.isEmpty()) { - remove(m_Settings, "Settings", "app_id"); - } else { - set(m_Settings, "Settings", "app_id", id); - } + return m_Game; } -bool Settings::usePrereleases() const +GeometrySettings& Settings::geometry() { - return get(m_Settings, "Settings", "use_prereleases", false); + return m_Geometry; } -void Settings::setUsePrereleases(bool b) +const GeometrySettings& Settings::geometry() const { - set(m_Settings, "Settings", "use_prereleases", b); + return m_Geometry; } -QString Settings::getConfigurablePath(const QString &key, - const QString &def, - bool resolve) const +WidgetSettings& Settings::widgets() { - QString result = QDir::fromNativeSeparators( - get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); - - if (resolve) { - result.replace("%BASE_DIR%", getBaseDirectory()); - } - - return result; + return m_Widgets; } -void Settings::setConfigurablePath(const QString &key, const QString& path) +const WidgetSettings& Settings::widgets() const { - if (path.isEmpty()) { - remove(m_Settings, "Settings", key); - } else { - set(m_Settings, "Settings", key, path); - } + return m_Widgets; } -QString Settings::getBaseDirectory() const +ColorSettings& Settings::colors() { - return QDir::fromNativeSeparators(get(m_Settings, - "Settings", "base_directory", qApp->property("dataPath").toString())); + return m_Colors; } -QString Settings::getDownloadDirectory(bool resolve) const +const ColorSettings& Settings::colors() const { - return getConfigurablePath( - "download_directory", - ToQString(AppConfig::downloadPath()), - resolve); + return m_Colors; } -QString Settings::getCacheDirectory(bool resolve) const +PluginSettings& Settings::plugins() { - return getConfigurablePath( - "cache_directory", - ToQString(AppConfig::cachePath()), - resolve); + return m_Plugins; } -QString Settings::getModDirectory(bool resolve) const +const PluginSettings& Settings::plugins() const { - return getConfigurablePath( - "mod_directory", - ToQString(AppConfig::modsPath()), - resolve); + return m_Plugins; } -QString Settings::getProfileDirectory(bool resolve) const +PathSettings& Settings::paths() { - return getConfigurablePath( - "profiles_directory", - ToQString(AppConfig::profilesPath()), - resolve); + return m_Paths; } -QString Settings::getOverwriteDirectory(bool resolve) const +const PathSettings& Settings::paths() const { - return getConfigurablePath( - "overwrite_directory", - ToQString(AppConfig::overwritePath()), - resolve); + return m_Paths; } -void Settings::setBaseDirectory(const QString& path) +NetworkSettings& Settings::network() { - if (path.isEmpty()) { - remove(m_Settings, "Settings", "base_directory"); - } else { - set(m_Settings, "Settings", "base_directory", path); - } + return m_Network; } -void Settings::setDownloadDirectory(const QString& path) +const NetworkSettings& Settings::network() const { - setConfigurablePath("download_directory", path); + return m_Network; } -void Settings::setModDirectory(const QString& path) +NexusSettings& Settings::nexus() { - setConfigurablePath("mod_directory", path); + return m_Nexus; } -void Settings::setCacheDirectory(const QString& path) +const NexusSettings& Settings::nexus() const { - setConfigurablePath("cache_directory", path); + return m_Nexus; } -void Settings::setProfileDirectory(const QString& path) +SteamSettings& Settings::steam() { - setConfigurablePath("profiles_directory", path); + return m_Steam; } -void Settings::setOverwriteDirectory(const QString& path) +const SteamSettings& Settings::steam() const { - setConfigurablePath("overwrite_directory", path); + return m_Steam; } -std::optional Settings::getManagedGameDirectory() const +InterfaceSettings& Settings::interface() { - if (auto v=getOptional(m_Settings, "General", "gamePath")) { - return QString::fromUtf8(*v); - } - - return {}; + return m_Interface; } -void Settings::setManagedGameDirectory(const QString& path) +const InterfaceSettings& Settings::interface() const { - set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); + return m_Interface; } -std::optional Settings::getManagedGameName() const +DiagnosticsSettings& Settings::diagnostics() { - return getOptional(m_Settings, "General", "gameName"); + return m_Diagnostics; } -void Settings::setManagedGameName(const QString& name) +const DiagnosticsSettings& Settings::diagnostics() const { - set(m_Settings, "General", "gameName", name); + return m_Diagnostics; } -std::optional Settings::getManagedGameEdition() const +QSettings::Status Settings::sync() const { - return getOptional(m_Settings, "General", "game_edition"); + m_Settings.sync(); + return m_Settings.status(); } -void Settings::setManagedGameEdition(const QString& name) +void Settings::dump() const { - set(m_Settings, "General", "game_edition", name); -} + static const QStringList ignore({ + "username", "password", "nexus_api_key" + }); -std::optional Settings::getSelectedProfileName() const -{ - if (auto v=getOptional(m_Settings, "General", "selected_profile")) { - return QString::fromUtf8(*v); + log::debug("settings:"); + + { + ScopedGroup sg(m_Settings, "Settings"); + + for (auto k : m_Settings.allKeys()) { + if (ignore.contains(k, Qt::CaseInsensitive)) { + continue; + } + + log::debug(" . {}={}", k, m_Settings.value(k).toString()); + } } - return {}; + m_Network.dump(); } -void Settings::setSelectedProfileName(const QString& name) +void Settings::managedGameChanged(IPluginGame const *gamePlugin) { - set(m_Settings, "General", "selected_profile", name.toUtf8()); + m_Game.setPlugin(gamePlugin); } -std::optional Settings::getStyleName() const + +GameSettings::GameSettings(QSettings& settings) + : m_Settings(settings), m_GamePlugin(nullptr) { - return getOptional(m_Settings, "Settings", "style"); } -void Settings::setStyleName(const QString& name) +const MOBase::IPluginGame* GameSettings::plugin() { - set(m_Settings, "Settings", "style", name); + return m_GamePlugin; } -bool Settings::getUseProxy() const +void GameSettings::setPlugin(const MOBase::IPluginGame* gamePlugin) { - return get(m_Settings, "Settings", "use_proxy", false); + m_GamePlugin = gamePlugin; } -void Settings::setUseProxy(bool b) +bool GameSettings::forceEnableCoreFiles() const { - set(m_Settings, "Settings", "use_proxy", b); + return get(m_Settings, "Settings", "force_enable_core_files", true); } -std::optional Settings::getVersion() const +void GameSettings::setForceEnableCoreFiles(bool b) { - if (auto v=getOptional(m_Settings, "General", "version")) { - return QVersionNumber::fromString(*v).normalized(); - } - - return {}; + set(m_Settings, "Settings", "force_enable_core_files", b); } -bool Settings::getFirstStart() const +std::optional GameSettings::directory() const { - return get(m_Settings, "General", "first_start", true); + if (auto v=getOptional(m_Settings, "General", "gamePath")) { + return QString::fromUtf8(*v); + } + + return {}; } -void Settings::setFirstStart(bool b) +void GameSettings::setDirectory(const QString& path) { - set(m_Settings, "General", "first_start", b); + set(m_Settings, "General", "gamePath", QDir::toNativeSeparators(path).toUtf8()); } -std::optional Settings::getPreviousSeparatorColor() const +std::optional GameSettings::name() const { - const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); - if (c && c->isValid()) { - return c; - } - - return {}; + return getOptional(m_Settings, "General", "gameName"); } -void Settings::setPreviousSeparatorColor(const QColor& c) const +void GameSettings::setName(const QString& name) { - set(m_Settings, "General", "previousSeparatorColor", c); + set(m_Settings, "General", "gameName", name); } -void Settings::removePreviousSeparatorColor() +std::optional GameSettings::edition() const { - remove(m_Settings, "General", "previousSeparatorColor"); + return getOptional(m_Settings, "General", "game_edition"); } -bool Settings::getNexusApiKey(QString &apiKey) const +void GameSettings::setEdition(const QString& name) { - QString tempKey = deObfuscate("APIKEY"); - if (tempKey.isEmpty()) - return false; - - apiKey = tempKey; - return true; + set(m_Settings, "General", "game_edition", name); } -bool Settings::setNexusApiKey(const QString& apiKey) +std::optional GameSettings::selectedProfileName() const { - if (!obfuscate("APIKEY", apiKey)) { - const auto e = GetLastError(); - log::error("Storing API key failed: {}", formatSystemMessage(e)); - return false; + if (auto v=getOptional(m_Settings, "General", "selected_profile")) { + return QString::fromUtf8(*v); } - return true; + return {}; } -bool Settings::clearNexusApiKey() +void GameSettings::setSelectedProfileName(const QString& name) { - return setNexusApiKey(""); + set(m_Settings, "General", "selected_profile", name.toUtf8()); } -bool Settings::hasNexusApiKey() const +LoadMechanism::EMechanism GameSettings::loadMechanismType() const { - return !deObfuscate("APIKEY").isEmpty(); -} + const auto def = LoadMechanism::LOAD_MODORGANIZER; -bool Settings::getSteamLogin(QString &username, QString &password) const -{ - username = get(m_Settings, "Settings", "steam_username", ""); - password = deObfuscate("steam_password"); + const auto i = get(m_Settings, + "Settings", "load_mechanism", def); - return !username.isEmpty() && !password.isEmpty(); -} + switch (i) + { + // ok + case LoadMechanism::LOAD_MODORGANIZER: // fall-through + { + break; + } -bool Settings::compactDownloads() const -{ - return get(m_Settings, "Settings", "compact_downloads", false); -} + default: + { + log::error( + "invalid load mechanism {}, reverting to {}", + static_cast(i), toString(def)); -void Settings::setCompactDownloads(bool b) -{ - set(m_Settings, "Settings", "compact_downloads", b); -} + set(m_Settings, "Settings", "load_mechanism", def); -bool Settings::metaDownloads() const -{ - return get(m_Settings, "Settings", "meta_downloads", false); -} + return def; + } + } -void Settings::setMetaDownloads(bool b) -{ - set(m_Settings, "Settings", "meta_downloads", b); + return i; } -bool Settings::offlineMode() const +void GameSettings::setLoadMechanism(LoadMechanism::EMechanism m) { - return get(m_Settings, "Settings/offline_mode", false); + set(m_Settings, "Settings", "load_mechanism", m); } -void Settings::setOfflineMode(bool b) +const LoadMechanism& GameSettings::loadMechanism() const { - set(m_Settings, "Settings", "offline_mode", b); + return m_LoadMechanism; } -log::Levels Settings::logLevel() const +void GameSettings::setupLoadMechanism() { - return get(m_Settings, "Settings", "log_level", log::Levels::Info); + m_LoadMechanism.activate(loadMechanismType()); } -void Settings::setLogLevel(log::Levels level) +bool GameSettings::hideUncheckedPlugins() const { - set(m_Settings, "Settings", "log_level", level); + return get(m_Settings, "Settings", "hide_unchecked_plugins", false); } -CrashDumpsType Settings::crashDumpsType() const +void GameSettings::setHideUncheckedPlugins(bool b) { - return get(m_Settings, - "Settings", "crash_dumps_type", CrashDumpsType::Mini); + set(m_Settings, "Settings", "hide_unchecked_plugins", b); } -void Settings::setCrashDumpsType(CrashDumpsType type) -{ - set(m_Settings, "Settings", "crash_dumps_type", type); -} -int Settings::crashDumpsMax() const +GeometrySettings::GeometrySettings(QSettings& s) + : m_Settings(s), m_Reset(false) { - return get(m_Settings, "Settings", "crash_dumps_max", 5); } -void Settings::setCrashDumpsMax(int n) +void GeometrySettings::requestReset() { - set(m_Settings, "Settings", "crash_dumps_max", n); + m_Reset = true; } -QString Settings::executablesBlacklist() const +void GeometrySettings::resetIfNeeded() { - static const QString def = (QStringList() - << "Chrome.exe" - << "Firefox.exe" - << "TSVNCache.exe" - << "TGitCache.exe" - << "Steam.exe" - << "GameOverlayUI.exe" - << "Discord.exe" - << "GalaxyClient.exe" - << "Spotify.exe" - ).join(";"); + if (!m_Reset) { + return; + } - return get(m_Settings, "Settings", "executable_blacklist", def); + removeSection(m_Settings, "Geometry"); } -void Settings::setExecutablesBlacklist(const QString& s) +void GeometrySettings::saveGeometry(const QWidget* w) { - set(m_Settings, "Settings", "executable_blacklist", s); + set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); } -void Settings::setSteamLogin(QString username, QString password) +bool GeometrySettings::restoreGeometry(QWidget* w) const { - if (username == "") { - remove(m_Settings, "Settings", "steam_username"); - password = ""; - } else { - set(m_Settings, "Settings", "steam_username", username); + if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { + w->restoreGeometry(*v); + return true; } - if (!obfuscate("steam_password", password)) { - const auto e = GetLastError(); - log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); - } + return false; } -LoadMechanism::EMechanism Settings::getLoadMechanism() const +void GeometrySettings::saveState(const QMainWindow* w) { - const auto def = LoadMechanism::LOAD_MODORGANIZER; - - const auto i = get(m_Settings, - "Settings", "load_mechanism", def); - - switch (i) - { - // ok - case LoadMechanism::LOAD_MODORGANIZER: // fall-through - { - break; - } - - default: - { - log::error( - "invalid load mechanism {}, reverting to {}", - static_cast(i), toString(def)); - - set(m_Settings, "Settings", "load_mechanism", def); - - return def; - } - } - - return i; + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setLoadMechanism(LoadMechanism::EMechanism m) +bool GeometrySettings::restoreState(QMainWindow* w) const { - set(m_Settings, "Settings", "load_mechanism", m); -} + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; + } -void Settings::setupLoadMechanism() -{ - m_LoadMechanism.activate(getLoadMechanism()); + return false; } -bool Settings::endorsementIntegration() const +void GeometrySettings::saveState(const QHeaderView* w) { - return get(m_Settings, "Settings", "endorsement_integration", true); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setEndorsementIntegration(bool b) const +bool GeometrySettings::restoreState(QHeaderView* w) const { - set(m_Settings, "Settings", "endorsement_integration", b); + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; + } + + return false; } -EndorsementState Settings::endorsementState() const +void GeometrySettings::saveState(const QSplitter* w) { - return endorsementStateFromString( - get(m_Settings, "General", "endorse_state", "")); + set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); } -void Settings::setEndorsementState(EndorsementState s) +bool GeometrySettings::restoreState(QSplitter* w) const { - const auto v = toString(s); - - if (v.isEmpty()) { - remove(m_Settings, "General", "endorse_state"); - } else { - set(m_Settings, "General", "endorse_state", v); + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { + w->restoreState(*v); + return true; } -} -bool Settings::hideAPICounter() const -{ - return get(m_Settings, "Settings", "hide_api_counter", false); + return false; } -void Settings::setHideAPICounter(bool b) +void GeometrySettings::saveState(const ExpanderWidget* expander) { - set(m_Settings, "Settings", "hide_api_counter", b); + set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); } -bool Settings::displayForeign() const +bool GeometrySettings::restoreState(ExpanderWidget* expander) const { - return get(m_Settings, "Settings", "display_foreign", true); -} + if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { + expander->restoreState(*v); + return true; + } -void Settings::setDisplayForeign(bool b) -{ - set(m_Settings, "Settings", "display_foreign", b); + return false; } -void Settings::setMotDHash(uint hash) +void GeometrySettings::saveVisibility(const QWidget* w) { - set(m_Settings, "General", "motd_hash", hash); + set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); } -unsigned int Settings::getMotDHash() const +bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const { - return get(m_Settings, "motd_hash", 0); -} + if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { + w->setVisible(*v); + return true; + } -bool Settings::archiveParsing() const -{ - return get(m_Settings, "Settings", "archive_parsing_experimental", false); + return false; } -void Settings::setArchiveParsing(bool b) +void GeometrySettings::restoreToolbars(QMainWindow* w) const { - set(m_Settings, "Settings", "archive_parsing_experimental", b); -} + // all toolbars have the same size and button style settings + const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); + const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); -QString Settings::language() + for (auto* tb : w->findChildren()) { + if (size) { + tb->setIconSize(*size); + } + + if (style) { + tb->setToolButtonStyle(static_cast(*style)); + } + + restoreVisibility(tb); + } +} + +void GeometrySettings::saveToolbars(const QMainWindow* w) { - QString result = get(m_Settings, "Settings", "language", ""); + const auto tbs = w->findChildren(); - if (result.isEmpty()) { - QStringList languagePreferences = QLocale::system().uiLanguages(); + // save visibility for all + for (auto* tb : tbs) { + saveVisibility(tb); + } - if (languagePreferences.length() > 0) { - // the users most favoritest language - result = languagePreferences.at(0); - } else { - // fallback system locale - result = QLocale::system().name(); + // all toolbars have the same size and button style settings, just save the + // first one + if (!tbs.isEmpty()) { + const auto* tb = tbs[0]; + + set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); + set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); + } +} + +QStringList GeometrySettings::modInfoTabOrder() const +{ + QStringList v; + + if (m_Settings.contains("mod_info_tabs")) { + // old byte array from 2.2.0 + QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + + int count = 0; + stream >> count; + + for (int i=0; i> s; + v.push_back(s); + } + } else { + // string list since 2.2.1 + QString string = m_Settings.value("mod_info_tab_order").toString(); + QTextStream stream(&string); + + while (!stream.atEnd()) { + QString s; + stream >> s; + v.push_back(s); } } - return result; + return v; } -void Settings::setLanguage(const QString& name) +void GeometrySettings::setModInfoTabOrder(const QString& names) { - set(m_Settings, "Settings", "language", name); + set(m_Settings, "Geometry", "mod_info_tab_order", names); } -void Settings::setDownloadSpeed(const QString& name, int bytesPerSecond) +void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { - auto servers = getServers(); + const auto monitor = getOptional( + m_Settings, "Geometry", "MainWindow_monitor"); - for (auto& server : servers) { - if (server.name() == name) { - server.addDownload(bytesPerSecond); - updateServers(servers); - return; + QPoint center; + + if (monitor && QGuiApplication::screens().size() > *monitor) { + center = QGuiApplication::screens().at(*monitor)->geometry().center(); + } else { + center = QGuiApplication::primaryScreen()->geometry().center(); + } + + w->move(center - w->rect().center()); +} + +void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) +{ + if (auto* handle=w->windowHandle()) { + if (auto* screen = handle->screen()) { + const int screenId = QGuiApplication::screens().indexOf(screen); + set(m_Settings, "Geometry", "MainWindow_monitor", screenId); } } +} - log::error( - "server '{}' not found while trying to add a download with bps {}", - name, bytesPerSecond); +Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) +{ + // docks in these areas are horizontal + const auto horizontalAreas = + Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + + if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { + return Qt::Horizontal; + } else { + return Qt::Vertical; + } } -ServerList Settings::getServers() const +void GeometrySettings::saveDocks(const QMainWindow* mw) { - // servers used to be a map of byte arrays until 2.2.1, it's now an array of - // individual values instead + // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock + // sizes are not restored when the main window is maximized; it is used in + // MainWindow::readSettings() and MainWindow::storeSettings() + // + // there's also https://stackoverflow.com/questions/44005852, which has what + // seems to be a popular fix, but it breaks the restored size of the window + // by setting it to the desktop's resolution, so that doesn't work + // + // the only fix I could find is to remember the sizes of the docks and manually + // setting them back; saving is straightforward, but restoring is messy + // + // this also depends on the window being visible before the timer in restore() + // is fired and the timer must be processed by application.exec(); therefore, + // the splash screen _must_ be closed before readSettings() is called, because + // it has its own event loop, which seems to interfere with this + // + // all of this should become unnecessary when QTBUG-46620 is fixed // - // so post 2.2.1, only one key is returned: "size", the size of the arrays; - // in 2.2.1, one key per server is returned - { - const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - if (!keys.empty() && keys[0] != "size") { - // old format - return getServersFromOldMap(); + // saves the size of each dock + for (const auto* dock : mw->findChildren()) { + int size = 0; + + // save the width for horizontal docks, or the height for vertical + if (dockOrientation(mw, dock) == Qt::Horizontal) { + size = dock->size().width(); + } else { + size = dock->size().height(); } + + set(m_Settings, "Geometry", dockSettingName(dock), size); } +} +void GeometrySettings::restoreDocks(QMainWindow* mw) const +{ + struct DockInfo + { + QDockWidget* d; + int size = 0; + Qt::Orientation ori; + }; - // post 2.2.1 format, array of values + std::vector dockInfos; - ServerList list; + // for each dock + for (auto* dock : mw->findChildren()) { + if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { + // remember this dock, its size and orientation + dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + } + } - { - ScopedReadArray sra(m_Settings, "Servers"); + // the main window must have had time to process the settings from + // readSettings() or it seems to override whatever is set here + // + // some people said a single processEvents() call is enough, but it doesn't + // look like it + QTimer::singleShot(5, [=] { + for (const auto& info : dockInfos) { + mw->resizeDocks({info.d}, {info.size}, info.ori); + } + }); +} - sra.for_each([&] { - ServerInfo::SpeedList lastDownloads; - const auto lastDownloadsString = sra.get("lastDownloads", ""); +WidgetSettings::WidgetSettings(QSettings& s) + : m_Settings(s) +{ + MOBase::QuestionBoxMemory::setCallbacks( + [this](auto&& w, auto&& f){ return questionButton(w, f); }, + [this](auto&& w, auto&& b){ setQuestionWindowButton(w, b); }, + [this](auto&& w, auto&& f, auto&& b){ setQuestionFileButton(w, f, b); }); +} - for (const auto& s : lastDownloadsString.split(" ")) { - const auto bytesPerSecond = s.toInt(); - if (bytesPerSecond > 0) { - lastDownloads.push_back(bytesPerSecond); - } - } +std::optional WidgetSettings::index(const QComboBox* cb) const +{ + return getOptional(m_Settings, "Widgets", indexSettingName(cb)); +} - ServerInfo server( - sra.get("name", ""), - sra.get("premium", false), - QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), - sra.get("preferred", 0), - lastDownloads); +void WidgetSettings::saveIndex(const QComboBox* cb) +{ + set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); +} - list.add(std::move(server)); - }); +void WidgetSettings::restoreIndex(QComboBox* cb, std::optional def) const +{ + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { + cb->setCurrentIndex(*v); } - - return list; } -ServerList Settings::getServersFromOldMap() const +std::optional WidgetSettings::index(const QTabWidget* w) const { - // for 2.2.1 and before + return getOptional(m_Settings, "Widgets", indexSettingName(w)); +} - ServerList list; - const ScopedGroup sg(m_Settings, "Servers"); +void WidgetSettings::saveIndex(const QTabWidget* w) +{ + set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); +} - sg.for_each([&](auto&& serverKey) { - QVariantMap data = sg.get(serverKey); +void WidgetSettings::restoreIndex(QTabWidget* w, std::optional def) const +{ + if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { + w->setCurrentIndex(*v); + } +} - ServerInfo server( - serverKey, - data["premium"].toBool(), - data["lastSeen"].toDate(), - data["preferred"].toInt(), - {}); +std::optional WidgetSettings::checked(const QAbstractButton* w) const +{ + warnIfNotCheckable(w); + return getOptional(m_Settings, "Widgets", checkedSettingName(w)); +} - // ignoring download count and speed, it's now a list of values instead of - // a total +void WidgetSettings::saveChecked(const QAbstractButton* w) +{ + warnIfNotCheckable(w); + set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); +} - list.add(std::move(server)); - }); +void WidgetSettings::restoreChecked(QAbstractButton* w, std::optional def) const +{ + warnIfNotCheckable(w); - return list; + if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { + w->setChecked(*v); + } } -void Settings::updateServers(ServerList servers) +QuestionBoxMemory::Button WidgetSettings::questionButton( + const QString& windowName, const QString& filename) const { - // clean up unavailable servers - servers.cleanup(); - - removeSection(m_Settings, "Servers"); + const QString sectionName("DialogChoices"); - { - ScopedWriteArray swa(m_Settings, "Servers"); + if (!filename.isEmpty()) { + const auto fileSetting = windowName + "/" + filename; + if (auto v=getOptional(m_Settings, sectionName, filename)) { + return static_cast(*v); + } + } - for (const auto& server : servers) { - swa.next(); + if (auto v=getOptional(m_Settings, sectionName, windowName)) { + return static_cast(*v); + } - swa.set("name", server.name()); - swa.set("premium", server.isPremium()); - swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); - swa.set("preferred", server.preferred()); + return QuestionBoxMemory::NoButton; +} - QString lastDownloads; - for (const auto& speed : server.lastDownloads()) { - if (speed > 0) { - lastDownloads += QString("%1 ").arg(speed); - } - } +void WidgetSettings::setQuestionWindowButton( + const QString& windowName, QuestionBoxMemory::Button button) +{ + const QString sectionName("DialogChoices"); - swa.set("lastDownloads", lastDownloads.trimmed()); - } + if (button == QuestionBoxMemory::NoButton) { + remove(m_Settings, sectionName, windowName); + } else { + set(m_Settings, sectionName, windowName, button); } } -std::map Settings::getRecentDirectories() const +void WidgetSettings::setQuestionFileButton( + const QString& windowName, const QString& filename, + QuestionBoxMemory::Button button) { - std::map map; + const QString sectionName("DialogChoices"); + const QString settingName(windowName + "/" + filename); - ScopedReadArray sra(m_Settings, "RecentDirectories"); + if (button == QuestionBoxMemory::NoButton) { + remove(m_Settings, sectionName, settingName); + } else { + set(m_Settings, sectionName, settingName, button); + } +} - sra.for_each([&] { - const QVariant name = sra.get("name"); - const QVariant dir = sra.get("directory"); +void WidgetSettings::resetQuestionButtons() +{ + removeSection(m_Settings, "DialogChoices"); +} - if (name.isValid() && dir.isValid()) { - map.emplace(name.toString(), dir.toString()); - } - }); - return map; +ColorSettings::ColorSettings(QSettings& s) + : m_Settings(s) +{ } -void Settings::setRecentDirectories(const std::map& map) +QColor ColorSettings::modlistOverwrittenLoose() const { - removeSection(m_Settings, "RecentDirectories"); + return get( + m_Settings, "Settings", "overwrittenLooseFilesColor", + QColor(0, 255, 0, 64)); +} - ScopedWriteArray swa(m_Settings, "recentDirectories"); +void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +{ + set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); +} - for (auto&& p : map) { - swa.next(); +QColor ColorSettings::modlistOverwritingLoose() const +{ + return get( + m_Settings, "Settings", "overwritingLooseFilesColor", + QColor(255, 0, 0, 64)); +} - swa.set("name", p.first); - swa.set("directory", p.second); - } +void ColorSettings::setModlistOverwritingLoose(const QColor& c) +{ + set(m_Settings, "Settings", "overwritingLooseFilesColor", c); } -std::vector> Settings::getExecutables() const +QColor ColorSettings::modlistOverwrittenArchive() const { - ScopedReadArray sra(m_Settings, "customExecutables"); - std::vector> v; + return get( + m_Settings, "Settings", "overwrittenArchiveFilesColor", + QColor(0, 255, 255, 64)); +} - sra.for_each([&]{ - std::map map; +void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +{ + set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); +} - for (auto&& key : sra.keys()) { - map[key] = m_Settings.value(key); - } +QColor ColorSettings::modlistOverwritingArchive() const +{ + return get( + m_Settings, "Settings", "overwritingArchiveFilesColor", + QColor(255, 0, 255, 64)); +} - v.push_back(map); - }); +void ColorSettings::setModlistOverwritingArchive(const QColor& c) +{ + set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); +} - return v; +QColor ColorSettings::modlistContainsPlugin() const +{ + return get( + m_Settings, "Settings", "containsPluginColor", + QColor(0, 0, 255, 64)); } -void Settings::setExecutables(const std::vector>& v) +void ColorSettings::setModlistContainsPlugin(const QColor& c) { - removeSection(m_Settings, "customExecutables"); + set(m_Settings, "Settings", "containsPluginColor", c); +} - ScopedWriteArray swa(m_Settings, "customExecutables"); +QColor ColorSettings::pluginListContained() const +{ + return get( + m_Settings, "Settings", "containedColor", + QColor(0, 0, 255, 64)); +} - for (const auto& map : v) { - swa.next(); +void ColorSettings::setPluginListContained(const QColor& c) +{ + set(m_Settings, "Settings", "containedColor", c); +} - for (auto&& p : map) { - swa.set(p.first, p.second); - } +std::optional ColorSettings::previousSeparatorColor() const +{ + const auto c = getOptional(m_Settings, "General", "previousSeparatorColor"); + if (c && c->isValid()) { + return c; } + + return {}; } -bool Settings::isTutorialCompleted(const QString& windowName) const +void ColorSettings::setPreviousSeparatorColor(const QColor& c) const { - return get(m_Settings, "CompletedWindowTutorials", windowName, false); + set(m_Settings, "General", "previousSeparatorColor", c); } -void Settings::setTutorialCompleted(const QString& windowName, bool b) +void ColorSettings::removePreviousSeparatorColor() { - set(m_Settings, "CompletedWindowTutorials", windowName, b); + remove(m_Settings, "General", "previousSeparatorColor"); } -bool Settings::keepBackupOnInstall() const +bool ColorSettings::colorSeparatorScrollbar() const { - return get(m_Settings, "backup_install", false); + return get(m_Settings, "Settings", "colorSeparatorScrollbars", true); } -void Settings::setKeepBackupOnInstall(bool b) +void ColorSettings::setColorSeparatorScrollbar(bool b) { - set(m_Settings, "General", "backup_install", b); + set(m_Settings, "Settings", "colorSeparatorScrollbars", b); } -QuestionBoxMemory::Button Settings::getQuestionButton( - const QString& windowName, const QString& filename) const +QColor ColorSettings::idealTextColor(const QColor& rBackgroundColor) { - const QString sectionName("DialogChoices"); + if (rBackgroundColor.alpha() == 0) + return QColor(Qt::black); - if (!filename.isEmpty()) { - const auto fileSetting = windowName + "/" + filename; - if (auto v=getOptional(m_Settings, sectionName, filename)) { - return static_cast(*v); + const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha(); + int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); + return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white); +} + + + +PluginSettings::PluginSettings(QSettings& settings) + : m_Settings(settings) +{ +} + +void PluginSettings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); + + m_PluginBlacklist.clear(); + + ScopedReadArray sra(m_Settings, "pluginBlacklist"); + sra.for_each([&]{ + m_PluginBlacklist.insert(sra.get("name")); + }); +} + +void PluginSettings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + m_PluginSettings.insert(plugin->name(), QVariantMap()); + m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + + for (const PluginSetting &setting : plugin->settings()) { + const QString settingName = plugin->name() + "/" + setting.key; + + QVariant temp = get( + m_Settings, "Plugins", settingName, setting.defaultValue); + + if (!temp.convert(setting.defaultValue.type())) { + log::warn( + "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", + temp.toString(), setting.key, plugin->name()); + + temp = setting.defaultValue; } - } - if (auto v=getOptional(m_Settings, sectionName, windowName)) { - return static_cast(*v); + m_PluginSettings[plugin->name()][setting.key] = temp; + + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") + .arg(setting.description) + .arg(setting.defaultValue.toString()); } +} - return QuestionBoxMemory::NoButton; +bool PluginSettings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); } -void Settings::setQuestionWindowButton( - const QString& windowName, QuestionBoxMemory::Button button) +QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const { - const QString sectionName("DialogChoices/"); + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + return QVariant(); + } - if (button == QuestionBoxMemory::NoButton) { - remove(m_Settings, sectionName, windowName); - } else { - set(m_Settings, sectionName, windowName, button); + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + return QVariant(); } + + return *iterSetting; } -void Settings::setQuestionFileButton( - const QString& windowName, const QString& filename, - QuestionBoxMemory::Button button) +void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - const QString sectionName("DialogChoices"); - const QString settingName(windowName + "/" + filename); + auto iterPlugin = m_PluginSettings.find(pluginName); - if (button == QuestionBoxMemory::NoButton) { - remove(m_Settings, sectionName, settingName); - } else { - set(m_Settings, sectionName, settingName, button); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); } + + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + set(m_Settings, "Plugins", pluginName + "/" + key, value); } -void Settings::resetQuestionButtons() +QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const { - removeSection(m_Settings, "DialogChoices"); + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + + return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); } -std::optional Settings::getIndex(const QComboBox* cb) const +void PluginSettings::setPluginPersistent( + const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - return getOptional(m_Settings, "Widgets", indexSettingName(cb)); + if (!m_PluginSettings.contains(pluginName)) { + throw MyException( + QObject::tr("attempt to store setting for unknown plugin \"%1\"") + .arg(pluginName)); + } + + set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + + if (sync) { + m_Settings.sync(); + } } -void Settings::saveIndex(const QComboBox* cb) +void PluginSettings::addBlacklistPlugin(const QString &fileName) { - set(m_Settings, "Widgets", indexSettingName(cb), cb->currentIndex()); + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); } -void Settings::restoreIndex(QComboBox* cb, std::optional def) const +void PluginSettings::writePluginBlacklist() { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(cb), def)) { - cb->setCurrentIndex(*v); + removeSection(m_Settings, "PluginBlacklist"); + + ScopedWriteArray swa(m_Settings, "PluginBlacklist"); + + for (const QString &plugin : m_PluginBlacklist) { + swa.next(); + swa.set("name", plugin); } } -std::optional Settings::getIndex(const QTabWidget* w) const +QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const { - return getOptional(m_Settings, "Widgets", indexSettingName(w)); + return m_PluginSettings[pluginName]; } -void Settings::saveIndex(const QTabWidget* w) +void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) { - set(m_Settings, "Widgets", indexSettingName(w), w->currentIndex()); + m_PluginSettings[pluginName] = map; } -void Settings::restoreIndex(QTabWidget* w, std::optional def) const +QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const { - if (auto v=getOptional(m_Settings, "Widgets", indexSettingName(w), def)) { - w->setCurrentIndex(*v); - } + return m_PluginDescriptions[pluginName]; } -std::optional Settings::getChecked(const QAbstractButton* w) const +void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) { - warnIfNotCheckable(w); - return getOptional(m_Settings, "Widgets", checkedSettingName(w)); + m_PluginDescriptions[pluginName] = map; } -void Settings::saveChecked(const QAbstractButton* w) +const QSet& PluginSettings::pluginBlacklist() const { - warnIfNotCheckable(w); - set(m_Settings, "Widgets", checkedSettingName(w), w->isChecked()); + return m_PluginBlacklist; } -void Settings::restoreChecked(QAbstractButton* w, std::optional def) const +void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) { - warnIfNotCheckable(w); + m_PluginBlacklist.clear(); - if (auto v=getOptional(m_Settings, "Widgets", checkedSettingName(w), def)) { - w->setChecked(*v); + for (const auto& name : pluginNames) { + m_PluginBlacklist.insert(name); } } -GeometrySettings& Settings::geometry() +void PluginSettings::save() { - return m_Geometry; -} + for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { + const auto key = iterPlugins.key() + "/" + iterSettings.key(); + set(m_Settings, "Plugins", key, iterSettings.value()); + } + } -const GeometrySettings& Settings::geometry() const -{ - return m_Geometry; + writePluginBlacklist(); } -ColorSettings& Settings::colors() -{ - return m_Colors; -} -const ColorSettings& Settings::colors() const +PathSettings::PathSettings(QSettings& settings) + : m_Settings(settings) { - return m_Colors; } -PluginSettings& Settings::plugins() +std::map PathSettings::recent() const { - return m_Plugins; -} + std::map map; -const PluginSettings& Settings::plugins() const -{ - return m_Plugins; -} + ScopedReadArray sra(m_Settings, "RecentDirectories"); -QSettings::Status Settings::sync() const -{ - m_Settings.sync(); - return m_Settings.status(); -} + sra.for_each([&] { + const QVariant name = sra.get("name"); + const QVariant dir = sra.get("directory"); -void Settings::dump() const -{ - static const QStringList ignore({ - "username", "password", "nexus_api_key" + if (name.isValid() && dir.isValid()) { + map.emplace(name.toString(), dir.toString()); + } }); - log::debug("settings:"); + return map; +} - { - ScopedGroup sg(m_Settings, "Settings"); +void PathSettings::setRecent(const std::map& map) +{ + removeSection(m_Settings, "RecentDirectories"); - for (auto k : m_Settings.allKeys()) { - if (ignore.contains(k, Qt::CaseInsensitive)) { - continue; - } + ScopedWriteArray swa(m_Settings, "recentDirectories"); - log::debug(" . {}={}", k, m_Settings.value(k).toString()); - } - } + for (auto&& p : map) { + swa.next(); - log::debug("servers:"); + swa.set("name", p.first); + swa.set("directory", p.second); + } +} - for (const auto& server : getServers()) { - QString lastDownloads; - for (auto speed : server.lastDownloads()) { - lastDownloads += QString("%1 ").arg(speed); - } +QString PathSettings::getConfigurablePath(const QString &key, + const QString &def, + bool resolve) const +{ + QString result = QDir::fromNativeSeparators( + get(m_Settings, "Settings", key, QString("%BASE_DIR%/") + def)); - log::debug( - " . {} premium={} lastSeen={} preferred={} lastDownloads={}", - server.name(), - server.isPremium() ? "yes" : "no", - server.lastSeen().toString(Qt::ISODate), - server.preferred(), - lastDownloads.trimmed()); + if (resolve) { + result.replace("%BASE_DIR%", base()); } -} + return result; +} -GeometrySettings::GeometrySettings(QSettings& s) - : m_Settings(s), m_Reset(false) +void PathSettings::setConfigurablePath(const QString &key, const QString& path) { + if (path.isEmpty()) { + remove(m_Settings, "Settings", key); + } else { + set(m_Settings, "Settings", key, path); + } } -void GeometrySettings::requestReset() +QString PathSettings::base() const { - m_Reset = true; + return QDir::fromNativeSeparators(get(m_Settings, + "Settings", "base_directory", qApp->property("dataPath").toString())); } -void GeometrySettings::resetIfNeeded() +QString PathSettings::downloads(bool resolve) const { - if (!m_Reset) { - return; - } - - removeSection(m_Settings, "Geometry"); + return getConfigurablePath( + "download_directory", + ToQString(AppConfig::downloadPath()), + resolve); } -void GeometrySettings::saveGeometry(const QWidget* w) +QString PathSettings::cache(bool resolve) const { - set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); + return getConfigurablePath( + "cache_directory", + ToQString(AppConfig::cachePath()), + resolve); } -bool GeometrySettings::restoreGeometry(QWidget* w) const +QString PathSettings::mods(bool resolve) const { - if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { - w->restoreGeometry(*v); - return true; - } + return getConfigurablePath( + "mod_directory", + ToQString(AppConfig::modsPath()), + resolve); +} - return false; +QString PathSettings::profiles(bool resolve) const +{ + return getConfigurablePath( + "profiles_directory", + ToQString(AppConfig::profilesPath()), + resolve); } -void GeometrySettings::saveState(const QMainWindow* w) +QString PathSettings::overwrite(bool resolve) const { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + return getConfigurablePath( + "overwrite_directory", + ToQString(AppConfig::overwritePath()), + resolve); } -bool GeometrySettings::restoreState(QMainWindow* w) const +void PathSettings::setBase(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; + if (path.isEmpty()) { + remove(m_Settings, "Settings", "base_directory"); + } else { + set(m_Settings, "Settings", "base_directory", path); } - - return false; } -void GeometrySettings::saveState(const QHeaderView* w) +void PathSettings::setDownloads(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + setConfigurablePath("download_directory", path); } -bool GeometrySettings::restoreState(QHeaderView* w) const +void PathSettings::setMods(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; - } - - return false; + setConfigurablePath("mod_directory", path); } -void GeometrySettings::saveState(const QSplitter* w) +void PathSettings::setCache(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); + setConfigurablePath("cache_directory", path); } -bool GeometrySettings::restoreState(QSplitter* w) const +void PathSettings::setProfiles(const QString& path) { - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(w))) { - w->restoreState(*v); - return true; - } - - return false; + setConfigurablePath("profiles_directory", path); } -void GeometrySettings::saveState(const ExpanderWidget* expander) +void PathSettings::setOverwrite(const QString& path) { - set(m_Settings, "Geometry", stateSettingName(expander), expander->saveState()); + setConfigurablePath("overwrite_directory", path); } -bool GeometrySettings::restoreState(ExpanderWidget* expander) const -{ - if (auto v=getOptional(m_Settings, "Geometry", stateSettingName(expander))) { - expander->restoreState(*v); - return true; - } - return false; +NetworkSettings::NetworkSettings(QSettings& settings) + : m_Settings(settings) +{ } -void GeometrySettings::saveVisibility(const QWidget* w) +bool NetworkSettings::offlineMode() const { - set(m_Settings, "Geometry", visibilitySettingName(w), w->isVisible()); + return get(m_Settings, "Settings", "offline_mode", false); } -bool GeometrySettings::restoreVisibility(QWidget* w, std::optional def) const +void NetworkSettings::setOfflineMode(bool b) { - if (auto v=getOptional(m_Settings, "Geometry", visibilitySettingName(w), def)) { - w->setVisible(*v); - return true; - } + set(m_Settings, "Settings", "offline_mode", b); +} - return false; +bool NetworkSettings::useProxy() const +{ + return get(m_Settings, "Settings", "use_proxy", false); } -void GeometrySettings::restoreToolbars(QMainWindow* w) const +void NetworkSettings::setUseProxy(bool b) { - // all toolbars have the same size and button style settings - const auto size = getOptional(m_Settings, "Geometry", "toolbar_size"); - const auto style = getOptional(m_Settings, "Geometry", "toolbar_button_style"); + set(m_Settings, "Settings", "use_proxy", b); +} - for (auto* tb : w->findChildren()) { - if (size) { - tb->setIconSize(*size); - } +void NetworkSettings::setDownloadSpeed(const QString& name, int bytesPerSecond) +{ + auto current = servers(); - if (style) { - tb->setToolButtonStyle(static_cast(*style)); + for (auto& server : current) { + if (server.name() == name) { + server.addDownload(bytesPerSecond); + updateServers(current); + return; } - - restoreVisibility(tb); } + + log::error( + "server '{}' not found while trying to add a download with bps {}", + name, bytesPerSecond); } -void GeometrySettings::saveToolbars(const QMainWindow* w) +ServerList NetworkSettings::servers() const { - const auto tbs = w->findChildren(); + // servers used to be a map of byte arrays until 2.2.1, it's now an array of + // individual values instead + // + // so post 2.2.1, only one key is returned: "size", the size of the arrays; + // in 2.2.1, one key per server is returned + { + const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - // save visibility for all - for (auto* tb : tbs) { - saveVisibility(tb); + if (!keys.empty() && keys[0] != "size") { + // old format + return serversFromOldMap(); + } } - // all toolbars have the same size and button style settings, just save the - // first one - if (!tbs.isEmpty()) { - const auto* tb = tbs[0]; - set(m_Settings, "Geometry", "toolbar_size", tb->iconSize()); - set(m_Settings, "Geometry", "toolbar_button_style", static_cast(tb->toolButtonStyle())); - } -} + // post 2.2.1 format, array of values -QStringList GeometrySettings::getModInfoTabOrder() const -{ - QStringList v; + ServerList list; - if (m_Settings.contains("mod_info_tabs")) { - // old byte array from 2.2.0 - QDataStream stream(m_Settings.value("mod_info_tabs").toByteArray()); + { + ScopedReadArray sra(m_Settings, "Servers"); - int count = 0; - stream >> count; + sra.for_each([&] { + ServerInfo::SpeedList lastDownloads; - for (int i=0; i> s; - v.push_back(s); - } - } else { - // string list since 2.2.1 - QString string = m_Settings.value("mod_info_tab_order").toString(); - QTextStream stream(&string); + const auto lastDownloadsString = sra.get("lastDownloads", ""); - while (!stream.atEnd()) { - QString s; - stream >> s; - v.push_back(s); - } + for (const auto& s : lastDownloadsString.split(" ")) { + const auto bytesPerSecond = s.toInt(); + if (bytesPerSecond > 0) { + lastDownloads.push_back(bytesPerSecond); + } + } + + ServerInfo server( + sra.get("name", ""), + sra.get("premium", false), + QDate::fromString(sra.get("lastSeen", ""), Qt::ISODate), + sra.get("preferred", 0), + lastDownloads); + + list.add(std::move(server)); + }); } - return v; + return list; } -void GeometrySettings::setModInfoTabOrder(const QString& names) +ServerList NetworkSettings::serversFromOldMap() const { - set(m_Settings, "Geometry", "mod_info_tab_order", names); -} + // for 2.2.1 and before -void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) -{ - const auto monitor = getOptional( - m_Settings, "Geometry", "MainWindow_monitor"); + ServerList list; + const ScopedGroup sg(m_Settings, "Servers"); - QPoint center; + sg.for_each([&](auto&& serverKey) { + QVariantMap data = sg.get(serverKey); - if (monitor && QGuiApplication::screens().size() > *monitor) { - center = QGuiApplication::screens().at(*monitor)->geometry().center(); - } else { - center = QGuiApplication::primaryScreen()->geometry().center(); - } + ServerInfo server( + serverKey, + data["premium"].toBool(), + data["lastSeen"].toDate(), + data["preferred"].toInt(), + {}); - w->move(center - w->rect().center()); -} + // ignoring download count and speed, it's now a list of values instead of + // a total -void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) -{ - if (auto* handle=w->windowHandle()) { - if (auto* screen = handle->screen()) { - const int screenId = QGuiApplication::screens().indexOf(screen); - set(m_Settings, "Geometry", "MainWindow_monitor", screenId); - } - } + list.add(std::move(server)); + }); + + return list; } -Qt::Orientation dockOrientation(const QMainWindow* mw, const QDockWidget* d) +void NetworkSettings::updateServers(ServerList servers) { - // docks in these areas are horizontal - const auto horizontalAreas = - Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea; + // clean up unavailable servers + servers.cleanup(); - if (mw->dockWidgetArea(const_cast(d)) & horizontalAreas) { - return Qt::Horizontal; - } else { - return Qt::Vertical; - } -} + removeSection(m_Settings, "Servers"); -void GeometrySettings::saveDocks(const QMainWindow* mw) -{ - // this attempts to fix https://bugreports.qt.io/browse/QTBUG-46620 where dock - // sizes are not restored when the main window is maximized; it is used in - // MainWindow::readSettings() and MainWindow::storeSettings() - // - // there's also https://stackoverflow.com/questions/44005852, which has what - // seems to be a popular fix, but it breaks the restored size of the window - // by setting it to the desktop's resolution, so that doesn't work - // - // the only fix I could find is to remember the sizes of the docks and manually - // setting them back; saving is straightforward, but restoring is messy - // - // this also depends on the window being visible before the timer in restore() - // is fired and the timer must be processed by application.exec(); therefore, - // the splash screen _must_ be closed before readSettings() is called, because - // it has its own event loop, which seems to interfere with this - // - // all of this should become unnecessary when QTBUG-46620 is fixed - // + { + ScopedWriteArray swa(m_Settings, "Servers"); - // saves the size of each dock - for (const auto* dock : mw->findChildren()) { - int size = 0; + for (const auto& server : servers) { + swa.next(); - // save the width for horizontal docks, or the height for vertical - if (dockOrientation(mw, dock) == Qt::Horizontal) { - size = dock->size().width(); - } else { - size = dock->size().height(); - } + swa.set("name", server.name()); + swa.set("premium", server.isPremium()); + swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); + swa.set("preferred", server.preferred()); - set(m_Settings, "Geometry", dockSettingName(dock), size); + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } + } + + swa.set("lastDownloads", lastDownloads.trimmed()); + } } } -void GeometrySettings::restoreDocks(QMainWindow* mw) const +void NetworkSettings::dump() const { - struct DockInfo - { - QDockWidget* d; - int size = 0; - Qt::Orientation ori; - }; - - std::vector dockInfos; + log::debug("servers:"); - // for each dock - for (auto* dock : mw->findChildren()) { - if (auto size=getOptional(m_Settings, "Geometry", dockSettingName(dock))) { - // remember this dock, its size and orientation - dockInfos.push_back({dock, *size, dockOrientation(mw, dock)}); + for (const auto& server : servers()) { + QString lastDownloads; + for (auto speed : server.lastDownloads()) { + lastDownloads += QString("%1 ").arg(speed); } - } - // the main window must have had time to process the settings from - // readSettings() or it seems to override whatever is set here - // - // some people said a single processEvents() call is enough, but it doesn't - // look like it - QTimer::singleShot(5, [=] { - for (const auto& info : dockInfos) { - mw->resizeDocks({info.d}, {info.size}, info.ori); - } - }); + log::debug( + " . {} premium={} lastSeen={} preferred={} lastDownloads={}", + server.name(), + server.isPremium() ? "yes" : "no", + server.lastSeen().toString(Qt::ISODate), + server.preferred(), + lastDownloads.trimmed()); + } } -ColorSettings::ColorSettings(QSettings& s) - : m_Settings(s) +NexusSettings::NexusSettings(Settings& parent, QSettings& settings) + : m_Parent(parent), m_Settings(settings) { } -QColor ColorSettings::modlistOverwrittenLoose() const +bool NexusSettings::automaticLoginEnabled() const { - return get( - m_Settings, "Settings", "overwrittenLooseFilesColor", - QColor(0, 255, 0, 64)); + return get(m_Settings, "Settings", "nexus_login", false); } -void ColorSettings::setModlistOverwrittenLoose(const QColor& c) +bool NexusSettings::apiKey(QString &apiKey) const { - set(m_Settings, "Settings", "overwrittenLooseFilesColor", c); + QString tempKey = getWindowsCredential("APIKEY"); + if (tempKey.isEmpty()) + return false; + + apiKey = tempKey; + return true; } -QColor ColorSettings::modlistOverwritingLoose() const +bool NexusSettings::setApiKey(const QString& apiKey) { - return get( - m_Settings, "Settings", "overwritingLooseFilesColor", - QColor(255, 0, 0, 64)); + if (!setWindowsCredential("APIKEY", apiKey)) { + const auto e = GetLastError(); + log::error("Storing API key failed: {}", formatSystemMessage(e)); + return false; + } + + return true; } -void ColorSettings::setModlistOverwritingLoose(const QColor& c) +bool NexusSettings::clearApiKey() { - set(m_Settings, "Settings", "overwritingLooseFilesColor", c); + return setApiKey(""); } -QColor ColorSettings::modlistOverwrittenArchive() const +bool NexusSettings::hasApiKey() const { - return get( - m_Settings, "Settings", "overwrittenArchiveFilesColor", - QColor(0, 255, 255, 64)); + return !getWindowsCredential("APIKEY").isEmpty(); } -void ColorSettings::setModlistOverwrittenArchive(const QColor& c) +bool NexusSettings::endorsementIntegration() const { - set(m_Settings, "Settings", "overwrittenArchiveFilesColor", c); + return get(m_Settings, "Settings", "endorsement_integration", true); } -QColor ColorSettings::modlistOverwritingArchive() const +void NexusSettings::setEndorsementIntegration(bool b) const { - return get( - m_Settings, "Settings", "overwritingArchiveFilesColor", - QColor(255, 0, 255, 64)); + set(m_Settings, "Settings", "endorsement_integration", b); } -void ColorSettings::setModlistOverwritingArchive(const QColor& c) +EndorsementState NexusSettings::endorsementState() const { - set(m_Settings, "Settings", "overwritingArchiveFilesColor", c); + return endorsementStateFromString( + get(m_Settings, "General", "endorse_state", "")); } -QColor ColorSettings::modlistContainsPlugin() const +void NexusSettings::setEndorsementState(EndorsementState s) { - return get( - m_Settings, "Settings", "containsPluginColor", - QColor(0, 0, 255, 64)); + const auto v = toString(s); + + if (v.isEmpty()) { + remove(m_Settings, "General", "endorse_state"); + } else { + set(m_Settings, "General", "endorse_state", v); + } } -void ColorSettings::setModlistContainsPlugin(const QColor& c) +void NexusSettings::registerAsNXMHandler(bool force) { - set(m_Settings, "Settings", "containsPluginColor", c); + const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; + const auto executable = QCoreApplication::applicationFilePath(); + + QString mode = force ? "forcereg" : "reg"; + QString parameters = mode + " " + m_Parent.game().plugin()->gameShortName(); + for (const QString& altGame : m_Parent.game().plugin()->validShortNames()) { + parameters += "," + altGame; + } + parameters += " \"" + executable + "\""; + + if (!shell::Execute(nxmPath, parameters)) { + QMessageBox::critical( + nullptr, QObject::tr("Failed"), + QObject::tr("Failed to start the helper application")); + } } -QColor ColorSettings::pluginListContained() const + +SteamSettings::SteamSettings(Settings& parent, QSettings& settings) + : m_Parent(parent), m_Settings(settings) { - return get( - m_Settings, "Settings", "containedColor", - QColor(0, 0, 255, 64)); } -void ColorSettings::setPluginListContained(const QColor& c) +QString SteamSettings::appID() const { - set(m_Settings, "Settings", "containedColor", c); + return get( + m_Settings, "Settings", "app_id", m_Parent.game().plugin()->steamAPPId()); } - -PluginSettings::PluginSettings(QSettings& settings) - : m_Settings(settings) +void SteamSettings::setAppID(const QString& id) { + if (id.isEmpty()) { + remove(m_Settings, "Settings", "app_id"); + } else { + set(m_Settings, "Settings", "app_id", id); + } } -void PluginSettings::clearPlugins() +bool SteamSettings::login(QString &username, QString &password) const { - m_Plugins.clear(); - m_PluginSettings.clear(); - - m_PluginBlacklist.clear(); + username = get(m_Settings, "Settings", "steam_username", ""); + password = getWindowsCredential("steam_password"); - ScopedReadArray sra(m_Settings, "pluginBlacklist"); - sra.for_each([&]{ - m_PluginBlacklist.insert(sra.get("name")); - }); + return !username.isEmpty() && !password.isEmpty(); } -void PluginSettings::registerPlugin(IPlugin *plugin) +void SteamSettings::setLogin(QString username, QString password) { - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QVariantMap()); - m_PluginDescriptions.insert(plugin->name(), QVariantMap()); + if (username == "") { + remove(m_Settings, "Settings", "steam_username"); + password = ""; + } else { + set(m_Settings, "Settings", "steam_username", username); + } - for (const PluginSetting &setting : plugin->settings()) { - const QString settingName = plugin->name() + "/" + setting.key; + if (!setWindowsCredential("steam_password", password)) { + const auto e = GetLastError(); + log::error("Storing or deleting password failed: {}", formatSystemMessage(e)); + } +} - QVariant temp = get( - m_Settings, "Plugins", settingName, setting.defaultValue); - if (!temp.convert(setting.defaultValue.type())) { - log::warn( - "failed to interpret \"{}\" as correct type for \"{}\" in plugin \"{}\", using default", - temp.toString(), setting.key, plugin->name()); +InterfaceSettings::InterfaceSettings(QSettings& settings) + : m_Settings(settings) +{ +} - temp = setting.defaultValue; - } +bool InterfaceSettings::lockGUI() const +{ + return get(m_Settings, "Settings", "lock_gui", true); +} - m_PluginSettings[plugin->name()][setting.key] = temp; +void InterfaceSettings::setLockGUI(bool b) +{ + set(m_Settings, "Settings", "lock_gui", b); +} - m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)") - .arg(setting.description) - .arg(setting.defaultValue.toString()); - } +std::optional InterfaceSettings::styleName() const +{ + return getOptional(m_Settings, "Settings", "style"); } -bool PluginSettings::pluginBlacklisted(const QString &fileName) const +void InterfaceSettings::setStyleName(const QString& name) { - return m_PluginBlacklist.contains(fileName); + set(m_Settings, "Settings", "style", name); } -QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const +bool InterfaceSettings::compactDownloads() const { - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } + return get(m_Settings, "Settings", "compact_downloads", false); +} - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } +void InterfaceSettings::setCompactDownloads(bool b) +{ + set(m_Settings, "Settings", "compact_downloads", b); +} - return *iterSetting; +bool InterfaceSettings::metaDownloads() const +{ + return get(m_Settings, "Settings", "meta_downloads", false); } -void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +void InterfaceSettings::setMetaDownloads(bool b) { - auto iterPlugin = m_PluginSettings.find(pluginName); + set(m_Settings, "Settings", "meta_downloads", b); +} - if (iterPlugin == m_PluginSettings.end()) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } +bool InterfaceSettings::hideAPICounter() const +{ + return get(m_Settings, "Settings", "hide_api_counter", false); +} - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - set(m_Settings, "Plugins", pluginName + "/" + key, value); +void InterfaceSettings::setHideAPICounter(bool b) +{ + set(m_Settings, "Settings", "hide_api_counter", b); } -QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +bool InterfaceSettings::displayForeign() const { - if (!m_PluginSettings.contains(pluginName)) { - return def; - } + return get(m_Settings, "Settings", "display_foreign", true); +} - return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); +void InterfaceSettings::setDisplayForeign(bool b) +{ + set(m_Settings, "Settings", "display_foreign", b); } -void PluginSettings::setPluginPersistent( - const QString &pluginName, const QString &key, const QVariant &value, bool sync) +QString InterfaceSettings::language() { - if (!m_PluginSettings.contains(pluginName)) { - throw MyException( - QObject::tr("attempt to store setting for unknown plugin \"%1\"") - .arg(pluginName)); - } + QString result = get(m_Settings, "Settings", "language", ""); - set(m_Settings, "PluginPersistance", pluginName + "/" + key, value); + if (result.isEmpty()) { + QStringList languagePreferences = QLocale::system().uiLanguages(); - if (sync) { - m_Settings.sync(); + if (languagePreferences.length() > 0) { + // the users most favoritest language + result = languagePreferences.at(0); + } else { + // fallback system locale + result = QLocale::system().name(); + } } + + return result; } -void PluginSettings::addBlacklistPlugin(const QString &fileName) +void InterfaceSettings::setLanguage(const QString& name) { - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); + set(m_Settings, "Settings", "language", name); } -void PluginSettings::writePluginBlacklist() +bool InterfaceSettings::isTutorialCompleted(const QString& windowName) const { - removeSection(m_Settings, "PluginBlacklist"); - - ScopedWriteArray swa(m_Settings, "PluginBlacklist"); - - for (const QString &plugin : m_PluginBlacklist) { - swa.next(); - swa.set("name", plugin); - } + return get(m_Settings, "CompletedWindowTutorials", windowName, false); } -QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const +void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b) { - return m_PluginSettings[pluginName]; + set(m_Settings, "CompletedWindowTutorials", windowName, b); } -void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) + +DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) + : m_Settings(settings) { - m_PluginSettings[pluginName] = map; } -QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const +log::Levels DiagnosticsSettings::logLevel() const { - return m_PluginDescriptions[pluginName]; + return get(m_Settings, "Settings", "log_level", log::Levels::Info); } -void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) +void DiagnosticsSettings::setLogLevel(log::Levels level) { - m_PluginDescriptions[pluginName] = map; + set(m_Settings, "Settings", "log_level", level); } -const QSet& PluginSettings::pluginBlacklist() const +CrashDumpsType DiagnosticsSettings::crashDumpsType() const { - return m_PluginBlacklist; + return get(m_Settings, + "Settings", "crash_dumps_type", CrashDumpsType::Mini); } -void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) +void DiagnosticsSettings::setCrashDumpsType(CrashDumpsType type) { - m_PluginBlacklist.clear(); - - for (const auto& name : pluginNames) { - m_PluginBlacklist.insert(name); - } + set(m_Settings, "Settings", "crash_dumps_type", type); } -void PluginSettings::save() +int DiagnosticsSettings::crashDumpsMax() const { - for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { - const auto key = iterPlugins.key() + "/" + iterSettings.key(); - set(m_Settings, "Plugins", key, iterSettings.value()); - } - } + return get(m_Settings, "Settings", "crash_dumps_max", 5); +} - writePluginBlacklist(); +void DiagnosticsSettings::setCrashDumpsMax(int n) +{ + set(m_Settings, "Settings", "crash_dumps_max", n); } diff --git a/src/settings.h b/src/settings.h index 403c2d71..5c0a2542 100644 --- a/src/settings.h +++ b/src/settings.h @@ -25,6 +25,10 @@ along with Mod Organizer. If not, see . #include #include +#ifdef interface + #undef interface +#endif + namespace MOBase { class IPlugin; class IPluginGame; @@ -50,6 +54,58 @@ private: }; +class GameSettings +{ +public: + GameSettings(QSettings& setting); + + const MOBase::IPluginGame* plugin(); + void setPlugin(const MOBase::IPluginGame* gamePlugin); + + /** + * whether files of the core game are forced-enabled so the user can't + * accidentally disable them + */ + bool forceEnableCoreFiles() const; + void setForceEnableCoreFiles(bool b); + + /** + * the directory where the managed game is stored (with native separators) + **/ + std::optional directory() const; + void setDirectory(const QString& path); + + std::optional name() const; + void setName(const QString& name); + + std::optional edition() const; + void setEdition(const QString& name); + + std::optional selectedProfileName() const; + void setSelectedProfileName(const QString& name); + + /** + * @return the load mechanism to be used + **/ + LoadMechanism::EMechanism loadMechanismType() const; + void setLoadMechanism(LoadMechanism::EMechanism m); + const LoadMechanism& loadMechanism() const; + void setupLoadMechanism(); + + /** + * @return true if the user wants unchecked plugins (esp, esm) should be hidden from + * the virtual data directory + **/ + bool hideUncheckedPlugins() const; + void setHideUncheckedPlugins(bool b); + +private: + QSettings& m_Settings; + const MOBase::IPluginGame* m_GamePlugin; + LoadMechanism m_LoadMechanism; +}; + + class GeometrySettings { public: @@ -83,7 +139,7 @@ public: void saveDocks(const QMainWindow* w); void restoreDocks(QMainWindow* w) const; - QStringList getModInfoTabOrder() const; + QStringList modInfoTabOrder() const; void setModInfoTabOrder(const QString& names); void centerOnMainWindowMonitor(QWidget* w); @@ -95,6 +151,40 @@ private: }; +class WidgetSettings +{ +public: + WidgetSettings(QSettings& s); + + std::optional index(const QComboBox* cb) const; + void saveIndex(const QComboBox* cb); + void restoreIndex(QComboBox* cb, std::optional def={}) const; + + std::optional index(const QTabWidget* w) const; + void saveIndex(const QTabWidget* w); + void restoreIndex(QTabWidget* w, std::optional def={}) const; + + std::optional checked(const QAbstractButton* w) const; + void saveChecked(const QAbstractButton* w); + void restoreChecked(QAbstractButton* w, std::optional def={}) const; + + MOBase::QuestionBoxMemory::Button questionButton( + const QString& windowName, const QString& filename) const; + + void setQuestionWindowButton( + const QString& windowName, MOBase::QuestionBoxMemory::Button button); + + void setQuestionFileButton( + const QString& windowName, const QString& filename, + MOBase::QuestionBoxMemory::Button choice); + + void resetQuestionButtons(); + +private: + QSettings& m_Settings; +}; + + class ColorSettings { public: @@ -120,6 +210,19 @@ public: QColor pluginListContained() const; void setPluginListContained(const QColor& c) ; + std::optional previousSeparatorColor() const; + void setPreviousSeparatorColor(const QColor& c) const; + void removePreviousSeparatorColor(); + + /** + * @brief color the scrollbar of the mod list for custom separator colors? + * @return the state of the setting + */ + bool colorSeparatorScrollbar() const; + void setColorSeparatorScrollbar(bool b); + + static QColor idealTextColor(const QColor& rBackgroundColor); + private: QSettings& m_Settings; }; @@ -165,210 +268,229 @@ private: }; -enum class EndorsementState -{ - Accepted = 1, - Refused, - NoDecision -}; - -EndorsementState endorsementStateFromString(const QString& s); -QString toString(EndorsementState s); - - -/** - * manages the settings for Mod Organizer. The settings are not cached - * inside the class but read/written directly from/to disc - **/ -class Settings : public QObject +class PathSettings { - Q_OBJECT; - public: - Settings(const QString& path); - ~Settings(); + PathSettings(QSettings& settings); - static Settings &instance(); + QString base() const; + QString downloads(bool resolve = true) const; + QString mods(bool resolve = true) const; + QString cache(bool resolve = true) const; + QString profiles(bool resolve = true) const; + QString overwrite(bool resolve = true) const; - void processUpdates( - const QVersionNumber& currentVersion, const QVersionNumber& lastVersion); + void setBase(const QString& path); + void setDownloads(const QString& path); + void setMods(const QString& path); + void setCache(const QString& path); + void setProfiles(const QString& path); + void setOverwrite(const QString& path); - QString getFilename() const; + std::map recent() const; + void setRecent(const std::map& map); - /** - * @return true if the user wants unchecked plugins (esp, esm) should be hidden from - * the virtual dat adirectory - **/ - bool hideUncheckedPlugins() const; - void setHideUncheckedPlugins(bool b); +private: + QSettings& m_Settings; - /** - * @return true if files of the core game are forced-enabled so the user can't accidentally disable them - */ - bool forceEnableCoreFiles() const; - void setForceEnableCoreFiles(bool b); + QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; + void setConfigurablePath(const QString &key, const QString& path); +}; - /** - * @return true if the GUI should be locked when running executables - */ - bool lockGUI() const; - void setLockGUI(bool b); - /** - * the steam appid is assigned by the steam platform to each product sold there. - * The appid may differ between different versions of a game so it may be impossible - * for Mod Organizer to automatically recognize it, though usually it does - * @return the steam appid for the game - **/ - QString getSteamAppID() const; - void setSteamAppID(const QString& id); - - QString getBaseDirectory() const; - QString getDownloadDirectory(bool resolve = true) const; - QString getModDirectory(bool resolve = true) const; - QString getCacheDirectory(bool resolve = true) const; - QString getProfileDirectory(bool resolve = true) const; - QString getOverwriteDirectory(bool resolve = true) const; - - void setBaseDirectory(const QString& path); - void setDownloadDirectory(const QString& path); - void setModDirectory(const QString& path); - void setCacheDirectory(const QString& path); - void setProfileDirectory(const QString& path); - void setOverwriteDirectory(const QString& path); +class NetworkSettings +{ +public: + NetworkSettings(QSettings& settings); /** - * retrieve the directory where the managed game is stored (with native separators) - **/ - std::optional getManagedGameDirectory() const; - void setManagedGameDirectory(const QString& path); - - std::optional getManagedGameName() const; - void setManagedGameName(const QString& name); - - std::optional getManagedGameEdition() const; - void setManagedGameEdition(const QString& name); + * @return true if the user disabled internet features + */ + bool offlineMode() const; + void setOfflineMode(bool b); - std::optional getSelectedProfileName() const; - void setSelectedProfileName(const QString& name); + /** + * @return true if the user configured the use of a network proxy + */ + bool useProxy() const; + void setUseProxy(bool b); - std::optional getStyleName() const; - void setStyleName(const QString& name); + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + ServerList servers() const; + void updateServers(ServerList servers); - std::optional getVersion() const; + void dump() const; - bool getFirstStart() const; - void setFirstStart(bool b); +private: + QSettings& m_Settings; - std::optional getPreviousSeparatorColor() const; - void setPreviousSeparatorColor(const QColor& c) const; - void removePreviousSeparatorColor(); + ServerList serversFromOldMap() const; +}; - std::map getRecentDirectories() const; - void setRecentDirectories(const std::map& map); - std::vector> getExecutables() const; - void setExecutables(const std::vector>& v); +enum class EndorsementState +{ + Accepted = 1, + Refused, + NoDecision +}; - bool isTutorialCompleted(const QString& windowName) const; - void setTutorialCompleted(const QString& windowName, bool b=true); +EndorsementState endorsementStateFromString(const QString& s); +QString toString(EndorsementState s); - bool keepBackupOnInstall() const; - void setKeepBackupOnInstall(bool b); +class NexusSettings +{ +public: + NexusSettings(Settings& parent, QSettings& settings); - MOBase::QuestionBoxMemory::Button getQuestionButton( - const QString& windowName, const QString& filename) const; + /** + * @return true if the user has set up automatic login to nexus + **/ + bool automaticLoginEnabled() const; - void setQuestionWindowButton( - const QString& windowName, MOBase::QuestionBoxMemory::Button button); + /** + * @brief retrieve the login information for nexus + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if automatic login is active, false otherwise + **/ + bool apiKey(QString &apiKey) const; - void setQuestionFileButton( - const QString& windowName, const QString& filename, - MOBase::QuestionBoxMemory::Button choice); + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + bool setApiKey(const QString& apiKey); - void resetQuestionButtons(); + /** + * @brief clears the nexus login information + */ + bool clearApiKey(); - std::optional getIndex(const QComboBox* cb) const; - void saveIndex(const QComboBox* cb); - void restoreIndex(QComboBox* cb, std::optional def={}) const; + /** + * @brief returns whether an API key is currently stored + */ + bool hasApiKey() const; - std::optional getIndex(const QTabWidget* w) const; - void saveIndex(const QTabWidget* w); - void restoreIndex(QTabWidget* w, std::optional def={}) const; + /** + * @return true if endorsement integration is enabled + */ + bool endorsementIntegration() const; + void setEndorsementIntegration(bool b) const; - std::optional getChecked(const QAbstractButton* w) const; - void saveChecked(const QAbstractButton* w); - void restoreChecked(QAbstractButton* w, std::optional def={}) const; + EndorsementState endorsementState() const; + void setEndorsementState(EndorsementState s); - GeometrySettings& geometry(); - const GeometrySettings& geometry() const; + /** + * @brief register MO as the handler for nxm links + * @param force set to true to enforce the registration dialog to show up, + * even if the user said earlier not to + */ + void registerAsNXMHandler(bool force); - ColorSettings& colors(); - const ColorSettings& colors() const; +private: + Settings& m_Parent; + QSettings& m_Settings; +}; - PluginSettings& plugins(); - const PluginSettings& plugins() const; +class SteamSettings +{ +public: + SteamSettings(Settings& parent, QSettings& settings); /** - * @return true if the user has set up automatic login to nexus - **/ - bool automaticLoginEnabled() const; + * the steam appid is assigned by the steam platform to each product sold there. + * The appid may differ between different versions of a game so it may be impossible + * for Mod Organizer to automatically recognize it, though usually it does + * @return the steam appid for the game + **/ + QString appID() const; + void setAppID(const QString& id); /** - * @brief retrieve the login information for nexus - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if automatic login is active, false otherwise - **/ - bool getNexusApiKey(QString &apiKey) const; + * @brief retrieve the login information for steam + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if a username has been specified, false otherwise + **/ + bool login(QString &username, QString &password) const; /** - * @brief set the nexus login information + * @brief set the steam login information * * @param username username * @param password password */ - bool setNexusApiKey(const QString& apiKey); + void setLogin(QString username, QString password); - /** - * @brief clears the nexus login information - */ - bool clearNexusApiKey(); +private: + Settings& m_Parent; + QSettings& m_Settings; +}; - /** - * @brief returns whether an API key is currently stored - */ - bool hasNexusApiKey() const; - /** - * @brief retrieve the login information for steam - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if a username has been specified, false otherwise - **/ - bool getSteamLogin(QString &username, QString &password) const; +class InterfaceSettings +{ +public: + InterfaceSettings(QSettings& settings); /** - * @return true if the user disabled internet features - */ - bool offlineMode() const; - void setOfflineMode(bool b); + * @return true if the GUI should be locked when running executables + */ + bool lockGUI() const; + void setLockGUI(bool b); + + std::optional styleName() const; + void setStyleName(const QString& name); /** - * @return true if the user chose compact downloads - */ + * @return true if the user chose compact downloads + */ bool compactDownloads() const; void setCompactDownloads(bool b); /** - * @return true if the user chose meta downloads - */ + * @return true if the user chose meta downloads + */ bool metaDownloads() const; void setMetaDownloads(bool b); + /** + * @return true if the API counter should be hidden + */ + bool hideAPICounter() const; + void setHideAPICounter(bool b); + + /** + * @return true if the user wants to see non-official plugins installed outside MO in his mod list + */ + bool displayForeign() const; + void setDisplayForeign(bool b); + + /** + * @return short code of the configured language (corresponding to the translation files) + */ + QString language(); + void setLanguage(const QString& name); + + bool isTutorialCompleted(const QString& windowName) const; + void setTutorialCompleted(const QString& windowName, bool b=true); + +private: + QSettings& m_Settings; +}; + + +class DiagnosticsSettings +{ +public: + DiagnosticsSettings(QSettings& settings); + MOBase::log::Levels logLevel() const; void setLogLevel(MOBase::log::Levels level); @@ -378,60 +500,48 @@ public: int crashDumpsMax() const; void setCrashDumpsMax(int n); - QString executablesBlacklist() const; - void setExecutablesBlacklist(const QString& s); +private: + QSettings& m_Settings; +}; - /** - * @brief set the steam login information - * - * @param username username - * @param password password - */ - void setSteamLogin(QString username, QString password); - /** - * @return the load mechanism to be used - **/ - LoadMechanism::EMechanism getLoadMechanism() const; - void setLoadMechanism(LoadMechanism::EMechanism m); - /** - * @brief activate the load mechanism selected by the user - **/ - void setupLoadMechanism(); +/** + * manages the settings for Mod Organizer. The settings are not cached + * inside the class but read/written directly from/to disc + **/ +class Settings : public QObject +{ + Q_OBJECT; - /** - * @return true if the user configured the use of a network proxy - */ - bool getUseProxy() const; - void setUseProxy(bool b); +public: + Settings(const QString& path); + ~Settings(); - /** - * @return true if endorsement integration is enabled - */ - bool endorsementIntegration() const; - void setEndorsementIntegration(bool b) const; + static Settings &instance(); - EndorsementState endorsementState() const; - void setEndorsementState(EndorsementState s); + QString filename() const; - /** - * @return true if the API counter should be hidden - */ - bool hideAPICounter() const; - void setHideAPICounter(bool b); + std::optional version() const; + void processUpdates(const QVersionNumber& current, const QVersionNumber& last); - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ - bool displayForeign() const; - void setDisplayForeign(bool b); + bool firstStart() const; + void setFirstStart(bool b); + + std::vector> executables() const; + void setExecutables(const std::vector>& v); + + bool keepBackupOnInstall() const; + void setKeepBackupOnInstall(bool b); + + QString executablesBlacklist() const; + void setExecutablesBlacklist(const QString& s); /** * @brief sets the new motd hash **/ - unsigned int getMotDHash() const; - void setMotDHash(unsigned int hash); + unsigned int motdHash() const; + void setMotdHash(unsigned int hash); /** * @return true if the user wants to have archives being parsed to show conflicts and contents @@ -439,41 +549,45 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); - /** - * @return short code of the configured language (corresponding to the translation files) - */ - QString language(); - void setLanguage(const QString& name); - - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - ServerList getServers() const; - ServerList getServersFromOldMap() const; - void updateServers(ServerList servers); - bool usePrereleases() const; void setUsePrereleases(bool b); - /** - * @brief register MO as the handler for nxm links - * @param force set to true to enforce the registration dialog to show up, - * even if the user said earlier not to - */ - void registerAsNXMHandler(bool force); - /** - * @brief color the scrollbar of the mod list for custom separator colors? - * @return the state of the setting - */ - bool colorSeparatorScrollbar() const; - void setColorSeparatorScrollbar(bool b); + GameSettings& game(); + const GameSettings& game() const; + + GeometrySettings& geometry(); + const GeometrySettings& geometry() const; - static QColor getIdealTextColor(const QColor& rBackgroundColor); + WidgetSettings& widgets(); + const WidgetSettings& widgets() const; - MOBase::IPluginGame const *gamePlugin() { return m_GamePlugin; } - const LoadMechanism& loadMechanism() const { return m_LoadMechanism; } + ColorSettings& colors(); + const ColorSettings& colors() const; - QSettings::Status sync() const; + PluginSettings& plugins(); + const PluginSettings& plugins() const; + + PathSettings& paths(); + const PathSettings& paths() const; + + NetworkSettings& network(); + const NetworkSettings& network() const; + + NexusSettings& nexus(); + const NexusSettings& nexus() const; + + SteamSettings& steam(); + const SteamSettings& steam() const; + InterfaceSettings& interface(); + const InterfaceSettings& interface() const; + + DiagnosticsSettings& diagnostics(); + const DiagnosticsSettings& diagnostics() const; + + + QSettings::Status sync() const; void dump() const; public slots: @@ -485,18 +599,19 @@ signals: private: static Settings *s_Instance; - MOBase::IPluginGame const *m_GamePlugin; mutable QSettings m_Settings; + + GameSettings m_Game; GeometrySettings m_Geometry; + WidgetSettings m_Widgets; ColorSettings m_Colors; PluginSettings m_Plugins; - LoadMechanism m_LoadMechanism; - - static bool obfuscate(const QString key, const QString data); - static QString deObfuscate(const QString key); - - QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; - void setConfigurablePath(const QString &key, const QString& path); + PathSettings m_Paths; + NetworkSettings m_Network; + NexusSettings m_Nexus; + SteamSettings m_Steam; + InterfaceSettings m_Interface; + DiagnosticsSettings m_Diagnostics; }; #endif // SETTINGS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 35d14644..1d3d4a39 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -51,11 +51,11 @@ int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); - m_settings.restoreIndex(ui->tabWidget); + m_settings.widgets().restoreIndex(ui->tabWidget); auto ret = TutorableDialog::exec(); - m_settings.saveIndex(ui->tabWidget); + m_settings.widgets().saveIndex(ui->tabWidget); if (ret == QDialog::Accepted) { for (auto&& tab : m_tabs) { @@ -109,7 +109,7 @@ void SettingsDialog::accept() if ((QDir::fromNativeSeparators(newModPath) != QDir::fromNativeSeparators( - Settings::instance().getModDirectory(true))) && + Settings::instance().paths().mods(true))) && (QMessageBox::question( nullptr, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 278da0bf..386c7425 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -12,7 +12,7 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) setLevelsBox(); setCrashDumpTypesBox(); - ui->dumpsMaxEdit->setValue(settings().crashDumpsMax()); + ui->dumpsMaxEdit->setValue(settings().diagnostics().crashDumpsMax()); QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); @@ -36,7 +36,7 @@ void DiagnosticsSettingsTab::setLevelsBox() ui->logLevelBox->addItem(QObject::tr("Error"), log::Error); for (int i=0; ilogLevelBox->count(); ++i) { - if (ui->logLevelBox->itemData(i) == settings().logLevel()) { + if (ui->logLevelBox->itemData(i) == settings().diagnostics().logLevel()) { ui->logLevelBox->setCurrentIndex(i); break; } @@ -56,7 +56,8 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() add(QObject::tr("Data"), CrashDumpsType::Data); add(QObject::tr("Full"), CrashDumpsType::Full); - const auto current = static_cast(settings().crashDumpsType()); + const auto current = static_cast( + settings().diagnostics().crashDumpsType()); for (int i=0; idumpsTypeBox->count(); ++i) { if (ui->dumpsTypeBox->itemData(i) == current) { @@ -68,11 +69,11 @@ void DiagnosticsSettingsTab::setCrashDumpTypesBox() void DiagnosticsSettingsTab::update() { - settings().setLogLevel( + settings().diagnostics().setLogLevel( static_cast(ui->logLevelBox->currentData().toInt())); - settings().setCrashDumpsType( + settings().diagnostics().setCrashDumpsType( static_cast(ui->dumpsTypeBox->currentData().toInt())); - settings().setCrashDumpsMax(ui->dumpsMaxEdit->value()); + settings().diagnostics().setCrashDumpsMax(ui->dumpsMaxEdit->value()); } diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index e3d73037..3f7ece38 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -11,7 +11,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) { addLanguages(); { - QString languageCode = settings().language(); + QString languageCode = settings().interface().language(); int currentID = ui->languageBox->findData(languageCode); // I made a mess. :( Most languages are stored with only the iso country // code (2 characters like "de") but chinese @@ -29,7 +29,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) { const int currentID = ui->styleBox->findData( - settings().getStyleName().value_or("")); + settings().interface().styleName().value_or("")); if (currentID != -1) { ui->styleBox->setCurrentIndex(currentID); @@ -51,10 +51,10 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) setContainsColor(settings().colors().modlistContainsPlugin()); setContainedColor(settings().colors().pluginListContained()); - ui->compactBox->setChecked(settings().compactDownloads()); - ui->showMetaBox->setChecked(settings().metaDownloads()); + ui->compactBox->setChecked(settings().interface().compactDownloads()); + ui->showMetaBox->setChecked(settings().interface().metaDownloads()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); - ui->colorSeparatorsBox->setChecked(settings().colorSeparatorScrollbar()); + ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); }); QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); }); @@ -69,18 +69,18 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) void GeneralSettingsTab::update() { - const QString oldLanguage = settings().language(); + const QString oldLanguage = settings().interface().language(); const QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { - settings().setLanguage(newLanguage); + settings().interface().setLanguage(newLanguage); emit settings().languageChanged(newLanguage); } - const QString oldStyle = settings().getStyleName().value_or(""); + const QString oldStyle = settings().interface().styleName().value_or(""); const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); if (oldStyle != newStyle) { - settings().setStyleName(newStyle); + settings().interface().setStyleName(newStyle); emit settings().styleChanged(newStyle); } @@ -91,10 +91,10 @@ void GeneralSettingsTab::update() settings().colors().setModlistContainsPlugin(getContainsColor()); settings().colors().setPluginListContained(getContainedColor()); - settings().setCompactDownloads(ui->compactBox->isChecked()); - settings().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().interface().setCompactDownloads(ui->compactBox->isChecked()); + settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); - settings().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); + settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } void GeneralSettingsTab::addLanguages() @@ -145,7 +145,7 @@ void GeneralSettingsTab::addStyles() void GeneralSettingsTab::resetDialogs() { - settings().resetQuestionButtons(); + settings().widgets().resetQuestionButtons(); } void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) @@ -161,7 +161,7 @@ void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color .arg(color.green()) .arg(color.blue()) .arg(color.alpha()) - .arg(Settings::getIdealTextColor(color).name()) + .arg(ColorSettings::idealTextColor(color).name()) ); }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 8822200e..0b08f13f 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -74,13 +74,13 @@ private: NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->offlineBox->setChecked(settings().offlineMode()); - ui->proxyBox->setChecked(settings().getUseProxy()); - ui->endorsementBox->setChecked(settings().endorsementIntegration()); - ui->hideAPICounterBox->setChecked(settings().hideAPICounter()); + ui->offlineBox->setChecked(settings().network().offlineMode()); + ui->proxyBox->setChecked(settings().network().useProxy()); + ui->endorsementBox->setChecked(settings().nexus().endorsementIntegration()); + ui->hideAPICounterBox->setChecked(settings().interface().hideAPICounter()); // display server preferences - for (const auto& server : s.getServers()) { + for (const auto& server : s.network().servers()) { QString descriptor = server.name(); if (!descriptor.compare("CDN", Qt::CaseInsensitive)) { @@ -117,12 +117,12 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) void NexusSettingsTab::update() { - settings().setOfflineMode(ui->offlineBox->isChecked()); - settings().setUseProxy(ui->proxyBox->isChecked()); - settings().setEndorsementIntegration(ui->endorsementBox->isChecked()); - settings().setHideAPICounter(ui->hideAPICounterBox->isChecked()); + settings().network().setOfflineMode(ui->offlineBox->isChecked()); + settings().network().setUseProxy(ui->proxyBox->isChecked()); + settings().nexus().setEndorsementIntegration(ui->endorsementBox->isChecked()); + settings().interface().setHideAPICounter(ui->hideAPICounterBox->isChecked()); - auto servers = settings().getServers(); + auto servers = settings().network().servers(); // store server preference for (int i = 0; i < ui->knownServersList->count(); ++i) { @@ -167,7 +167,7 @@ void NexusSettingsTab::update() } } - settings().updateServers(servers); + settings().network().updateServers(servers); } void NexusSettingsTab::on_nexusConnect_clicked() @@ -225,13 +225,13 @@ void NexusSettingsTab::on_nexusDisconnect_clicked() void NexusSettingsTab::on_clearCacheButton_clicked() { - QDir(Settings::instance().getCacheDirectory()).removeRecursively(); + QDir(Settings::instance().paths().cache()).removeRecursively(); NexusInterface::instance(dialog().m_PluginContainer)->clearCache(); } void NexusSettingsTab::on_associateButton_clicked() { - Settings::instance().registerAsNXMHandler(true); + Settings::instance().nexus().registerAsNXMHandler(true); } void NexusSettingsTab::validateKey(const QString& key) @@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { dialog().m_keyChanged = true; - const bool ret = settings().setNexusApiKey(key); + const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; } @@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { dialog().m_keyChanged = true; - const auto ret = settings().clearNexusApiKey(); + const auto ret = settings().nexus().clearApiKey(); NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey(); updateNexusState(); @@ -352,7 +352,7 @@ void NexusSettingsTab::updateNexusButtons() ui->nexusManualKey->setText(QObject::tr("Cancel")); ui->nexusManualKey->setEnabled(true); } - else if (settings().hasNexusApiKey()) { + else if (settings().nexus().hasApiKey()) { // api key is present ui->nexusConnect->setText(QObject::tr("Connect to Nexus")); ui->nexusConnect->setEnabled(false); diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index 32aaf4bf..aeb4dd5d 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -6,17 +6,23 @@ PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->baseDirEdit->setText(settings().getBaseDirectory()); - ui->managedGameDirEdit->setText(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); - QString basePath = settings().getBaseDirectory(); + ui->baseDirEdit->setText(settings().paths().base()); + + ui->managedGameDirEdit->setText( + settings().game().plugin()->gameDirectory().absoluteFilePath( + settings().game().plugin()->binaryName())); + + QString basePath = settings().paths().base(); QDir baseDir(basePath); + for (const auto &dir : { - std::make_pair(ui->downloadDirEdit, settings().getDownloadDirectory(false)), - std::make_pair(ui->modDirEdit, settings().getModDirectory(false)), - std::make_pair(ui->cacheDirEdit, settings().getCacheDirectory(false)), - std::make_pair(ui->profilesDirEdit, settings().getProfileDirectory(false)), - std::make_pair(ui->overwriteDirEdit, settings().getOverwriteDirectory(false)) + std::make_pair(ui->downloadDirEdit, settings().paths().downloads(false)), + std::make_pair(ui->modDirEdit, settings().paths().mods(false)), + std::make_pair(ui->cacheDirEdit, settings().paths().cache(false)), + std::make_pair(ui->profilesDirEdit, settings().paths().profiles(false)), + std::make_pair(ui->overwriteDirEdit, settings().paths().overwrite(false)) }) { + QString storePath = baseDir.relativeFilePath(dir.second); storePath = dir.second; dir.first->setText(storePath); @@ -40,17 +46,17 @@ PathsSettingsTab::PathsSettingsTab(Settings& s, SettingsDialog& d) void PathsSettingsTab::update() { - using Setter = void (Settings::*)(const QString&); + using Setter = void (PathSettings::*)(const QString&); using Directory = std::tuple; - QString basePath = settings().getBaseDirectory(); + QString basePath = settings().paths().base(); for (const Directory &dir :{ - Directory{ui->downloadDirEdit->text(), &Settings::setDownloadDirectory, AppConfig::downloadPath()}, - Directory{ui->cacheDirEdit->text(), &Settings::setCacheDirectory, AppConfig::cachePath()}, - Directory{ui->modDirEdit->text(), &Settings::setModDirectory, AppConfig::modsPath()}, - Directory{ui->overwriteDirEdit->text(), &Settings::setOverwriteDirectory, AppConfig::overwritePath()}, - Directory{ui->profilesDirEdit->text(), &Settings::setProfileDirectory, AppConfig::profilesPath()} + Directory{ui->downloadDirEdit->text(), &PathSettings::setDownloads, AppConfig::downloadPath()}, + Directory{ui->cacheDirEdit->text(), &PathSettings::setCache, AppConfig::cachePath()}, + Directory{ui->modDirEdit->text(), &PathSettings::setMods, AppConfig::modsPath()}, + Directory{ui->overwriteDirEdit->text(), &PathSettings::setOverwrite, AppConfig::overwritePath()}, + Directory{ui->profilesDirEdit->text(), &PathSettings::setProfiles, AppConfig::profilesPath()} }) { QString path; Setter setter; @@ -70,22 +76,26 @@ void PathsSettingsTab::update() } if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - (settings().*setter)(path); + (settings().paths().*setter)(path); } else { - (settings().*setter)(""); + (settings().paths().*setter)(""); } } if (QFileInfo(ui->baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { - settings().setBaseDirectory(ui->baseDirEdit->text()); + settings().paths().setBase(ui->baseDirEdit->text()); } else { - settings().setBaseDirectory(""); + settings().paths().setBase(""); } - QFileInfo oldGameExe(settings().gamePlugin()->gameDirectory().absoluteFilePath(settings().gamePlugin()->binaryName())); + QFileInfo oldGameExe( + settings().game().plugin()->gameDirectory().absoluteFilePath( + settings().game().plugin()->binaryName())); + QFileInfo newGameExe(ui->managedGameDirEdit->text()); + if (oldGameExe != newGameExe) { - settings().setManagedGameDirectory(newGameExe.absolutePath()); + settings().game().setDirectory(newGameExe.absolutePath()); } } diff --git a/src/settingsdialogsteam.cpp b/src/settingsdialogsteam.cpp index 9ed93e47..3c4c5de6 100644 --- a/src/settingsdialogsteam.cpp +++ b/src/settingsdialogsteam.cpp @@ -5,7 +5,7 @@ SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { QString username, password; - settings().getSteamLogin(username, password); + settings().steam().login(username, password); ui->steamUserEdit->setText(username); ui->steamPassEdit->setText(password); @@ -13,5 +13,5 @@ SteamSettingsTab::SteamSettingsTab(Settings& s, SettingsDialog& d) void SteamSettingsTab::update() { - settings().setSteamLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); + settings().steam().setLogin(ui->steamUserEdit->text(), ui->steamPassEdit->text()); } diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index b06bd77c..4d811e40 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -6,12 +6,12 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - ui->appIDEdit->setText(settings().getSteamAppID()); + ui->appIDEdit->setText(settings().steam().appID()); - LoadMechanism::EMechanism mechanismID = settings().getLoadMechanism(); + LoadMechanism::EMechanism mechanismID = settings().game().loadMechanismType(); int index = 0; - if (settings().loadMechanism().isDirectLoadingSupported()) { + if (settings().game().loadMechanism().isDirectLoadingSupported()) { ui->mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { index = ui->mechanismBox->count() - 1; @@ -20,10 +20,10 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) ui->mechanismBox->setCurrentIndex(index); - ui->hideUncheckedBox->setChecked(settings().hideUncheckedPlugins()); - ui->forceEnableBox->setChecked(settings().forceEnableCoreFiles()); - ui->displayForeignBox->setChecked(settings().displayForeign()); - ui->lockGUIBox->setChecked(settings().lockGUI()); + ui->hideUncheckedBox->setChecked(settings().game().hideUncheckedPlugins()); + ui->forceEnableBox->setChecked(settings().game().forceEnableCoreFiles()); + ui->displayForeignBox->setChecked(settings().interface().displayForeign()); + ui->lockGUIBox->setChecked(settings().interface().lockGUI()); ui->enableArchiveParsingBox->setChecked(settings().archiveParsing()); setExecutableBlacklist(settings().executablesBlacklist()); @@ -35,19 +35,19 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) void WorkaroundsSettingsTab::update() { - if (ui->appIDEdit->text() != settings().gamePlugin()->steamAPPId()) { - settings().setSteamAppID(ui->appIDEdit->text()); + if (ui->appIDEdit->text() != settings().game().plugin()->steamAPPId()) { + settings().steam().setAppID(ui->appIDEdit->text()); } else { - settings().setSteamAppID(""); + settings().steam().setAppID(""); } - settings().setLoadMechanism(static_cast( + settings().game().setLoadMechanism(static_cast( ui->mechanismBox->itemData(ui->mechanismBox->currentIndex()).toInt())); - settings().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked()); - settings().setForceEnableCoreFiles(ui->forceEnableBox->isChecked()); - settings().setDisplayForeign(ui->displayForeignBox->isChecked()); - settings().setLockGUI(ui->lockGUIBox->isChecked()); + settings().game().setHideUncheckedPlugins(ui->hideUncheckedBox->isChecked()); + settings().game().setForceEnableCoreFiles(ui->forceEnableBox->isChecked()); + settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked()); + settings().interface().setLockGUI(ui->lockGUIBox->isChecked()); settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked()); settings().setExecutablesBlacklist(getExecutableBlacklist()); } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index d22010a5..3734aa87 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -129,7 +129,7 @@ void StatusBar::setUpdateAvailable(bool b) void StatusBar::checkSettings(const Settings& settings) { - m_api->setVisible(!settings.hideAPICounter()); + m_api->setVisible(!settings.interface().hideAPICounter()); } void StatusBar::showEvent(QShowEvent*) diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 41f58308..4315ed92 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -163,8 +163,8 @@ QString toString(CrashDumpsType t) UsvfsConnector::UsvfsConnector() { USVFSParameters params; - LogLevel level = toUsvfsLogLevel(Settings::instance().logLevel()); - CrashDumpsType dumpType = Settings::instance().crashDumpsType(); + LogLevel level = toUsvfsLogLevel(Settings::instance().diagnostics().logLevel()); + CrashDumpsType dumpType = Settings::instance().diagnostics().crashDumpsType(); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); -- cgit v1.3.1 From 3757aa3f532c943f8a47e997d0a4f250d9dacdd3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 15:07:35 -0400 Subject: split into settingsutilities --- src/CMakeLists.txt | 9 +- src/settings.cpp | 452 +--------------------------------------------- src/settings.h | 3 - src/settingsutilities.cpp | 247 +++++++++++++++++++++++++ src/settingsutilities.h | 266 +++++++++++++++++++++++++++ 5 files changed, 522 insertions(+), 455 deletions(-) create mode 100644 src/settingsutilities.cpp create mode 100644 src/settingsutilities.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d8316e7e..ea27184b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,6 +45,7 @@ SET(organizer_SRCS settingsdialogsteam.cpp settingsdialogworkarounds.cpp settings.cpp + settingsutilities.cpp selfupdater.cpp selectiondialog.cpp queryoverwritedialog.cpp @@ -166,6 +167,7 @@ SET(organizer_HDRS settingsdialogsteam.h settingsdialogworkarounds.h settings.h + settingsutilities.h selfupdater.h selectiondialog.h queryoverwritedialog.h @@ -444,6 +446,10 @@ set(profiles set(settings settings + settingsutilities +) + +set(settingsdialog settingsdialog settingsdialogdiagnostics settingsdialoggeneral @@ -490,7 +496,8 @@ set(widgets set(src_filters application core browser dialogs downloads env executables locking modinfo - modinfo\\dialog modlist plugins previews profiles settings utilities widgets + modinfo\\dialog modlist plugins previews profiles settings settingsdialog + utilities widgets ) foreach(filter in list ${src_filters}) diff --git a/src/settings.cpp b/src/settings.cpp index 8b063efb..14189be3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see . */ #include "settings.h" +#include "settingsutilities.h" #include "serverinfo.h" #include "executableslist.h" #include "appconfig.h" @@ -27,310 +28,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; -template -struct ValueConverter -{ - static const T& convert(const T& t) - { - return t; - } -}; - -template -struct ValueConverter>> -{ - static QString convert(const T& t) - { - return QString("%1").arg(static_cast>(t)); - } -}; - - -template -void logChange( - const QString& displayName, std::optional oldValue, const T& newValue) -{ - using VC = ValueConverter; - - if (oldValue) { - log::debug( - "setting '{}' changed from '{}' to '{}'", - displayName, VC::convert(*oldValue), VC::convert(newValue)); - } else { - log::debug( - "setting '{}' set to '{}'", - displayName, VC::convert(newValue)); - } -} - -void logRemoval(const QString& name) -{ - log::debug("setting '{}' removed", name); -} - - -QString settingName(const QString& section, const QString& key) -{ - if (section.isEmpty()) { - return key; - } else if (key.isEmpty()) { - return section; - } else { - if (section.compare("General", Qt::CaseInsensitive) == 0) { - return key; - } else { - return section + "/" + key; - } - } -} - -template -void setImpl( - QSettings& settings, const QString& displayName, - const QString& section, const QString& key, const T& value) -{ - const auto current = getOptional(settings, section, key); - - if (current && *current == value) { - // no change - return; - } - - const auto name = settingName(section, key); - - logChange(displayName, current, value); - - if constexpr (std::is_enum_v) { - settings.setValue( - name, static_cast>(value)); - } else { - settings.setValue(name, value); - } -} - -void removeImpl( - QSettings& settings, const QString& displayName, - const QString& section, const QString& key) -{ - if (key.isEmpty()) { - if (!settings.childGroups().contains(section, Qt::CaseInsensitive)) { - // not there - return; - } - } else { - if (!settings.contains(settingName(section, key))) { - // not there - return; - } - } - - logRemoval(displayName); - settings.remove(settingName(section, key)); -} - - -template -std::optional getOptional( - const QSettings& settings, - const QString& section, const QString& key, std::optional def={}) -{ - if (settings.contains(settingName(section, key))) { - const auto v = settings.value(settingName(section, key)); - - if constexpr (std::is_enum_v) { - return static_cast(v.value>()); - } else { - return v.value(); - } - } - - return def; -} - -template -T get( - const QSettings& settings, - const QString& section, const QString& key, T def) -{ - if (auto v=getOptional(settings, section, key)) { - return *v; - } else { - return def; - } -} - -template -void set( - QSettings& settings, - const QString& section, const QString& key, const T& value) -{ - setImpl(settings, settingName(section, key), section, key, value); -} - -void remove(QSettings& settings, const QString& section, const QString& key) -{ - removeImpl(settings, settingName(section, key), section, key); -} - -void removeSection(QSettings& settings, const QString& section) -{ - removeImpl(settings, section, section, ""); -} - - -class ScopedGroup -{ -public: - ScopedGroup(QSettings& s, const QString& name) - : m_settings(s), m_name(name) - { - m_settings.beginGroup(m_name); - } - - ~ScopedGroup() - { - m_settings.endGroup(); - } - - ScopedGroup(const ScopedGroup&) = delete; - ScopedGroup& operator=(const ScopedGroup&) = delete; - - template - void set(const QString& key, const T& value) - { - setImpl(m_settings, settingName(m_name, key), "", key, value); - } - - void remove(const QString& key) - { - removeImpl(m_settings, settingName(m_name, key), "", key); - } - - QStringList keys() const - { - return m_settings.childKeys(); - } - - template - void for_each(F&& f) const - { - for (const QString& key : keys()) { - f(key); - } - } - - template - std::optional getOptional(const QString& key, std::optional def={}) const - { - return ::getOptional(m_settings, "", key, def); - } - - template - T get(const QString& key, T def={}) const - { - return ::get(m_settings, "", key, def); - } - -private: - QSettings& m_settings; - QString m_name; -}; - - -class ScopedReadArray -{ -public: - ScopedReadArray(QSettings& s, const QString& section) - : m_settings(s), m_count(0) - { - m_count = m_settings.beginReadArray(section); - } - - ~ScopedReadArray() - { - m_settings.endArray(); - } - - ScopedReadArray(const ScopedReadArray&) = delete; - ScopedReadArray& operator=(const ScopedReadArray&) = delete; - - template - void for_each(F&& f) const - { - for (int i=0; i - std::optional getOptional(const QString& key, std::optional def={}) const - { - return ::getOptional(m_settings, "", key, def); - } - - template - T get(const QString& key, T def={}) const - { - return ::get(m_settings, "", key, def); - } - - int count() const - { - return m_count; - } - - QStringList keys() const - { - return m_settings.childKeys(); - } - -private: - QSettings& m_settings; - int m_count; -}; - - -class ScopedWriteArray -{ -public: - ScopedWriteArray(QSettings& s, const QString& section) - : m_settings(s), m_section(section), m_i(0) - { - m_settings.beginWriteArray(section); - } - - ~ScopedWriteArray() - { - m_settings.endArray(); - } - - ScopedWriteArray(const ScopedWriteArray&) = delete; - ScopedWriteArray& operator=(const ScopedWriteArray&) = delete; - - void next() - { - m_settings.setArrayIndex(m_i); - ++m_i; - } - - template - void set(const QString& key, const T& value) - { - const auto displayName = QString("%1/%2\\%3") - .arg(m_section) - .arg(m_i) - .arg(key); - - setImpl(m_settings, displayName, "", key, value); - } - -private: - QSettings& m_settings; - QString m_section; - int m_i; -}; - EndorsementState endorsementStateFromString(const QString& s) { @@ -360,153 +57,6 @@ QString toString(EndorsementState s) } -QString widgetNameWithTopLevel(const QWidget* widget) -{ - QStringList components; - - auto* tl = widget->window(); - - if (tl == widget) { - // this is a top level widget, such as a dialog - components.push_back(widget->objectName()); - } else { - // this is a widget - const auto toplevelName = tl->objectName(); - if (!toplevelName.isEmpty()) { - components.push_back(toplevelName); - } - - const auto widgetName = widget->objectName(); - if (!widgetName.isEmpty()) { - components.push_back(widgetName); - } - } - - if (components.isEmpty()) { - // can't do much - return "unknown_widget"; - } - - return components.join("_"); -} - -QString widgetName(const QMainWindow* w) -{ - return w->objectName(); -} - -QString widgetName(const QHeaderView* w) -{ - return widgetNameWithTopLevel(w->parentWidget()); -} - -QString widgetName(const ExpanderWidget* w) -{ - return widgetNameWithTopLevel(w->button()); -} - -QString widgetName(const QWidget* w) -{ - return widgetNameWithTopLevel(w); -} - -template -QString geoSettingName(const Widget* widget) -{ - return widgetName(widget) + "_geometry"; -} - -template -QString stateSettingName(const Widget* widget) -{ - return widgetName(widget) + "_state"; -} - -template -QString visibilitySettingName(const Widget* widget) -{ - return widgetName(widget) + "_visibility"; -} - -QString dockSettingName(const QDockWidget* dock) -{ - return "MainWindow_docks_" + dock->objectName() + "_size"; -} - -QString indexSettingName(const QWidget* widget) -{ - return widgetNameWithTopLevel(widget) + "_index"; -} - -QString checkedSettingName(const QAbstractButton* b) -{ - return widgetNameWithTopLevel(b) + "_checked"; -} - -void warnIfNotCheckable(const QAbstractButton* b) -{ - if (!b->isCheckable()) { - log::warn( - "button '{}' used in the settings as a checkbox or radio button " - "but is not checkable", b->objectName()); - } -} - - -bool setWindowsCredential(const QString key, const QString data) -{ - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - bool result = false; - if (data.isEmpty()) { - result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); - if (!result) - if (GetLastError() == ERROR_NOT_FOUND) - result = true; - } else { - wchar_t* charData = new wchar_t[data.size()]; - data.toWCharArray(charData); - - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = keyData; - cred.CredentialBlob = (LPBYTE)charData; - cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; - - result = CredWriteW(&cred, 0); - delete[] charData; - } - delete[] keyData; - return result; -} - -QString getWindowsCredential(const QString key) -{ - QString result; - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - PCREDENTIALW creds; - if (CredReadW(keyData, 1, 0, &creds)) { - wchar_t *charData = (wchar_t *)creds->CredentialBlob; - result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); - CredFree(creds); - } else { - const auto e = GetLastError(); - if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); - } - } - delete[] keyData; - return result; -} - - Settings *Settings::s_Instance = nullptr; Settings::Settings(const QString& path) : diff --git a/src/settings.h b/src/settings.h index 5c0a2542..68cfb9b7 100644 --- a/src/settings.h +++ b/src/settings.h @@ -36,8 +36,6 @@ namespace MOBase { class QSplitter; -class PluginContainer; -class ServerInfo; class ServerList; class Settings; class ExpanderWidget; @@ -505,7 +503,6 @@ private: }; - /** * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp new file mode 100644 index 00000000..7ac95b5f --- /dev/null +++ b/src/settingsutilities.cpp @@ -0,0 +1,247 @@ +#include "settingsutilities.h" +#include "expanderwidget.h" +#include + +using namespace MOBase; + +void logRemoval(const QString& name) +{ + log::debug("setting '{}' removed", name); +} + + +QString settingName(const QString& section, const QString& key) +{ + if (section.isEmpty()) { + return key; + } else if (key.isEmpty()) { + return section; + } else { + if (section.compare("General", Qt::CaseInsensitive) == 0) { + return key; + } else { + return section + "/" + key; + } + } +} + +void removeImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key) +{ + if (key.isEmpty()) { + if (!settings.childGroups().contains(section, Qt::CaseInsensitive)) { + // not there + return; + } + } else { + if (!settings.contains(settingName(section, key))) { + // not there + return; + } + } + + logRemoval(displayName); + settings.remove(settingName(section, key)); +} + +void remove(QSettings& settings, const QString& section, const QString& key) +{ + removeImpl(settings, settingName(section, key), section, key); +} + +void removeSection(QSettings& settings, const QString& section) +{ + removeImpl(settings, section, section, ""); +} + + +ScopedGroup::ScopedGroup(QSettings& s, const QString& name) + : m_settings(s), m_name(name) +{ + m_settings.beginGroup(m_name); +} + +ScopedGroup::~ScopedGroup() +{ + m_settings.endGroup(); +} + +void ScopedGroup::remove(const QString& key) +{ + removeImpl(m_settings, settingName(m_name, key), "", key); +} + +QStringList ScopedGroup::keys() const +{ + return m_settings.childKeys(); +} + + +ScopedReadArray::ScopedReadArray(QSettings& s, const QString& section) + : m_settings(s), m_count(0) +{ + m_count = m_settings.beginReadArray(section); +} + +ScopedReadArray::~ScopedReadArray() +{ + m_settings.endArray(); +} + +int ScopedReadArray::count() const +{ + return m_count; +} + +QStringList ScopedReadArray::keys() const +{ + return m_settings.childKeys(); +} + + +ScopedWriteArray::ScopedWriteArray(QSettings& s, const QString& section) + : m_settings(s), m_section(section), m_i(0) +{ + m_settings.beginWriteArray(section); +} + +ScopedWriteArray::~ScopedWriteArray() +{ + m_settings.endArray(); +} + +void ScopedWriteArray::next() +{ + m_settings.setArrayIndex(m_i); + ++m_i; +} + + +QString widgetNameWithTopLevel(const QWidget* widget) +{ + QStringList components; + + auto* tl = widget->window(); + + if (tl == widget) { + // this is a top level widget, such as a dialog + components.push_back(widget->objectName()); + } else { + // this is a widget + const auto toplevelName = tl->objectName(); + if (!toplevelName.isEmpty()) { + components.push_back(toplevelName); + } + + const auto widgetName = widget->objectName(); + if (!widgetName.isEmpty()) { + components.push_back(widgetName); + } + } + + if (components.isEmpty()) { + // can't do much + return "unknown_widget"; + } + + return components.join("_"); +} + +QString widgetName(const QMainWindow* w) +{ + return w->objectName(); +} + +QString widgetName(const QHeaderView* w) +{ + return widgetNameWithTopLevel(w->parentWidget()); +} + +QString widgetName(const ExpanderWidget* w) +{ + return widgetNameWithTopLevel(w->button()); +} + +QString widgetName(const QWidget* w) +{ + return widgetNameWithTopLevel(w); +} + +QString dockSettingName(const QDockWidget* dock) +{ + return "MainWindow_docks_" + dock->objectName() + "_size"; +} + +QString indexSettingName(const QWidget* widget) +{ + return widgetNameWithTopLevel(widget) + "_index"; +} + +QString checkedSettingName(const QAbstractButton* b) +{ + return widgetNameWithTopLevel(b) + "_checked"; +} + +void warnIfNotCheckable(const QAbstractButton* b) +{ + if (!b->isCheckable()) { + log::warn( + "button '{}' used in the settings as a checkbox or radio button " + "but is not checkable", b->objectName()); + } +} + + +bool setWindowsCredential(const QString key, const QString data) +{ + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + bool result = false; + if (data.isEmpty()) { + result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); + if (!result) + if (GetLastError() == ERROR_NOT_FOUND) + result = true; + } else { + wchar_t* charData = new wchar_t[data.size()]; + data.toWCharArray(charData); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = keyData; + cred.CredentialBlob = (LPBYTE)charData; + cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + result = CredWriteW(&cred, 0); + delete[] charData; + } + delete[] keyData; + return result; +} + +QString getWindowsCredential(const QString key) +{ + QString result; + QString finalKey("ModOrganizer2_" + key); + wchar_t* keyData = new wchar_t[finalKey.size()+1]; + finalKey.toWCharArray(keyData); + keyData[finalKey.size()] = L'\0'; + PCREDENTIALW creds; + if (CredReadW(keyData, 1, 0, &creds)) { + wchar_t *charData = (wchar_t *)creds->CredentialBlob; + result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); + CredFree(creds); + } else { + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { + log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + } + } + delete[] keyData; + return result; +} diff --git a/src/settingsutilities.h b/src/settingsutilities.h new file mode 100644 index 00000000..b70e55ef --- /dev/null +++ b/src/settingsutilities.h @@ -0,0 +1,266 @@ +#ifndef SETTINGSUTILITIES_H +#define SETTINGSUTILITIES_H + +#include + +class ExpanderWidget; + +template +struct ValueConverter +{ + static const T& convert(const T& t) + { + return t; + } +}; + +template +struct ValueConverter>> +{ + static QString convert(const T& t) + { + return QString("%1").arg(static_cast>(t)); + } +}; + + +template +void logChange( + const QString& displayName, std::optional oldValue, const T& newValue) +{ + using VC = ValueConverter; + + if (oldValue) { + log::debug( + "setting '{}' changed from '{}' to '{}'", + displayName, VC::convert(*oldValue), VC::convert(newValue)); + } else { + log::debug( + "setting '{}' set to '{}'", + displayName, VC::convert(newValue)); + } +} + +void logRemoval(const QString& name); + + +QString settingName(const QString& section, const QString& key); + +template +void setImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key, const T& value) +{ + const auto current = getOptional(settings, section, key); + + if (current && *current == value) { + // no change + return; + } + + const auto name = settingName(section, key); + + logChange(displayName, current, value); + + if constexpr (std::is_enum_v) { + settings.setValue( + name, static_cast>(value)); + } else { + settings.setValue(name, value); + } +} + +void removeImpl( + QSettings& settings, const QString& displayName, + const QString& section, const QString& key); + + +template +std::optional getOptional( + const QSettings& settings, + const QString& section, const QString& key, std::optional def={}) +{ + if (settings.contains(settingName(section, key))) { + const auto v = settings.value(settingName(section, key)); + + if constexpr (std::is_enum_v) { + return static_cast(v.value>()); + } else { + return v.value(); + } + } + + return def; +} + +template +T get( + const QSettings& settings, + const QString& section, const QString& key, T def) +{ + if (auto v=getOptional(settings, section, key)) { + return *v; + } else { + return def; + } +} + +template +void set( + QSettings& settings, + const QString& section, const QString& key, const T& value) +{ + setImpl(settings, settingName(section, key), section, key, value); +} + +void remove(QSettings& settings, const QString& section, const QString& key); +void removeSection(QSettings& settings, const QString& section); + + +class ScopedGroup +{ +public: + ScopedGroup(QSettings& s, const QString& name); + ~ScopedGroup(); + + ScopedGroup(const ScopedGroup&) = delete; + ScopedGroup& operator=(const ScopedGroup&) = delete; + + template + void set(const QString& key, const T& value) + { + setImpl(m_settings, settingName(m_name, key), "", key, value); + } + + void remove(const QString& key); + + QStringList keys() const; + + template + void for_each(F&& f) const + { + for (const QString& key : keys()) { + f(key); + } + } + + template + std::optional getOptional(const QString& key, std::optional def={}) const + { + return ::getOptional(m_settings, "", key, def); + } + + template + T get(const QString& key, T def={}) const + { + return ::get(m_settings, "", key, def); + } + +private: + QSettings& m_settings; + QString m_name; +}; + + +class ScopedReadArray +{ +public: + ScopedReadArray(QSettings& s, const QString& section); + ~ScopedReadArray(); + + ScopedReadArray(const ScopedReadArray&) = delete; + ScopedReadArray& operator=(const ScopedReadArray&) = delete; + + template + void for_each(F&& f) const + { + for (int i=0; i + std::optional getOptional(const QString& key, std::optional def={}) const + { + return ::getOptional(m_settings, "", key, def); + } + + template + T get(const QString& key, T def={}) const + { + return ::get(m_settings, "", key, def); + } + + int count() const; + QStringList keys() const; + +private: + QSettings& m_settings; + int m_count; +}; + + +class ScopedWriteArray +{ +public: + ScopedWriteArray(QSettings& s, const QString& section); + ~ScopedWriteArray(); + + ScopedWriteArray(const ScopedWriteArray&) = delete; + ScopedWriteArray& operator=(const ScopedWriteArray&) = delete; + + void next(); + + template + void set(const QString& key, const T& value) + { + const auto displayName = QString("%1/%2\\%3") + .arg(m_section) + .arg(m_i) + .arg(key); + + setImpl(m_settings, displayName, "", key, value); + } + +private: + QSettings& m_settings; + QString m_section; + int m_i; +}; + + +QString widgetNameWithTopLevel(const QWidget* widget); +QString widgetName(const QMainWindow* w); +QString widgetName(const QHeaderView* w); +QString widgetName(const ExpanderWidget* w); +QString widgetName(const QWidget* w); + +template +QString geoSettingName(const Widget* widget) +{ + return widgetName(widget) + "_geometry"; +} + +template +QString stateSettingName(const Widget* widget) +{ + return widgetName(widget) + "_state"; +} + +template +QString visibilitySettingName(const Widget* widget) +{ + return widgetName(widget) + "_visibility"; +} + +QString dockSettingName(const QDockWidget* dock); +QString indexSettingName(const QWidget* widget); +QString checkedSettingName(const QAbstractButton* b); + +void warnIfNotCheckable(const QAbstractButton* b); + +bool setWindowsCredential(const QString key, const QString data); +QString getWindowsCredential(const QString key); + +#endif // SETTINGSUTILITIES_H -- cgit v1.3.1 From 209c27c7a27e2f6cb34f122a929c15eb3d1e60b7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 15:47:21 -0400 Subject: only remove section when the array is larger, prevents logging changes when nothing actually changed changed back section names that were originally lowercase, arrays end up in two different sections --- src/settings.cpp | 69 ++++++++++++++++++++++++++++++----------------- src/settingsutilities.cpp | 8 +++--- src/settingsutilities.h | 4 ++- 3 files changed, 52 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 14189be3..8ae7c343 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -221,9 +221,19 @@ std::vector> Settings::executables() const void Settings::setExecutables(const std::vector>& v) { - removeSection(m_Settings, "customExecutables"); + const auto current = executables(); - ScopedWriteArray swa(m_Settings, "customExecutables"); + if (current == v) { + // no change + return; + } + + if (current.size() > v.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "customExecutables"); + } + + ScopedWriteArray swa(m_Settings, "customExecutables", v.size()); for (const auto& map : v) { swa.next(); @@ -1156,9 +1166,9 @@ void PluginSettings::addBlacklistPlugin(const QString &fileName) void PluginSettings::writePluginBlacklist() { - removeSection(m_Settings, "PluginBlacklist"); + removeSection(m_Settings, "pluginBlacklist"); - ScopedWriteArray swa(m_Settings, "PluginBlacklist"); + ScopedWriteArray swa(m_Settings, "pluginBlacklist"); for (const QString &plugin : m_PluginBlacklist) { swa.next(); @@ -1222,7 +1232,7 @@ std::map PathSettings::recent() const { std::map map; - ScopedReadArray sra(m_Settings, "RecentDirectories"); + ScopedReadArray sra(m_Settings, "recentDirectories"); sra.for_each([&] { const QVariant name = sra.get("name"); @@ -1238,9 +1248,14 @@ std::map PathSettings::recent() const void PathSettings::setRecent(const std::map& map) { - removeSection(m_Settings, "RecentDirectories"); + const auto current = recent(); - ScopedWriteArray swa(m_Settings, "recentDirectories"); + if (current.size() > map.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "recentDirectories"); + } + + ScopedWriteArray swa(m_Settings, "recentDirectories", map.size()); for (auto&& p : map) { swa.next(); @@ -1472,33 +1487,37 @@ ServerList NetworkSettings::serversFromOldMap() const return list; } -void NetworkSettings::updateServers(ServerList servers) +void NetworkSettings::updateServers(ServerList newServers) { // clean up unavailable servers - servers.cleanup(); + newServers.cleanup(); - removeSection(m_Settings, "Servers"); + const auto current = servers(); - { - ScopedWriteArray swa(m_Settings, "Servers"); + if (current.size() > newServers.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "Servers"); + } - for (const auto& server : servers) { - swa.next(); - swa.set("name", server.name()); - swa.set("premium", server.isPremium()); - swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); - swa.set("preferred", server.preferred()); + ScopedWriteArray swa(m_Settings, "Servers", newServers.size()); - QString lastDownloads; - for (const auto& speed : server.lastDownloads()) { - if (speed > 0) { - lastDownloads += QString("%1 ").arg(speed); - } - } + for (const auto& server : newServers) { + swa.next(); - swa.set("lastDownloads", lastDownloads.trimmed()); + swa.set("name", server.name()); + swa.set("premium", server.isPremium()); + swa.set("lastSeen", server.lastSeen().toString(Qt::ISODate)); + swa.set("preferred", server.preferred()); + + QString lastDownloads; + for (const auto& speed : server.lastDownloads()) { + if (speed > 0) { + lastDownloads += QString("%1 ").arg(speed); + } } + + swa.set("lastDownloads", lastDownloads.trimmed()); } } diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 7ac95b5f..d5e2dd9a 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -100,10 +100,12 @@ QStringList ScopedReadArray::keys() const } -ScopedWriteArray::ScopedWriteArray(QSettings& s, const QString& section) - : m_settings(s), m_section(section), m_i(0) +ScopedWriteArray::ScopedWriteArray( + QSettings& s, const QString& section, std::size_t size) + : m_settings(s), m_section(section), m_i(0) { - m_settings.beginWriteArray(section); + m_settings.beginWriteArray( + section, size == NoSize ? -1 : static_cast(size)); } ScopedWriteArray::~ScopedWriteArray() diff --git a/src/settingsutilities.h b/src/settingsutilities.h index b70e55ef..ca754759 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -204,7 +204,9 @@ private: class ScopedWriteArray { public: - ScopedWriteArray(QSettings& s, const QString& section); + static const auto NoSize = std::numeric_limits::max(); + + ScopedWriteArray(QSettings& s, const QString& section, std::size_t size=NoSize); ~ScopedWriteArray(); ScopedWriteArray(const ScopedWriteArray&) = delete; -- cgit v1.3.1 From 7f0fa1069f07d90a92be7073b11bab86bac7b2d2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Sep 2019 16:09:31 -0400 Subject: don't log widget and geometry setting changes fixed mod info dialog tab order using different settings for read and write mod info dialog now doesn't complain when no tab order exists in the settings only remove section when the array is larger, prevents logging changes when nothing actually changed --- src/modinfodialog.cpp | 9 ++++++--- src/settings.cpp | 27 ++++++++++++++++++++------- src/settings.h | 2 +- src/settingsutilities.cpp | 18 ++++++++++++++++++ src/settingsutilities.h | 6 ++++++ 5 files changed, 51 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2178ef34..c7e071ad 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -383,9 +383,12 @@ void ModInfoDialog::reAddTabs( // ordered tab names from settings const auto orderedNames = m_core->settings().geometry().modInfoTabOrder(); - // whether the tabs can be sorted; if the object name of a tab widget is not - // found in orderedNames, the list cannot be sorted safely - bool canSort = true; + // whether the tabs can be sorted + // + // if the object name of a tab widget is not found in orderedNames, the list + // cannot be sorted safely; if the list is empty, it's probably a first run + // and there's nothing to sort + bool canSort = !orderedNames.empty(); // gathering visible tabs std::vector visibleTabs; diff --git a/src/settings.cpp b/src/settings.cpp index 8ae7c343..a33005b6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -210,7 +210,7 @@ std::vector> Settings::executables() const std::map map; for (auto&& key : sra.keys()) { - map[key] = m_Settings.value(key); + map[key] = sra.get(key); } v.push_back(map); @@ -693,7 +693,7 @@ QStringList GeometrySettings::modInfoTabOrder() const } } else { // string list since 2.2.1 - QString string = m_Settings.value("mod_info_tab_order").toString(); + QString string = get(m_Settings, "Widgets", "ModInfoTabOrder", ""); QTextStream stream(&string); while (!stream.atEnd()) { @@ -708,7 +708,7 @@ QStringList GeometrySettings::modInfoTabOrder() const void GeometrySettings::setModInfoTabOrder(const QString& names) { - set(m_Settings, "Geometry", "mod_info_tab_order", names); + set(m_Settings, "Widgets", "ModInfoTabOrder", names); } void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) @@ -1061,13 +1061,21 @@ void PluginSettings::clearPlugins() { m_Plugins.clear(); m_PluginSettings.clear(); - m_PluginBlacklist.clear(); + m_PluginBlacklist = readPluginBlacklist(); +} + +QSet PluginSettings::readPluginBlacklist() const +{ + QSet set; + ScopedReadArray sra(m_Settings, "pluginBlacklist"); sra.for_each([&]{ - m_PluginBlacklist.insert(sra.get("name")); + set.insert(sra.get("name")); }); + + return set; } void PluginSettings::registerPlugin(IPlugin *plugin) @@ -1166,9 +1174,14 @@ void PluginSettings::addBlacklistPlugin(const QString &fileName) void PluginSettings::writePluginBlacklist() { - removeSection(m_Settings, "pluginBlacklist"); + const auto current = readPluginBlacklist(); + + if (current.size() > m_PluginBlacklist.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "pluginBlacklist"); + } - ScopedWriteArray swa(m_Settings, "pluginBlacklist"); + ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size()); for (const QString &plugin : m_PluginBlacklist) { swa.next(); diff --git a/src/settings.h b/src/settings.h index 68cfb9b7..2ff8da1c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -261,8 +261,8 @@ private: QMap m_PluginDescriptions; QSet m_PluginBlacklist; - void readPluginBlacklist(); void writePluginBlacklist(); + QSet readPluginBlacklist() const; }; diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index d5e2dd9a..7a9dcc35 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -4,8 +4,26 @@ using namespace MOBase; +bool shouldLogSetting(const QString& displayName) +{ + // don't log Geometry/ and Widgets/, too noisy and not very useful + static const QStringList ignorePrefixes = {"Geometry/", "Widgets/"}; + + for (auto&& prefix : ignorePrefixes) { + if (displayName.startsWith(prefix, Qt::CaseInsensitive)) { + return false; + } + } + + return true; +} + void logRemoval(const QString& name) { + if (!shouldLogSetting(name)) { + return; + } + log::debug("setting '{}' removed", name); } diff --git a/src/settingsutilities.h b/src/settingsutilities.h index ca754759..d99abb06 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -24,10 +24,16 @@ struct ValueConverter>> }; +bool shouldLogSetting(const QString& displayName); + template void logChange( const QString& displayName, std::optional oldValue, const T& newValue) { + if (!shouldLogSetting(displayName)) { + return; + } + using VC = ValueConverter; if (oldValue) { -- cgit v1.3.1 From 2a2af36a380c83043ff57ea312e7705bb77e6971 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 00:27:45 -0400 Subject: documentation for settings renamed some PluginSettings members and moved them around --- src/organizercore.cpp | 10 +-- src/plugincontainer.cpp | 4 +- src/settings.cpp | 130 +++++++++++++++--------------- src/settings.h | 181 +++++++++++++++++++++++++++++++++--------- src/settingsdialogplugins.cpp | 10 +-- 5 files changed, 222 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1a89641d..91e16716 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -866,26 +866,26 @@ void OrganizerCore::modDataChanged(MOBase::IModInterface *) QVariant OrganizerCore::pluginSetting(const QString &pluginName, const QString &key) const { - return m_Settings.plugins().pluginSetting(pluginName, key); + return m_Settings.plugins().setting(pluginName, key); } void OrganizerCore::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - m_Settings.plugins().setPluginSetting(pluginName, key, value); + m_Settings.plugins().setSetting(pluginName, key, value); } QVariant OrganizerCore::persistent(const QString &pluginName, const QString &key, const QVariant &def) const { - return m_Settings.plugins().pluginPersistent(pluginName, key, def); + return m_Settings.plugins().persistent(pluginName, key, def); } void OrganizerCore::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - m_Settings.plugins().setPluginPersistent(pluginName, key, value, sync); + m_Settings.plugins().setPersistent(pluginName, key, value, sync); } QString OrganizerCore::pluginDataPath() const @@ -2580,7 +2580,7 @@ void OrganizerCore::prepareStart() m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); - m_Settings.game().setupLoadMechanism(); + m_Settings.game().loadMechanism().activate(m_Settings.game().loadMechanismType()); storeSettings(); } diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 16a77387..c0706ba8 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -266,7 +266,7 @@ void PluginContainer::loadPlugins() "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " "The plugin may be able to recover from the problem)").arg(fileName), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Organizer->settings().plugins().addBlacklistPlugin(fileName); + m_Organizer->settings().plugins().addBlacklist(fileName); } loadCheck.close(); } @@ -279,7 +279,7 @@ void PluginContainer::loadPlugins() while (iter.hasNext()) { iter.next(); - if (m_Organizer->settings().plugins().pluginBlacklisted(iter.fileName())) { + if (m_Organizer->settings().plugins().blacklisted(iter.fileName())) { log::debug("plugin \"{}\" blacklisted", iter.fileName()); continue; } diff --git a/src/settings.cpp b/src/settings.cpp index a33005b6..ce9676ea 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -512,9 +512,9 @@ const LoadMechanism& GameSettings::loadMechanism() const return m_LoadMechanism; } -void GameSettings::setupLoadMechanism() +LoadMechanism& GameSettings::loadMechanism() { - m_LoadMechanism.activate(loadMechanismType()); + return m_LoadMechanism; } bool GameSettings::hideUncheckedPlugins() const @@ -1063,19 +1063,7 @@ void PluginSettings::clearPlugins() m_PluginSettings.clear(); m_PluginBlacklist.clear(); - m_PluginBlacklist = readPluginBlacklist(); -} - -QSet PluginSettings::readPluginBlacklist() const -{ - QSet set; - - ScopedReadArray sra(m_Settings, "pluginBlacklist"); - sra.for_each([&]{ - set.insert(sra.get("name")); - }); - - return set; + m_PluginBlacklist = readBlacklist(); } void PluginSettings::registerPlugin(IPlugin *plugin) @@ -1106,12 +1094,12 @@ void PluginSettings::registerPlugin(IPlugin *plugin) } } -bool PluginSettings::pluginBlacklisted(const QString &fileName) const +std::vector PluginSettings::plugins() const { - return m_PluginBlacklist.contains(fileName); + return m_Plugins; } -QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString &key) const +QVariant PluginSettings::setting(const QString &pluginName, const QString &key) const { auto iterPlugin = m_PluginSettings.find(pluginName); if (iterPlugin == m_PluginSettings.end()) { @@ -1126,7 +1114,7 @@ QVariant PluginSettings::pluginSetting(const QString &pluginName, const QString return *iterSetting; } -void PluginSettings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +void PluginSettings::setSetting(const QString &pluginName, const QString &key, const QVariant &value) { auto iterPlugin = m_PluginSettings.find(pluginName); @@ -1141,7 +1129,27 @@ void PluginSettings::setPluginSetting(const QString &pluginName, const QString & set(m_Settings, "Plugins", pluginName + "/" + key, value); } -QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +QVariantMap PluginSettings::settings(const QString &pluginName) const +{ + return m_PluginSettings[pluginName]; +} + +void PluginSettings::setSettings(const QString &pluginName, const QVariantMap& map) +{ + m_PluginSettings[pluginName] = map; +} + +QVariantMap PluginSettings::descriptions(const QString &pluginName) const +{ + return m_PluginDescriptions[pluginName]; +} + +void PluginSettings::setDescriptions(const QString &pluginName, const QVariantMap& map) +{ + m_PluginDescriptions[pluginName] = map; +} + +QVariant PluginSettings::persistent(const QString &pluginName, const QString &key, const QVariant &def) const { if (!m_PluginSettings.contains(pluginName)) { return def; @@ -1150,7 +1158,7 @@ QVariant PluginSettings::pluginPersistent(const QString &pluginName, const QStri return get(m_Settings, "PluginPersistance", pluginName + "/" + key, def); } -void PluginSettings::setPluginPersistent( +void PluginSettings::setPersistent( const QString &pluginName, const QString &key, const QVariant &value, bool sync) { if (!m_PluginSettings.contains(pluginName)) { @@ -1165,74 +1173,70 @@ void PluginSettings::setPluginPersistent( m_Settings.sync(); } } - -void PluginSettings::addBlacklistPlugin(const QString &fileName) +void PluginSettings::addBlacklist(const QString &fileName) { m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); + writeBlacklist(); } -void PluginSettings::writePluginBlacklist() +bool PluginSettings::blacklisted(const QString &fileName) const { - const auto current = readPluginBlacklist(); - - if (current.size() > m_PluginBlacklist.size()) { - // Qt can't remove array elements, the section must be cleared - removeSection(m_Settings, "pluginBlacklist"); - } + return m_PluginBlacklist.contains(fileName); +} - ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size()); +void PluginSettings::setBlacklist(const QStringList& pluginNames) +{ + m_PluginBlacklist.clear(); - for (const QString &plugin : m_PluginBlacklist) { - swa.next(); - swa.set("name", plugin); + for (const auto& name : pluginNames) { + m_PluginBlacklist.insert(name); } } -QVariantMap PluginSettings::pluginSettings(const QString &pluginName) const +const QSet& PluginSettings::blacklist() const { - return m_PluginSettings[pluginName]; + return m_PluginBlacklist; } -void PluginSettings::setPluginSettings(const QString &pluginName, const QVariantMap& map) +void PluginSettings::save() { - m_PluginSettings[pluginName] = map; -} + for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { + const auto key = iterPlugins.key() + "/" + iterSettings.key(); + set(m_Settings, "Plugins", key, iterSettings.value()); + } + } -QVariantMap PluginSettings::pluginDescriptions(const QString &pluginName) const -{ - return m_PluginDescriptions[pluginName]; + writeBlacklist(); } -void PluginSettings::pluginDescriptions(const QString &pluginName, const QVariantMap& map) +void PluginSettings::writeBlacklist() { - m_PluginDescriptions[pluginName] = map; -} + const auto current = readBlacklist(); -const QSet& PluginSettings::pluginBlacklist() const -{ - return m_PluginBlacklist; -} + if (current.size() > m_PluginBlacklist.size()) { + // Qt can't remove array elements, the section must be cleared + removeSection(m_Settings, "pluginBlacklist"); + } -void PluginSettings::setPluginBlacklist(const QStringList& pluginNames) -{ - m_PluginBlacklist.clear(); + ScopedWriteArray swa(m_Settings, "pluginBlacklist", m_PluginBlacklist.size()); - for (const auto& name : pluginNames) { - m_PluginBlacklist.insert(name); + for (const QString &plugin : m_PluginBlacklist) { + swa.next(); + swa.set("name", plugin); } } -void PluginSettings::save() +QSet PluginSettings::readBlacklist() const { - for (auto iterPlugins=m_PluginSettings.begin(); iterPlugins!=m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings=iterPlugins->begin(); iterSettings!=iterPlugins->end(); ++iterSettings) { - const auto key = iterPlugins.key() + "/" + iterSettings.key(); - set(m_Settings, "Plugins", key, iterSettings.value()); - } - } + QSet set; - writePluginBlacklist(); + ScopedReadArray sra(m_Settings, "pluginBlacklist"); + sra.for_each([&]{ + set.insert(sra.get("name")); + }); + + return set; } diff --git a/src/settings.h b/src/settings.h index 2ff8da1c..d3926d72 100644 --- a/src/settings.h +++ b/src/settings.h @@ -40,6 +40,10 @@ class ServerList; class Settings; class ExpanderWidget; + +// helper class that calls restoreGeometry() in the constructor and +// saveGeometry() in the destructor +// class GeometrySaver { public: @@ -52,48 +56,57 @@ private: }; +// setting for the currently managed game +// class GameSettings { public: GameSettings(QSettings& setting); + // game plugin + // const MOBase::IPluginGame* plugin(); void setPlugin(const MOBase::IPluginGame* gamePlugin); - /** - * whether files of the core game are forced-enabled so the user can't - * accidentally disable them - */ + // whether files of the core game are forced-enabled so the user can't + // accidentally disable them + // bool forceEnableCoreFiles() const; void setForceEnableCoreFiles(bool b); - /** - * the directory where the managed game is stored (with native separators) - **/ + // the directory where the managed game is stored + // std::optional directory() const; void setDirectory(const QString& path); + // the name of the managed game + // std::optional name() const; void setName(const QString& name); + // the edition of the managed game + // std::optional edition() const; void setEdition(const QString& name); + // the current profile name + // std::optional selectedProfileName() const; void setSelectedProfileName(const QString& name); - /** - * @return the load mechanism to be used - **/ + // load mechanism type + // LoadMechanism::EMechanism loadMechanismType() const; void setLoadMechanism(LoadMechanism::EMechanism m); + + // load mechanism object + // const LoadMechanism& loadMechanism() const; - void setupLoadMechanism(); + LoadMechanism& loadMechanism(); - /** - * @return true if the user wants unchecked plugins (esp, esm) should be hidden from - * the virtual data directory - **/ + // whether the user wants unchecked plugins (esp, esm) to be hidden from + // the virtual data directory + // bool hideUncheckedPlugins() const; void setHideUncheckedPlugins(bool b); @@ -104,11 +117,26 @@ private: }; +// geometry settings for various widgets; this should contain any setting that +// can get invalid through UI changes or when users change display settings +// (resolution, monitors, etc.); see WidgetSettings for the counterpart +// +// all these settings are stored under [Geometry] and get wiped when the +// "reset geometry settings" button is clicked in the settings +// +// saveGeometry(), restoreGeometry(), saveState() and restoreState() call the +// same functions on the given widget +// class GeometrySettings { public: GeometrySettings(QSettings& s); + // asks the settings to get reset + // + // this gets called from the settings dialog and gets picked up in + // resetIfNeeded(), called from runApplication() just before exiting + // void requestReset(); void resetIfNeeded(); @@ -137,10 +165,18 @@ public: void saveDocks(const QMainWindow* w); void restoreDocks(QMainWindow* w) const; + // this should be a generic "tab order" setting, but it only happens for the + // mod info dialog right now + // QStringList modInfoTabOrder() const; void setModInfoTabOrder(const QString& names); + // assumes the given widget is a top-level + // void centerOnMainWindowMonitor(QWidget* w); + + // saves the monitor number of the given window + // void saveMainWindowMonitor(const QMainWindow* w); private: @@ -149,33 +185,52 @@ private: }; +// widget settings that should stay valid regardless of UI changes or when users +// change display settings (resolution, monitors, etc.); see GeometrySettings +// for the counterpart +// class WidgetSettings { public: WidgetSettings(QSettings& s); + // selected index for a combobox + // std::optional index(const QComboBox* cb) const; void saveIndex(const QComboBox* cb); void restoreIndex(QComboBox* cb, std::optional def={}) const; + // selected tab index for a tab widget + // std::optional index(const QTabWidget* w) const; void saveIndex(const QTabWidget* w); void restoreIndex(QTabWidget* w, std::optional def={}) const; + // check state for a checkable button + // std::optional checked(const QAbstractButton* w) const; void saveChecked(const QAbstractButton* w); void restoreChecked(QAbstractButton* w, std::optional def={}) const; + // returns the remembered button for a question dialog, or NoButton if the + // user hasn't saved the choice + // MOBase::QuestionBoxMemory::Button questionButton( const QString& windowName, const QString& filename) const; + // sets the button to be remembered for the given window + // void setQuestionWindowButton( const QString& windowName, MOBase::QuestionBoxMemory::Button button); + // sets the button to be remembered for the given file + // void setQuestionFileButton( const QString& windowName, const QString& filename, MOBase::QuestionBoxMemory::Button choice); + // wipes all the remembered buttons + // void resetQuestionButtons(); private: @@ -183,13 +238,13 @@ private: }; +// various color settings +// class ColorSettings { public: ColorSettings(QSettings& s); - void setCrashDumpsMax(int i) const; - QColor modlistOverwrittenLoose() const; void setModlistOverwrittenLoose(const QColor& c); @@ -212,46 +267,91 @@ public: void setPreviousSeparatorColor(const QColor& c) const; void removePreviousSeparatorColor(); - /** - * @brief color the scrollbar of the mod list for custom separator colors? - * @return the state of the setting - */ + // whether the scrollbar of the mod list should have colors for custom + // separator colors + // bool colorSeparatorScrollbar() const; void setColorSeparatorScrollbar(bool b); - static QColor idealTextColor(const QColor& rBackgroundColor); + // returns a color with a good contrast for the given background + // + static QColor idealTextColor(const QColor& rBackgroundColor); private: QSettings& m_Settings; }; +// settings about plugins +// class PluginSettings { public: PluginSettings(QSettings& settings); + + // forgets all the plugins + // void clearPlugins(); + + // adds the given plugin to the list and loads all of its settings + // void registerPlugin(MOBase::IPlugin *plugin); - void addPluginSettings(const std::vector &plugins); - QVariant pluginSetting(const QString &pluginName, const QString &key) const; - void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); - QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; - void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); - void addBlacklistPlugin(const QString &fileName); - bool pluginBlacklisted(const QString &fileName) const; - void setPluginBlacklist(const QStringList& pluginNames); - std::vector plugins() const { return m_Plugins; } + // returns all the registered plugins + // + std::vector plugins() const; + + + // returns the plugin setting for the given key + // + QVariant setting(const QString &pluginName, const QString &key) const; + + // sets the plugin setting for the given key + // + void setSetting(const QString &pluginName, const QString &key, const QVariant &value); + + // returns all settings + // + QVariantMap settings(const QString &pluginName) const; + + // overwrites all settings + // + void setSettings(const QString &pluginName, const QVariantMap& map); + + // returns all descriptions + // + QVariantMap descriptions(const QString &pluginName) const; - QVariantMap pluginSettings(const QString &pluginName) const; - void setPluginSettings(const QString &pluginName, const QVariantMap& map); + // overwrites all descriptions + // + void setDescriptions(const QString &pluginName, const QVariantMap& map); - QVariantMap pluginDescriptions(const QString &pluginName) const; - void pluginDescriptions(const QString &pluginName, const QVariantMap& map); - const QSet& pluginBlacklist() const; + // ? + QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def) const; + void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); + + // adds the given plugin to the blacklist + // + void addBlacklist(const QString &fileName); + + // returns whether the given plugin is blacklisted + // + bool blacklisted(const QString &fileName) const; + + // overwrites the whole blacklist + // + void setBlacklist(const QStringList& pluginNames); + + // returns the blacklist + // + const QSet& blacklist() const; + + + // commits all the settings to the ini + // void save(); private: @@ -261,8 +361,13 @@ private: QMap m_PluginDescriptions; QSet m_PluginBlacklist; - void writePluginBlacklist(); - QSet readPluginBlacklist() const; + // commits the blacklist to the ini + // + void writeBlacklist(); + + // reads the blacklist from the ini + // + QSet readBlacklist() const; }; diff --git a/src/settingsdialogplugins.cpp b/src/settingsdialogplugins.cpp index 956971fe..c84d0556 100644 --- a/src/settingsdialogplugins.cpp +++ b/src/settingsdialogplugins.cpp @@ -17,14 +17,14 @@ PluginsSettingsTab::PluginsSettingsTab(Settings& s, SettingsDialog& d) continue; QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), ui->pluginsList); listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, settings().plugins().pluginSettings(plugin->name())); - listItem->setData(Qt::UserRole + 2, settings().plugins().pluginDescriptions(plugin->name())); + listItem->setData(Qt::UserRole + 1, settings().plugins().settings(plugin->name())); + listItem->setData(Qt::UserRole + 2, settings().plugins().descriptions(plugin->name())); ui->pluginsList->addItem(listItem); handledNames.insert(plugin->name()); } // display plugin blacklist - for (const QString &pluginName : settings().plugins().pluginBlacklist()) { + for (const QString &pluginName : settings().plugins().blacklist()) { ui->pluginBlacklist->addItem(pluginName); } @@ -42,7 +42,7 @@ void PluginsSettingsTab::update() // transfer plugin settings to in-memory structure for (int i = 0; i < ui->pluginsList->count(); ++i) { QListWidgetItem *item = ui->pluginsList->item(i); - settings().plugins().setPluginSettings( + settings().plugins().setSettings( item->text(), item->data(Qt::UserRole + 1).toMap()); } @@ -52,7 +52,7 @@ void PluginsSettingsTab::update() names.push_back(item->text()); } - settings().plugins().setPluginBlacklist(names); + settings().plugins().setBlacklist(names); settings().plugins().save(); } -- cgit v1.3.1 From c9397c3909cf3b2aa6c2ba5b185799a552ed3485 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 00:53:10 -0400 Subject: more documentation for settings removed unused automaticLoginEnabled() --- src/settings.cpp | 7 +- src/settings.h | 231 +++++++++++++++++++++++++++++++++---------------------- 2 files changed, 138 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index ce9676ea..7c8c34be 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1564,12 +1564,7 @@ NexusSettings::NexusSettings(Settings& parent, QSettings& settings) { } -bool NexusSettings::automaticLoginEnabled() const -{ - return get(m_Settings, "Settings", "nexus_login", false); -} - -bool NexusSettings::apiKey(QString &apiKey) const +bool NexusSettings::apiKey(QString& apiKey) const { QString tempKey = getWindowsCredential("APIKEY"); if (tempKey.isEmpty()) diff --git a/src/settings.h b/src/settings.h index d3926d72..9dccf41d 100644 --- a/src/settings.h +++ b/src/settings.h @@ -371,25 +371,38 @@ private: }; +// paths for the game and various components +// +// if the 'resolve' parameter is true, %BASE_DIR% is expanded; it's set to +// false mostly in the settings dialog +// class PathSettings { public: PathSettings(QSettings& settings); QString base() const; - QString downloads(bool resolve = true) const; - QString mods(bool resolve = true) const; - QString cache(bool resolve = true) const; - QString profiles(bool resolve = true) const; - QString overwrite(bool resolve = true) const; - void setBase(const QString& path); + + QString downloads(bool resolve = true) const; void setDownloads(const QString& path); + + QString mods(bool resolve = true) const; void setMods(const QString& path); + + QString cache(bool resolve = true) const; void setCache(const QString& path); + + QString profiles(bool resolve = true) const; void setProfiles(const QString& path); + + QString overwrite(bool resolve = true) const; void setOverwrite(const QString& path); + + // map of names to directories, used to remember the last directory used in + // various file pickers + // std::map recent() const; void setRecent(const std::map& map); @@ -406,20 +419,28 @@ class NetworkSettings public: NetworkSettings(QSettings& settings); - /** - * @return true if the user disabled internet features - */ + // whether the user has disabled online features + // bool offlineMode() const; void setOfflineMode(bool b); - /** - * @return true if the user configured the use of a network proxy - */ + // whether the user wants to use the system proxy + // bool useProxy() const; void setUseProxy(bool b); + // add a new download speed to the list for the given server; each server + // remembers the last couple of download speeds and displays the average in + // the network settings + // void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + + // known servers + // ServerList servers() const; + + // sets the servers + // void updateServers(ServerList servers); void dump() const; @@ -427,6 +448,8 @@ public: private: QSettings& m_Settings; + // for pre 2.2.1 ini files + // ServerList serversFromOldMap() const; }; @@ -441,57 +464,45 @@ enum class EndorsementState EndorsementState endorsementStateFromString(const QString& s); QString toString(EndorsementState s); + class NexusSettings { public: NexusSettings(Settings& parent, QSettings& settings); - /** - * @return true if the user has set up automatic login to nexus - **/ - bool automaticLoginEnabled() const; - - /** - * @brief retrieve the login information for nexus - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if automatic login is active, false otherwise - **/ - bool apiKey(QString &apiKey) const; - - /** - * @brief set the nexus login information - * - * @param username username - * @param password password - */ + // if the key exists from the credentials store, puts it in `apiKey` and + // returns true; otherwise, returns false and leaves `apiKey` untouched + // + bool apiKey(QString& apiKey) const; + + // sets the api key in the credentials store, removes it if empty; returns + // false on errors + // bool setApiKey(const QString& apiKey); - /** - * @brief clears the nexus login information - */ + // removes the api key from the credentials store; returns false on errors + // bool clearApiKey(); - /** - * @brief returns whether an API key is currently stored - */ + // returns whether an API key is currently stored + // bool hasApiKey() const; - /** - * @return true if endorsement integration is enabled - */ + // returns whether endorsement integration is enabled + // bool endorsementIntegration() const; void setEndorsementIntegration(bool b) const; + // returns the endorsement state of MO itself + // EndorsementState endorsementState() const; void setEndorsementState(EndorsementState s); - /** - * @brief register MO as the handler for nxm links - * @param force set to true to enforce the registration dialog to show up, - * even if the user said earlier not to - */ + // registers MO as the handler for nxm links + // + // if 'force' is true, the registration dialog will be shown even if the user + // said earlier not to + // void registerAsNXMHandler(bool force); private: @@ -505,30 +516,34 @@ class SteamSettings public: SteamSettings(Settings& parent, QSettings& settings); - /** - * the steam appid is assigned by the steam platform to each product sold there. - * The appid may differ between different versions of a game so it may be impossible - * for Mod Organizer to automatically recognize it, though usually it does - * @return the steam appid for the game - **/ + // the steam appid is assigned by the steam platform to each product sold + // there. + // + // the appid may differ between different versions of a game so it may be + // impossible for MO to automatically recognize it, though usually it does + // QString appID() const; void setAppID(const QString& id); - /** - * @brief retrieve the login information for steam - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if a username has been specified, false otherwise - **/ + // the steam username is stored in the ini, but the password is in the + // windows credentials store; both values are independent and either can be + // empty + // + // if the username exists in the ini, it is assigned to `username`; if not + // `username` is set to an empty string + // + // if the password exists in the credentials store, it is assigned to + // `password`; if not, `password` is set to an empty string + // + // returns whether _both_ the username and password have a value + // bool login(QString &username, QString &password) const; - /** - * @brief set the steam login information - * - * @param username username - * @param password password - */ + // sets the steam login; the username is saved in the ini file and the + // password in the credentials store + // + // if a value is empty, it is removed from its backing store + // void setLogin(QString username, QString password); private: @@ -542,45 +557,45 @@ class InterfaceSettings public: InterfaceSettings(QSettings& settings); - /** - * @return true if the GUI should be locked when running executables - */ + // whether the GUI should be locked when running executables + // bool lockGUI() const; void setLockGUI(bool b); + // filename of the theme + // std::optional styleName() const; void setStyleName(const QString& name); - /** - * @return true if the user chose compact downloads - */ + // whether to show compact downloads + // bool compactDownloads() const; void setCompactDownloads(bool b); - /** - * @return true if the user chose meta downloads - */ + // whether to show meta information for downloads + // bool metaDownloads() const; void setMetaDownloads(bool b); - /** - * @return true if the API counter should be hidden - */ + // whether the API counter should be hidden + // bool hideAPICounter() const; void setHideAPICounter(bool b); - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ + // whether the user wants to see non-official plugins installed outside MO in + // the mod list + // bool displayForeign() const; void setDisplayForeign(bool b); - /** - * @return short code of the configured language (corresponding to the translation files) - */ + // short code of the configured language (corresponding to the translation + // files) + // QString language(); void setLanguage(const QString& name); + // whether the given tutorial has been completed + // bool isTutorialCompleted(const QString& windowName) const; void setTutorialCompleted(const QString& windowName, bool b=true); @@ -594,12 +609,18 @@ class DiagnosticsSettings public: DiagnosticsSettings(QSettings& settings); + // log level for both MO and usvfs + // MOBase::log::Levels logLevel() const; void setLogLevel(MOBase::log::Levels level); + // crash dump type for both MO and usvfs + // CrashDumpsType crashDumpsType() const; void setCrashDumpsType(CrashDumpsType type); + // maximum number of dump files keps, for both MO and usvfs + // int crashDumpsMax() const; void setCrashDumpsMax(int n); @@ -608,10 +629,9 @@ private: }; -/** - * manages the settings for Mod Organizer. The settings are not cached - * inside the class but read/written directly from/to disc - **/ +// manages the settings for MO; the settings are accessed directly through a +// QSettings and so are not cached here +// class Settings : public QObject { Q_OBJECT; @@ -622,35 +642,52 @@ public: static Settings &instance(); + // name of the ini file + // QString filename() const; + // version of MO stored in the ini; this may be different from the current + // version if the user just updated + // std::optional version() const; + + // updates the settings to bring them up to date + // void processUpdates(const QVersionNumber& current, const QVersionNumber& last); + // whether MO has been started for the first time + // bool firstStart() const; void setFirstStart(bool b); + // configured executables + // std::vector> executables() const; void setExecutables(const std::vector>& v); + // whether to backup existing mods on install + // bool keepBackupOnInstall() const; void setKeepBackupOnInstall(bool b); + // blacklisted executables do not get hooked by usvfs; this list is managed + // by MO but given to usvfs when starting an executable + // QString executablesBlacklist() const; void setExecutablesBlacklist(const QString& s); - /** - * @brief sets the new motd hash - **/ + // ? looks obsolete, only used by dead code + // unsigned int motdHash() const; void setMotdHash(unsigned int hash); - /** - * @return true if the user wants to have archives being parsed to show conflicts and contents - */ + // whether archives should be parsed to show conflicts and contents + // bool archiveParsing() const; void setArchiveParsing(bool b); + // whether the user wants to upgrade to pre-releases + // bool usePrereleases() const; void setUsePrereleases(bool b); @@ -688,14 +725,20 @@ public: DiagnosticsSettings& diagnostics(); const DiagnosticsSettings& diagnostics() const; - + // makes sure the ini file is written to disk + // QSettings::Status sync() const; + void dump() const; public slots: + // this slot is connected to by various parts of MO + // void managedGameChanged(MOBase::IPluginGame const *gamePlugin); signals: + // these are fired from outside the settings, mostly by the settings dialog + // void languageChanged(const QString &newLanguage); void styleChanged(const QString &newStyle); -- cgit v1.3.1 From 4df40baea64d2355f4cb976aaf00f651e7cb4f60 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 01:23:17 -0400 Subject: clean up old settings when updating --- src/settings.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 7c8c34be..1288e92d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -94,7 +94,22 @@ void Settings::processUpdates( return; } - if (lastVersion < QVersionNumber(2, 2, 0)) { + if (currentVersion == lastVersion) { + return; + } + + log::info( + "updating from {} to {}", + lastVersion.toString(), currentVersion.toString()); + + auto version = [&](const QVersionNumber& v, auto&& f) { + if (lastVersion < v) { + log::info("processing updates for {}", v.toString()); + f(); + } + }; + + version({2, 2, 0}, [&]{ remove(m_Settings, "Settings", "steam_password"); remove(m_Settings, "Settings", "nexus_username"); remove(m_Settings, "Settings", "nexus_password"); @@ -104,9 +119,9 @@ void Settings::processUpdates( remove(m_Settings, "Settings", "nmm_version"); removeSection(m_Settings, "Servers"); - } + }); - if (lastVersion < QVersionNumber(2, 2, 1)) { + version({2, 2, 1}, [&]{ remove(m_Settings, "General", "mod_info_tabs"); remove(m_Settings, "General", "mod_info_conflict_expanders"); remove(m_Settings, "General", "mod_info_conflicts"); @@ -114,15 +129,39 @@ void Settings::processUpdates( remove(m_Settings, "General", "mod_info_conflicts_overwrite"); remove(m_Settings, "General", "mod_info_conflicts_noconflict"); remove(m_Settings, "General", "mod_info_conflicts_overwritten"); - } + }); - if (lastVersion < QVersionNumber(2, 2, 2)) { + version({2, 2, 2}, [&]{ // log splitter is gone, it's a dock now remove(m_Settings, "General", "log_split"); - } + + // moved to widgets + remove(m_Settings, "General", "mod_info_conflicts_tab"); + remove(m_Settings, "General", "mod_info_conflicts_general_expanders"); + remove(m_Settings, "General", "mod_info_conflicts_general_overwrite"); + remove(m_Settings, "General", "mod_info_conflicts_general_noconflict"); + remove(m_Settings, "General", "mod_info_conflicts_general_overwritten"); + remove(m_Settings, "General", "mod_info_conflicts_advanced_list"); + remove(m_Settings, "General", "mod_info_conflicts_advanced_options"); + remove(m_Settings, "General", "mod_info_tab_order"); + remove(m_Settings, "General", "mod_info_dialog_images_show_dds"); + + // moved to geometry + remove(m_Settings, "General", "window_geometry"); + remove(m_Settings, "General", "window_state"); + remove(m_Settings, "General", "toolbar_size"); + remove(m_Settings, "General", "toolbar_button_style"); + remove(m_Settings, "General", "menubar_visible"); + remove(m_Settings, "General", "window_split"); + remove(m_Settings, "General", "window_monitor"); + remove(m_Settings, "General", "browser_geometry"); + remove(m_Settings, "General", "filters_visible"); + }); //save version in all case set(m_Settings, "General", "version", currentVersion.toString()); + + log::debug("updating done"); } QString Settings::filename() const -- cgit v1.3.1 From da212968ca404dd1840dc39a8c8cf41090a551a7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 03:10:43 -0400 Subject: change the old servers map while processing updates instead of on-demand statusbar_visible is not used anymore --- src/settings.cpp | 99 +++++++++++++++++++++++++++++++------------------------- src/settings.h | 4 +++ 2 files changed, 59 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 1288e92d..4a149726 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -104,7 +104,7 @@ void Settings::processUpdates( auto version = [&](const QVersionNumber& v, auto&& f) { if (lastVersion < v) { - log::info("processing updates for {}", v.toString()); + log::debug("processing updates for {}", v.toString()); f(); } }; @@ -152,10 +152,13 @@ void Settings::processUpdates( remove(m_Settings, "General", "toolbar_size"); remove(m_Settings, "General", "toolbar_button_style"); remove(m_Settings, "General", "menubar_visible"); + remove(m_Settings, "General", "statusbar_visible"); remove(m_Settings, "General", "window_split"); remove(m_Settings, "General", "window_monitor"); remove(m_Settings, "General", "browser_geometry"); remove(m_Settings, "General", "filters_visible"); + + m_Network.updateFromOldMap(); }); //save version in all case @@ -1469,23 +1472,6 @@ void NetworkSettings::setDownloadSpeed(const QString& name, int bytesPerSecond) ServerList NetworkSettings::servers() const { - // servers used to be a map of byte arrays until 2.2.1, it's now an array of - // individual values instead - // - // so post 2.2.1, only one key is returned: "size", the size of the arrays; - // in 2.2.1, one key per server is returned - { - const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); - - if (!keys.empty() && keys[0] != "size") { - // old format - return serversFromOldMap(); - } - } - - - // post 2.2.1 format, array of values - ServerList list; { @@ -1517,32 +1503,6 @@ ServerList NetworkSettings::servers() const return list; } -ServerList NetworkSettings::serversFromOldMap() const -{ - // for 2.2.1 and before - - ServerList list; - const ScopedGroup sg(m_Settings, "Servers"); - - sg.for_each([&](auto&& serverKey) { - QVariantMap data = sg.get(serverKey); - - ServerInfo server( - serverKey, - data["premium"].toBool(), - data["lastSeen"].toDate(), - data["preferred"].toInt(), - {}); - - // ignoring download count and speed, it's now a list of values instead of - // a total - - list.add(std::move(server)); - }); - - return list; -} - void NetworkSettings::updateServers(ServerList newServers) { // clean up unavailable servers @@ -1577,6 +1537,57 @@ void NetworkSettings::updateServers(ServerList newServers) } } +void NetworkSettings::updateFromOldMap() +{ + // servers used to be a map of byte arrays until 2.2.1, it's now an array of + // individual values instead + // + // so post 2.2.1, only one key is returned: "size", the size of the arrays; + // in 2.2.1, one key per server is returned + + // sanity check that this is really 2.2.1 + { + const QStringList keys = ScopedGroup(m_Settings, "Servers").keys(); + + for (auto&& k : keys) { + if (k == "size") { + // this looks like an array, so the upgrade was probably already done + return; + } + } + } + + const auto servers = serversFromOldMap(); + removeSection(m_Settings, "Servers"); + updateServers(servers); +} + +ServerList NetworkSettings::serversFromOldMap() const +{ + // for 2.2.1 and before + + ServerList list; + const ScopedGroup sg(m_Settings, "Servers"); + + sg.for_each([&](auto&& serverKey) { + QVariantMap data = sg.get(serverKey); + + ServerInfo server( + serverKey, + data["premium"].toBool(), + data["lastSeen"].toDate(), + data["preferred"].toInt(), + {}); + + // ignoring download count and speed, it's now a list of values instead of + // a total + + list.add(std::move(server)); + }); + + return list; +} + void NetworkSettings::dump() const { log::debug("servers:"); diff --git a/src/settings.h b/src/settings.h index 9dccf41d..91b87e29 100644 --- a/src/settings.h +++ b/src/settings.h @@ -443,6 +443,10 @@ public: // void updateServers(ServerList servers); + // for 2.2.1 and before, rewrites the old byte array map to the new format + // + void updateFromOldMap(); + void dump() const; private: -- cgit v1.3.1 From d863e62c6e2f3c0daa50e16a0206f59558a7bb7e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 08:09:38 -0400 Subject: really remove ask_for_nexuspw, the 2.2.0 update code was using the wrong section --- src/settings.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 4a149726..1f066100 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -158,6 +158,10 @@ void Settings::processUpdates( remove(m_Settings, "General", "browser_geometry"); remove(m_Settings, "General", "filters_visible"); + // this was supposed to have been removed above when updating from 2.2.0, + // but it wasn't in Settings, it was in General + remove(m_Settings, "General", "ask_for_nexuspw"); + m_Network.updateFromOldMap(); }); -- cgit v1.3.1 From e08e605c85a1f62f4b6b83f5404457f5dc55654a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 03:49:33 -0400 Subject: added missing include guards log free space on drives involved in paths --- src/env.cpp | 40 +++++++++++++++++++++++++++++++++++++++- src/env.h | 13 ++++++++++++- src/envmetrics.h | 5 +++++ src/envmodule.h | 5 +++++ src/envsecurity.h | 5 +++++ src/envshortcut.h | 5 +++++ src/envwindows.h | 5 +++++ src/main.cpp | 3 +-- src/pch.h | 1 + 9 files changed, 78 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 641eb4a7..4628e3f4 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -4,6 +4,7 @@ #include "envsecurity.h" #include "envshortcut.h" #include "envwindows.h" +#include "settings.h" #include #include @@ -85,7 +86,7 @@ const Metrics& Environment::metrics() const return *m_metrics; } -void Environment::dump() const +void Environment::dump(const Settings& s) const { log::debug("windows: {}", m_windows->toString()); @@ -107,6 +108,43 @@ void Environment::dump() const for (const auto& d : m_metrics->displays()) { log::debug(" . {}", d.toString()); } + + dumpDisks(s); +} + +void Environment::dumpDisks(const Settings& s) const +{ + std::set rootPaths; + + auto dump = [&](auto&& path) { + const QFileInfo fi(path); + const QStorageInfo si(fi.absoluteFilePath()); + + if (rootPaths.contains(si.rootPath())) { + // already seen + return; + } + + // remember + rootPaths.insert(si.rootPath()); + + log::debug( + " . {} free={} MB{}", + si.rootPath(), + (si.bytesFree() / 1000 / 1000), + (si.isReadOnly() ? " (readonly)" : "")); + }; + + log::debug("drives:"); + + dump(QStorageInfo::root().rootPath()); + dump(s.paths().base()); + dump(s.paths().downloads()); + dump(s.paths().mods()); + dump(s.paths().cache()); + dump(s.paths().profiles()); + dump(s.paths().overwrite()); + dump(QCoreApplication::applicationDirPath()); } diff --git a/src/env.h b/src/env.h index 0e88263b..1f146a08 100644 --- a/src/env.h +++ b/src/env.h @@ -1,3 +1,8 @@ +#ifndef ENV_ENV_H +#define ENV_ENV_H + +class Settings; + namespace env { @@ -125,13 +130,17 @@ public: // logs the environment // - void dump() const; + void dump(const Settings& s) const; private: std::vector m_modules; std::unique_ptr m_windows; std::vector m_security; std::unique_ptr m_metrics; + + // dumps all the disks involved in the settings + // + void dumpDisks(const Settings& s) const; }; @@ -152,3 +161,5 @@ bool coredump(CoreDumpTypes type); bool coredumpOther(CoreDumpTypes type); } // namespace env + +#endif // ENV_ENV_H diff --git a/src/envmetrics.h b/src/envmetrics.h index bede36fc..c5d2765a 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -1,3 +1,6 @@ +#ifndef ENV_METRICS_H +#define ENV_METRICS_H + #include #include @@ -70,3 +73,5 @@ private: }; } // namespace + +#endif // ENV_METRICS_H diff --git a/src/envmodule.h b/src/envmodule.h index ea1156bd..3f0f99ab 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -1,3 +1,6 @@ +#ifndef ENV_MODULE_H +#define ENV_MODULE_H + #include #include @@ -96,3 +99,5 @@ private: std::vector getLoadedModules(); } // namespace env + +#endif // ENV_MODULE_H diff --git a/src/envsecurity.h b/src/envsecurity.h index 200cb531..bc63c4a2 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -1,3 +1,6 @@ +#ifndef ENV_SECURITY_H +#define ENV_SECURITY_H + #include #include @@ -47,3 +50,5 @@ private: std::vector getSecurityProducts(); } // namespace env + +#endif // ENV_SECURITY_H diff --git a/src/envshortcut.h b/src/envshortcut.h index 82eea191..a05528d9 100644 --- a/src/envshortcut.h +++ b/src/envshortcut.h @@ -1,3 +1,6 @@ +#ifndef ENV_SHORTCUT_H +#define ENV_SHORTCUT_H + #include class Executable; @@ -103,3 +106,5 @@ private: QString toString(Shortcut::Locations loc); } // namespace + +#endif // ENV_SHORTCUT_H diff --git a/src/envwindows.h b/src/envwindows.h index c23f99f4..90655e49 100644 --- a/src/envwindows.h +++ b/src/envwindows.h @@ -1,3 +1,6 @@ +#ifndef ENV_WINDOWS_H +#define ENV_WINDOWS_H + #include #include @@ -104,3 +107,5 @@ private: }; } // namespace + +#endif // ENV_WINDOWS_H diff --git a/src/main.cpp b/src/main.cpp index b5568fec..04bc423b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -577,8 +577,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); env::Environment env; - - env.dump(); + env.dump(settings); settings.dump(); sanityChecks(env); diff --git a/src/pch.h b/src/pch.h index af1a4ade..01a97357 100644 --- a/src/pch.h +++ b/src/pch.h @@ -255,3 +255,4 @@ #include #include #include +#include -- cgit v1.3.1 From 12e1a91e4fe8de291fbe72c23031f3e79613c0dd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Sep 2019 04:58:17 -0400 Subject: log desktop geometry log more info on game plugin --- src/env.cpp | 5 +++++ src/envmetrics.cpp | 12 ++++++++++++ src/envmetrics.h | 4 ++++ src/main.cpp | 5 ++++- src/settings.cpp | 1 + 5 files changed, 26 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 4628e3f4..411443c5 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -109,6 +109,11 @@ void Environment::dump(const Settings& s) const log::debug(" . {}", d.toString()); } + const auto r = m_metrics->desktopGeometry(); + log::debug( + "desktop geometry: ({},{})-({},{})", + r.left(), r.top(), r.right(), r.bottom()); + dumpDisks(s); } diff --git a/src/envmetrics.cpp b/src/envmetrics.cpp index b1b9bd2e..5fb80449 100644 --- a/src/envmetrics.cpp +++ b/src/envmetrics.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace env { @@ -225,6 +226,17 @@ const std::vector& Metrics::displays() const return m_displays; } +QRect Metrics::desktopGeometry() const +{ + QRect r; + + for (auto* s : QGuiApplication::screens()) { + r = r.united(s->geometry()); + } + + return r; +} + void Metrics::getDisplays() { // don't bother if it goes over 100 diff --git a/src/envmetrics.h b/src/envmetrics.h index c5d2765a..8dfdb087 100644 --- a/src/envmetrics.h +++ b/src/envmetrics.h @@ -66,6 +66,10 @@ public: // const std::vector& displays() const; + // full resolution + // + QRect desktopGeometry() const; + private: std::vector m_displays; diff --git a/src/main.cpp b/src/main.cpp index 04bc423b..9cb8c08d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -648,7 +648,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, game->setGameVariant(edition); - log::info("managing game at {}", game->gameDirectory().absolutePath()); + log::info( + "using game plugin '{}' ('{}', steam id '{}') at {}", + game->gameName(), game->gameShortName(), game->steamAPPId(), + game->gameDirectory().absolutePath()); organizer.updateExecutablesList(); diff --git a/src/settings.cpp b/src/settings.cpp index 1f066100..7fdda2bf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1219,6 +1219,7 @@ void PluginSettings::setPersistent( m_Settings.sync(); } } + void PluginSettings::addBlacklist(const QString &fileName) { m_PluginBlacklist.insert(fileName); -- cgit v1.3.1 From 635c0b7a06d358cefcdddb00b7f3b8562c994689 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Sep 2019 03:24:09 -0400 Subject: log line and line number when there's an error with the locked order file --- src/pluginlist.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 33423225..c6c61da3 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -437,7 +437,10 @@ void PluginList::readLockedOrderFrom(const QString &fileName) } file.open(QIODevice::ReadOnly); + + int lineNumber = 0; while (!file.atEnd()) { + ++lineNumber; QByteArray line = file.readLine(); if ((line.size() > 0) && (line.at(0) != '#')) { QList fields = line.split('|'); @@ -463,6 +466,7 @@ void PluginList::readLockedOrderFrom(const QString &fileName) } } } else { + log::error("locked order file: invalid line #{} '{}'", lineNumber, QString::fromUtf8(line)); reportError(tr("The file containing locked plugin indices is broken")); break; } -- cgit v1.3.1 From 61956802b1bc42c4878944ddd22514578c188e86 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Sep 2019 04:24:12 -0400 Subject: now logs in utc, added year to avoid confusion --- src/main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 9cb8c08d..74b04970 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -827,7 +827,12 @@ void initLogging() { LogModel::create(); - log::createDefault(MOBase::log::Debug, "%^[%m-%d %H:%M:%S.%e %L] %v%$"); + log::LoggerConfiguration conf; + conf.maxLevel = MOBase::log::Debug; + conf.pattern = "%^[%Y-%m-%d %H:%M:%S.%e %L] %v%$"; + conf.utc = true; + + log::createDefault(conf); log::getDefault().setCallback( [](log::Entry e){ LogModel::instance().add(e); }); -- cgit v1.3.1 From 4d9c1db885bd3ab230440b25e70dcd8049cf4650 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 11 Sep 2019 11:45:32 -0500 Subject: Add portable lock feature If the file "portable.txt" is present in the application directory, MO will force itself to be launched as a portable instance. The change game button and menu item are hidden to prevent the user from changing out of the portable instance. --- src/instancemanager.cpp | 21 +++++++++++++++++++++ src/instancemanager.h | 3 +++ src/mainwindow.cpp | 4 ++++ src/shared/appconfig.inc | 1 + 4 files changed, 29 insertions(+) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index fdc30e22..c0d343de 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -186,6 +186,10 @@ QString InstanceManager::queryInstanceName(const QStringList &instanceList) cons QString InstanceManager::chooseInstance(const QStringList &instanceList) const { + if (portableInstallIsLocked()) { + return QString(); + } + enum class Special : uint8_t { NewInstance, Portable, @@ -266,6 +270,19 @@ bool InstanceManager::portableInstall() const } +bool InstanceManager::portableInstallIsLocked() const +{ + return QFile::exists(qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::portableLockFileName())); +} + + +bool InstanceManager::allowedToChangeInstance() const +{ + return !portableInstallIsLocked(); +} + + void InstanceManager::createDataPath(const QString &dataPath) const { if (!QDir(dataPath).exists()) { @@ -286,6 +303,10 @@ void InstanceManager::createDataPath(const QString &dataPath) const QString InstanceManager::determineDataPath() { QString instanceId = currentInstance(); + if (portableInstallIsLocked()) + { + instanceId.clear(); + } if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall())) { // startup, apparently using portable mode before diff --git a/src/instancemanager.h b/src/instancemanager.h index 4efa6f03..0e31fb08 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -38,6 +38,8 @@ public: QString currentInstance() const; + bool allowedToChangeInstance() const; + private: InstanceManager(); @@ -58,6 +60,7 @@ private: void createDataPath(const QString &dataPath) const; bool portableInstall() const; + bool portableInstallIsLocked() const; private: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 657c1a27..bd9f1486 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -727,6 +727,10 @@ void MainWindow::setupToolbar() } else { log::warn("no separator found on the toolbar, icons won't be right-aligned"); } + + if (!InstanceManager::instance().allowedToChangeInstance()) { + ui->actionChange_Game->setVisible(false); + } } void MainWindow::setupActionMenu(QAction* a) diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc index e572a32b..709c845d 100644 --- a/src/shared/appconfig.inc +++ b/src/shared/appconfig.inc @@ -17,6 +17,7 @@ APPPARAM(std::wstring, proxyDLLOrig, L"steam_api_orig.dll") // needs to be ident APPPARAM(std::wstring, proxyDLLSource, L"proxy.dll") APPPARAM(std::wstring, vfs32DLLName, L"usvfs_x86.dll") APPPARAM(std::wstring, vfs64DLLName, L"usvfs_x64.dll") +APPPARAM(std::wstring, portableLockFileName, L"portable.txt") APPPARAM(const wchar_t*, localSavePlaceholder, L"__MOProfileSave__\\") APPPARAM(std::wstring, firstStepsTutorial, L"tutorial_firststeps_main.js") -- cgit v1.3.1 From 9c59c739d4ef9a4479ac849badc998b200fddb13 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 11 Sep 2019 23:32:32 -0400 Subject: moved ExpanderWidget to uibase --- src/expanderwidget.cpp | 81 -------------------------------------------------- src/expanderwidget.h | 51 ------------------------------- 2 files changed, 132 deletions(-) delete mode 100644 src/expanderwidget.cpp delete mode 100644 src/expanderwidget.h (limited to 'src') diff --git a/src/expanderwidget.cpp b/src/expanderwidget.cpp deleted file mode 100644 index a9d045a5..00000000 --- a/src/expanderwidget.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "expanderwidget.h" - -ExpanderWidget::ExpanderWidget() - : m_button(nullptr), m_content(nullptr), opened_(false) -{ -} - -ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content) - : ExpanderWidget() -{ - set(button, content); -} - -void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) -{ - m_button = button; - m_content = content; - - m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); - - toggle(o); -} - -void ExpanderWidget::toggle() -{ - if (opened()) { - toggle(false); - } - else { - toggle(true); - } -} - -void ExpanderWidget::toggle(bool b) -{ - if (b) { - m_button->setArrowType(Qt::DownArrow); - m_content->show(); - } else { - m_button->setArrowType(Qt::RightArrow); - m_content->hide(); - } - - // the state has to be remembered instead of using m_content's visibility - // because saving the state in saveConflictExpandersState() happens after the - // dialog is closed, which marks all the widgets hidden - opened_ = b; -} - -bool ExpanderWidget::opened() const -{ - return opened_; -} - -QByteArray ExpanderWidget::saveState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - - stream << opened(); - - return result; -} - -void ExpanderWidget::restoreState(const QByteArray& a) -{ - QDataStream stream(a); - - bool opened = false; - stream >> opened; - - if (stream.status() == QDataStream::Ok) { - toggle(opened); - } -} - -QToolButton* ExpanderWidget::button() const -{ - return m_button; -} diff --git a/src/expanderwidget.h b/src/expanderwidget.h deleted file mode 100644 index 99b2d303..00000000 --- a/src/expanderwidget.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef EXPANDERWIDGET_H -#define EXPANDERWIDGET_H - -#include - -/* Takes a QToolButton and a widget and creates an expandable widget. -**/ -class ExpanderWidget -{ -public: - /** empty expander, use set() - **/ - ExpanderWidget(); - - /** see set() - **/ - ExpanderWidget(QToolButton* button, QWidget* content); - - /** @brief sets the button and content widgets to use - * the button will be given an arrow icon, clicking it will toggle the - * visibility of the given widget - * @param button the button that toggles the content - * @param content the widget that will be shown or hidden - * @param opened initial state, defaults to closed - **/ - void set(QToolButton* button, QWidget* content, bool opened=false); - - /** either opens or closes the expander depending on the current state - **/ - void toggle(); - - /** sets the current state of the expander - **/ - void toggle(bool b); - - /** returns whether the expander is currently opened - **/ - bool opened() const; - - QByteArray saveState() const; - void restoreState(const QByteArray& a); - - QToolButton* button() const; - -private: - QToolButton* m_button; - QWidget* m_content; - bool opened_; -}; - -#endif // EXPANDERWIDGET_H -- cgit v1.3.1 From 09f98576e882a5fef68cdefdff939834a8782eaa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 11 Sep 2019 23:33:23 -0400 Subject: added env::getFileSecurity() Environment gets stuff on demand --- src/env.cpp | 31 +++-- src/env.h | 21 +++- src/envsecurity.cpp | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/envsecurity.h | 17 +++ 4 files changed, 388 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 411443c5..e2b85560 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -57,10 +57,7 @@ Console::~Console() Environment::Environment() - : m_windows(new WindowsInfo), m_metrics(new Metrics) { - m_modules = getLoadedModules(); - m_security = getSecurityProducts(); } // anchor @@ -68,48 +65,64 @@ Environment::~Environment() = default; const std::vector& Environment::loadedModules() const { + if (m_modules.empty()){ + m_modules = getLoadedModules(); + } + return m_modules; } const WindowsInfo& Environment::windowsInfo() const { + if (!m_windows) { + m_windows.reset(new WindowsInfo); + } + return *m_windows; } const std::vector& Environment::securityProducts() const { + if (m_security.empty()) { + m_security = getSecurityProducts(); + } + return m_security; } const Metrics& Environment::metrics() const { + if (!m_metrics) { + m_metrics.reset(new Metrics); + } + return *m_metrics; } void Environment::dump(const Settings& s) const { - log::debug("windows: {}", m_windows->toString()); + log::debug("windows: {}", windowsInfo().toString()); - if (m_windows->compatibilityMode()) { + if (windowsInfo().compatibilityMode()) { log::warn("MO seems to be running in compatibility mode"); } log::debug("security products:"); - for (const auto& sp : m_security) { + for (const auto& sp : securityProducts()) { log::debug(" . {}", sp.toString()); } log::debug("modules loaded in process:"); - for (const auto& m : m_modules) { + for (const auto& m : loadedModules()) { log::debug(" . {}", m.toString()); } log::debug("displays:"); - for (const auto& d : m_metrics->displays()) { + for (const auto& d : metrics().displays()) { log::debug(" . {}", d.toString()); } - const auto r = m_metrics->desktopGeometry(); + const auto r = metrics().desktopGeometry(); log::debug( "desktop geometry: ({},{})-({},{})", r.left(), r.top(), r.right(), r.bottom()); diff --git a/src/env.h b/src/env.h index 1f146a08..46095ca3 100644 --- a/src/env.h +++ b/src/env.h @@ -79,6 +79,19 @@ template using COMPtr = std::unique_ptr; +// used by MallocPtr, calls std::free() as the deleter +// +struct MallocFreer +{ + void operator()(void* p) + { + std::free(p); + } +}; + +template +using MallocPtr = std::unique_ptr; + // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // @@ -133,10 +146,10 @@ public: void dump(const Settings& s) const; private: - std::vector m_modules; - std::unique_ptr m_windows; - std::vector m_security; - std::unique_ptr m_metrics; + mutable std::vector m_modules; + mutable std::unique_ptr m_windows; + mutable std::vector m_security; + mutable std::unique_ptr m_metrics; // dumps all the disks involved in the settings // diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 376be4df..6e3fadbe 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -9,6 +9,11 @@ #include #pragma comment(lib, "Wbemuuid.lib") +#include +#include +#include +#pragma comment(lib, "advapi32.lib") + namespace env { @@ -397,4 +402,331 @@ std::vector getSecurityProducts() return v; } + +class failed +{ +public: + failed(DWORD e, QString what) + : m_what(what + ", " + QString::fromStdWString(formatSystemMessage(e))) + { + } + + QString what() const + { + return m_what; + } + +private: + QString m_what; +}; + + +MallocPtr getSecurityDescriptor(const QString& path) +{ + const auto wpath = path.toStdWString(); + BOOL ret = FALSE; + + DWORD length = 0; + ret = ::GetFileSecurityW( + wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, + nullptr, 0, &length); + + if (!ret || length == 0) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + if (e == ERROR_ACCESS_DENIED) { + // if this fails, the user doesn't even have permissions to get the + // security descriptor, which probably means they're not the owner and + // their effective access is none + throw failed(e, "cannot get security descriptor"); + } else { + // other error + throw failed(e, "GetFileSecurity() for length failed"); + } + } + } + + MallocPtr sd( + static_cast(std::malloc(length))); + + std::memset(sd.get(), 0, length); + + ret = ::GetFileSecurityW( + wpath.c_str(), DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION, + sd.get(), length, &length); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetFileSecurity()"); + } + + return sd; +} + +PACL getDacl(SECURITY_DESCRIPTOR* sd) +{ + BOOL present = FALSE; + BOOL daclDefaulted = FALSE; + PACL acl = nullptr; + + BOOL ret = ::GetSecurityDescriptorDacl(sd, &present, &acl, &daclDefaulted); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetSecurityDescriptorDacl()"); + } + + if (!present) { + return nullptr; + } + + return acl; +} + +PSID getFileOwner(SECURITY_DESCRIPTOR* sd) +{ + BOOL ownerDefaulted = FALSE; + PSID owner; + + BOOL ret = ::GetSecurityDescriptorOwner(sd, &owner, &ownerDefaulted); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetSecurityDescriptionOwner()"); + } + + return owner; +} + +MallocPtr getCurrentUser() +{ + HANDLE hnd = ::GetCurrentProcess(); + HANDLE rawToken = 0; + + BOOL ret = ::OpenProcessToken(hnd, TOKEN_QUERY, &rawToken); + if (!ret) { + const auto e = GetLastError(); + throw(e, "OpenProcessToken()"); + } + + HandlePtr token(rawToken); + + DWORD retsize = 0; + ret = ::GetTokenInformation(token.get(), TokenUser, 0, 0, &retsize); + + if (!ret) { + const auto e = GetLastError(); + if (e != ERROR_INSUFFICIENT_BUFFER) { + throw failed(e, "GetTokenInformation() for length"); + } + } + + MallocPtr tokenBuffer(std::malloc(retsize)); + ret = ::GetTokenInformation( + token.get(), TokenUser, tokenBuffer.get(), retsize, &retsize); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "GetTokenInformation()"); + } + + PSID tokenSid = ((PTOKEN_USER)(tokenBuffer.get()))->User.Sid; + DWORD sidLen = ::GetLengthSid(tokenSid); + MallocPtr currentUserSID((SID*)(malloc(sidLen))); + + ret = ::CopySid(sidLen, currentUserSID.get(), tokenSid); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "CopySid()"); + } + + return currentUserSID; +} + +ACCESS_MASK getEffectiveRights(ACL* dacl, PSID sid) +{ + TRUSTEEW trustee = {}; + BuildTrusteeWithSid(&trustee, sid); + + ACCESS_MASK access = 0; + DWORD ret = ::GetEffectiveRightsFromAclW(dacl, &trustee, &access); + + if (ret != ERROR_SUCCESS) { + throw failed(ret, "GetEffectiveRightsFromAclW()"); + } + + return access; +} + +QString getUsername(PSID owner) +{ + DWORD nameSize=0, domainSize=0; + auto use = SidTypeUnknown; + + BOOL ret = LookupAccountSidW( + nullptr, owner, nullptr, &nameSize, nullptr, &domainSize, &use); + + if (!ret) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + throw failed(e, "LookupAccountSid() for sizes"); + } + } + + auto wsName = std::make_unique(nameSize); + auto wsDomain = std::make_unique(domainSize); + + ret = LookupAccountSidW( + nullptr, owner, wsName.get(), &nameSize, wsDomain.get(), &domainSize, &use); + + if (!ret) { + const auto e = GetLastError(); + throw failed(e, "LookupAccountSid()"); + } + + const QString name = QString::fromWCharArray(wsName.get(), nameSize); + const QString domain = QString::fromWCharArray(wsDomain.get(), domainSize); + + if (!name.isEmpty() && !domain.isEmpty()) { + return domain + "\\" + name; + } else { + // either or both are empty + return name + domain; + } +} + +FileRights makeFileRights(ACCESS_MASK m) +{ + FileRights fr; + + if (m & FILE_GENERIC_READ) { + fr.list.push_back("file_generic_read"); + } else { + if (m & READ_CONTROL) { + fr.list.push_back("read_ctrl"); + } + + if (m & FILE_READ_DATA) { + fr.list.push_back("read_data"); + } + + if (m & FILE_READ_ATTRIBUTES) { + fr.list.push_back("read_atts"); + } + + if (m & FILE_READ_EA) { + fr.list.push_back("read_ex_atts"); + } + + if (m & SYNCHRONIZE) { + fr.list.push_back("sync"); + } + } + + if (m & FILE_GENERIC_WRITE) { + fr.list.push_back("file_generic_write"); + } else { + // READ_CONTROL handled above + + if (m & FILE_WRITE_DATA) { + fr.list.push_back("write_data"); + } + + if (m & FILE_WRITE_ATTRIBUTES) { + fr.list.push_back("write_atts"); + } + + if (m & FILE_WRITE_EA) { + fr.list.push_back("write_ex_atts"); + } + + if (m & FILE_APPEND_DATA) { + fr.list.push_back("append_data"); + } + + // SYNCHRONIZE handled above + } + + if (m & FILE_GENERIC_EXECUTE) { + fr.list.push_back("file_generic_execute"); + fr.hasExecute = true; + } else { + // READ_CONTROL handled above + // FILE_READ_ATTRIBUTES handled above + + if (m & FILE_EXECUTE) { + fr.list.push_back("execute"); + fr.hasExecute = true; + } + + // SYNCHRONIZE handled above + } + + if (m & DELETE) { + fr.list.push_back("delete"); + } + + if (m & WRITE_DAC) { + fr.list.push_back("write_dac"); + } + + if (m & WRITE_OWNER) { + fr.list.push_back("write_owner"); + } + + if (m & GENERIC_ALL) { + fr.list.push_back("generic_all"); + } + + if (m & GENERIC_WRITE) { + fr.list.push_back("generic_write"); + } + + if (m & GENERIC_READ) { + fr.list.push_back("generic_read"); + } + + // 0x001f01ff + const auto normalRights = + STANDARD_RIGHTS_ALL | + FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | + FILE_DELETE_CHILD; + + if (m == normalRights) { + fr.normalRights = true; + } + + return fr; +} + +FileSecurity getFileSecurity(const QString& path) +{ + FileSecurity fs; + + try + { + auto sd = getSecurityDescriptor(path); + auto dacl = getDacl(sd.get()); + auto currentUser = getCurrentUser(); + auto owner = getFileOwner(sd.get()); + auto access = getEffectiveRights(dacl, currentUser.get()); + + fs.rights = makeFileRights(access); + + if (EqualSid(owner, currentUser.get())) { + fs.owner = "(this user)"; + } else { + fs.owner = getUsername(owner); + } + } + catch(failed& f) + { + fs.error = f.what(); + } + + return fs; +} } // namespace diff --git a/src/envsecurity.h b/src/envsecurity.h index bc63c4a2..436103b7 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -49,6 +49,23 @@ private: std::vector getSecurityProducts(); + +struct FileRights +{ + QStringList list; + bool hasExecute = false; + bool normalRights = false; +}; + +struct FileSecurity +{ + QString owner; + FileRights rights; + QString error; +}; + +FileSecurity getFileSecurity(const QString& file); + } // namespace env #endif // ENV_SECURITY_H -- cgit v1.3.1 From 06218502ed5379555eda1504e6b05f2ef6dfb292 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 11 Sep 2019 23:34:54 -0400 Subject: fixes for ExpanderWidget removed dead code in MainWindow --- src/CMakeLists.txt | 3 --- src/mainwindow.cpp | 11 ----------- src/mainwindow.h | 3 --- src/modinfodialogconflicts.h | 2 +- src/settings.h | 6 +++--- src/settingsutilities.h | 6 ++++-- 6 files changed, 8 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea27184b..60822834 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -136,7 +136,6 @@ SET(organizer_SRCS apiuseraccount.cpp filerenamer.cpp texteditor.cpp - expanderwidget.cpp env.cpp envmetrics.cpp envmodule.cpp @@ -260,7 +259,6 @@ SET(organizer_HDRS apiuseraccount.h filerenamer.h texteditor.h - expanderwidget.h env.h envmetrics.h envmodule.h @@ -478,7 +476,6 @@ set(utilities ) set(widgets - expanderwidget genericicondelegate filerenamer filterwidget diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 657c1a27..054262e9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2561,17 +2561,6 @@ void MainWindow::modInstalled(const QString &modName) modUpdateCheck(IDs); } -void MainWindow::procError(QProcess::ProcessError error) -{ - reportError(tr("failed to spawn notepad.exe: %1").arg(error)); - this->sender()->deleteLater(); -} - -void MainWindow::procFinished(int, QProcess::ExitStatus) -{ - this->sender()->deleteLater(); -} - void MainWindow::showMessage(const QString &message) { MessageDialog::showMessage(message, this); diff --git a/src/mainwindow.h b/src/mainwindow.h index 6f06b9d5..1f997ab1 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -489,9 +489,6 @@ private slots: void doMoveOverwriteContentToMod(const QString &modAbsolutePath); void clearOverwrite(); - void procError(QProcess::ProcessError error); - void procFinished(int exitCode, QProcess::ExitStatus exitStatus); - // nexus related void checkModsForUpdates(); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index a77c2ac9..ad305dfc 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -33,7 +33,7 @@ signals: private: struct Expanders { - ExpanderWidget overwrite, overwritten, nonconflict; + MOBase::ExpanderWidget overwrite, overwritten, nonconflict; }; ConflictsTab* m_tab; diff --git a/src/settings.h b/src/settings.h index 91b87e29..815ed160 100644 --- a/src/settings.h +++ b/src/settings.h @@ -32,13 +32,13 @@ along with Mod Organizer. If not, see . namespace MOBase { class IPlugin; class IPluginGame; + class ExpanderWidget; } class QSplitter; class ServerList; class Settings; -class ExpanderWidget; // helper class that calls restoreGeometry() in the constructor and @@ -153,8 +153,8 @@ public: void saveState(const QSplitter* splitter); bool restoreState(QSplitter* splitter) const; - void saveState(const ExpanderWidget* expander); - bool restoreState(ExpanderWidget* expander) const; + void saveState(const MOBase::ExpanderWidget* expander); + bool restoreState(MOBase::ExpanderWidget* expander) const; void saveVisibility(const QWidget* w); bool restoreVisibility(QWidget* w, std::optional def={}) const; diff --git a/src/settingsutilities.h b/src/settingsutilities.h index d99abb06..c3eef12f 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -3,7 +3,9 @@ #include -class ExpanderWidget; +namespace MOBase { + class ExpanderWidget; +} template struct ValueConverter @@ -241,7 +243,7 @@ private: QString widgetNameWithTopLevel(const QWidget* widget); QString widgetName(const QMainWindow* w); QString widgetName(const QHeaderView* w); -QString widgetName(const ExpanderWidget* w); +QString widgetName(const MOBase::ExpanderWidget* w); QString widgetName(const QWidget* w); template -- cgit v1.3.1 From 0015a12cf8916d8000c6d14356b4c17c62f4a588 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 11 Sep 2019 23:36:13 -0400 Subject: rewritten spawn() to use std::wstring instead of manual buffers error dialogs now use TaskDialog with more user-friendly text --- src/spawn.cpp | 414 +++++++++++++++++++++++++++++++++++++++++++--------------- src/spawn.h | 19 --- 2 files changed, 307 insertions(+), 126 deletions(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index f77da35f..614bc92f 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -21,164 +21,364 @@ along with Mod Organizer. If not, see . #include "report.h" #include "utility.h" +#include "env.h" +#include "envwindows.h" +#include "envsecurity.h" +#include #include +#include #include #include #include #include #include "helper.h" - #include #include #include - - #include - -#include +#include using namespace MOBase; using namespace MOShared; +namespace +{ + +struct SpawnParameters +{ + std::wstring binary; + std::wstring arguments; + std::wstring currentDirectory; + bool suspended = false; + bool hooked = false; + HANDLE stdOut = INVALID_HANDLE_VALUE; + HANDLE stdErr = INVALID_HANDLE_VALUE; +}; -static const int BUFSIZE = 4096; -static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, - bool suspended, bool hooked, - HANDLE stdOut, HANDLE stdErr, - HANDLE& processHandle, HANDLE& threadHandle) +std::wstring pathEnv() +{ + std::wstring s(4000, L' '); + + DWORD realSize = ::GetEnvironmentVariableW( + L"PATH", s.data(), static_cast(s.size())); + + if (realSize > s.size()) { + s.resize(realSize); + + ::GetEnvironmentVariableW( + TEXT("PATH"), s.data(), static_cast(s.size())); + } + + return s; +} + +void setPathEnv(const std::wstring& s) +{ + ::SetEnvironmentVariableW(L"PATH", s.c_str()); +} + +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) { BOOL inheritHandles = FALSE; - STARTUPINFO si; - ::ZeroMemory(&si, sizeof(si)); - if (stdOut != INVALID_HANDLE_VALUE) { - si.hStdOutput = stdOut; + + STARTUPINFO si = {}; + si.cb = sizeof(si); + + // inherit handles if we plan to use stdout or stderr reroute + if (sp.stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = sp.stdOut; inheritHandles = TRUE; si.dwFlags |= STARTF_USESTDHANDLES; } - if (stdErr != INVALID_HANDLE_VALUE) { - si.hStdError = stdErr; + + if (sp.stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = sp.stdErr; inheritHandles = TRUE; si.dwFlags |= STARTF_USESTDHANDLES; } - si.cb = sizeof(si); - size_t length = wcslen(binary) + wcslen(arguments) + 4; - wchar_t *commandLine = nullptr; - if (arguments[0] != L'\0') { - commandLine = new wchar_t[length]; - _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments); - } else { - commandLine = new wchar_t[length]; - _snwprintf_s(commandLine, length, _TRUNCATE, L"\"%ls\"", binary); - } - QString moPath = QCoreApplication::applicationDirPath(); + std::wstring commandLine; - boost::scoped_array oldPath(new TCHAR[BUFSIZE]); - DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); - if (offset > BUFSIZE) { - oldPath.reset(new TCHAR[offset]); - ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); + if (sp.arguments[0] != L'\0') { + commandLine = L"\"" + sp.binary + L"\" " + sp.arguments; + } else { + commandLine = L"\"" + sp.binary + L"\""; } - { - boost::scoped_array newPath(new TCHAR[offset + moPath.length() + 2]); - _tcsncpy(newPath.get(), oldPath.get(), offset); - newPath.get()[offset] = '\0'; - _tcsncat(newPath.get(), TEXT(";"), 1); - _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length()); + QString moPath = QCoreApplication::applicationDirPath(); - ::SetEnvironmentVariable(TEXT("PATH"), newPath.get()); - } + const auto oldPath = pathEnv(); + setPathEnv(oldPath + L";" + QDir::toNativeSeparators(moPath).toStdWString()); PROCESS_INFORMATION pi; BOOL success = FALSE; - if (hooked) { - success = ::CreateProcessHooked(nullptr, - commandLine, - nullptr, nullptr, // no special process or thread attributes - inheritHandles, // inherit handles if we plan to use stdout or stderr reroute - CREATE_BREAKAWAY_FROM_JOB, - nullptr, // same environment as parent - currentDirectory, // current directory - &si, &pi // startup and process information - ); + + if (sp.hooked) { + success = ::CreateProcessHooked( + nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, + sp.currentDirectory.c_str(), &si, &pi); } else { - success = ::CreateProcess(nullptr, - commandLine, - nullptr, nullptr, // no special process or thread attributes - inheritHandles, // inherit handles if we plan to use stdout or stderr reroute - CREATE_BREAKAWAY_FROM_JOB, - nullptr, // same environment as parent - currentDirectory, // current directory - &si, &pi // startup and process information - ); + success = ::CreateProcess( + nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, + sp.currentDirectory.c_str(), &si, &pi); } - ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get()); - - delete [] commandLine; + const auto e = GetLastError(); + setPathEnv(oldPath); if (!success) { - throw windows_error("failed to start process"); + return e; } processHandle = pi.hProcess; threadHandle = pi.hThread; - return true; + + return ERROR_SUCCESS; } +std::wstring makeRightsDetails(const env::FileSecurity& fs) +{ + if (fs.rights.normalRights) { + return L"(normal rights)"; + } + + if (fs.rights.list.isEmpty()) { + return L"(none)"; + } + + std::wstring s = fs.rights.list.join("|").toStdWString(); + if (!fs.rights.hasExecute) { + s += L" (execute is missing)"; + } + + return s; +} -HANDLE startBinary(const QFileInfo &binary, - const QString &arguments, - const QDir ¤tDirectory, - bool hooked, - HANDLE stdOut, - HANDLE stdErr) +std::wstring makeDetails(const SpawnParameters& sp, DWORD code) { + const QFileInfo bin(QString::fromStdWString(sp.binary)); + std::wstring owner, rights; + + if (bin.isFile()) { + const auto fs = env::getFileSecurity(bin.absoluteFilePath()); + + if (fs.error.isEmpty()) { + owner = fs.owner.toStdWString(); + rights = makeRightsDetails(fs); + } else { + owner = fs.error.toStdWString(); + rights = fs.error.toStdWString(); + } + } else { + owner = L"(file not found)"; + rights = L"(file not found)"; + } + + const bool cwdExists = (sp.currentDirectory.empty() ? + true : QFileInfo(QString::fromStdWString(sp.currentDirectory)).isDir()); + + const auto appDir = QCoreApplication::applicationDirPath(); + const auto sep = QDir::separator(); + + const std::wstring usvfs_x86_dll = + QFileInfo(appDir + sep + "usvfs_x86.dll").isFile() ? L"ok" : L"not found"; + + const std::wstring usvfs_x64_dll = + QFileInfo(appDir + sep + "usvfs_x64.dll").isFile() ? L"ok" : L"not found"; + + const std::wstring usvfs_x86_proxy = + QFileInfo(appDir + sep + "usvfs_proxy_x86.exe").isFile() ? L"ok" : L"not found"; + + const std::wstring usvfs_x64_proxy = + QFileInfo(appDir + sep + "usvfs_proxy_x64.exe").isFile() ? L"ok" : L"not found"; + + std::wstring elevated; + if (auto b=env::Environment().windowsInfo().isElevated()) { + elevated = (*b ? L"yes" : L"no"); + } else { + elevated = L"(not available)"; + } + + return fmt::format( + L"Error {code} {codename}: {error}\n" + L" . binary: '{bin}'\n" + L" . owner: {owner}\n" + L" . rights: {rights}\n" + L" . arguments: '{args}'\n" + L" . cwd: '{cwd}'{cwdexists}\n" + L" . stdout: {stdout}, stderr: {stderr}, suspended: {susp}, hooked: {hooked}\n" + L" . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}\n" + L" . MO elevated: {elevated}", + fmt::arg(L"code", code), + fmt::arg(L"codename", errorCodeName(code)), + fmt::arg(L"bin", sp.binary), + fmt::arg(L"owner", owner), + fmt::arg(L"rights", rights), + fmt::arg(L"error", formatSystemMessage(code)), + fmt::arg(L"args", sp.arguments), + fmt::arg(L"cwd", sp.currentDirectory), + fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")), + fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")), + fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")), + fmt::arg(L"susp", (sp.suspended ? L"yes" : L"no")), + fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")), + fmt::arg(L"x86_dll", usvfs_x86_dll), + fmt::arg(L"x64_dll", usvfs_x64_dll), + fmt::arg(L"x86_proxy", usvfs_x86_proxy), + fmt::arg(L"x64_proxy", usvfs_x64_proxy), + fmt::arg(L"elevated", elevated)); +} + +void spawnFailed(const SpawnParameters& sp, DWORD code) +{ + const auto details = QString::fromStdWString(makeDetails(sp, code)); + log::error("{}", details); + + const auto binary = QFileInfo(QString::fromStdWString(sp.binary)); + + const auto title = QObject::tr("Cannot launch program"); + + const auto mainText = QObject::tr("Cannot start %1") + .arg(binary.fileName()); + + QString content; + + if (code == ERROR_INVALID_PARAMETER) { + content = QObject::tr( + "This error typically happens because an antivirus has deleted critical " + "files from Mod Organizer's installation folder or has made them " + "generally inaccessible. Add an exclusion for Mod Organizer's " + "installation folder in your antivirus, reinstall Mod Organizer and try " + "again."); + } else if (code == ERROR_ACCESS_DENIED) { + content = QObject::tr( + "This error typically happens because an antivirus is preventing Mod " + "Organizer from starting programs. Add an exclusion for Mod Organizer's " + "installation folder in your antivirus and try again."); + } else { + content = QString::fromStdWString(formatSystemMessage(code)); + } + + QWidget *window = qApp->activeWindow(); + if ((window != nullptr) && (!window->isVisible())) { + window = nullptr; + } + + MOBase::TaskDialog(window, title) + .main(mainText) + .content(content) + .details(details) + .exec(); +} + +bool confirmRestartAsAdmin(const SpawnParameters& sp) +{ + const auto details = QString::fromStdWString( + makeDetails(sp, ERROR_ELEVATION_REQUIRED)); + + log::error("{}", details); + + const auto binary = QFileInfo(QString::fromStdWString(sp.binary)); + + const auto title = QObject::tr("Elevation required"); + + const auto mainText = QObject::tr("Cannot start %1") + .arg(binary.fileName()); + + const auto content = QObject::tr( + "This program is requesting to run as administrator but Mod Organizer " + "itself is not running as administrator. Running programs as administrator " + "is typically unnecessary as long as the game and Mod Organizer have been " + "installed outside \"Program Files\".\r\n\r\n" + "You can restart Mod Organizer as administrator and try launching the " + "program again."); + + + QWidget *window = qApp->activeWindow(); + if ((window != nullptr) && (!window->isVisible())) { + window = nullptr; + } + + log::debug("asking user to restart MO as administrator"); + + const auto r = MOBase::TaskDialog(window, title) + .main(mainText) + .content(content) + .details(details) + .button({ + QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr("You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); + + return (r == QMessageBox::Yes); +} + +} // namespace + +void startBinaryAdmin(const SpawnParameters& sp) +{ + if (!confirmRestartAsAdmin(sp)) { + log::debug("user declined"); + return; + } + + log::info("restarting MO as administrator"); + + WCHAR cwd[MAX_PATH] = {}; + if (!GetCurrentDirectory(MAX_PATH, cwd)) { + cwd[0] = L'\0'; + } + + if (Helper::adminLaunch( + qApp->applicationDirPath().toStdWString(), + qApp->applicationFilePath().toStdWString(), + std::wstring(cwd))) { + qApp->exit(0); + } +} + +HANDLE startBinary( + const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, + bool hooked, HANDLE stdOut, HANDLE stdErr) +{ + SpawnParameters sp; + + sp.binary = QDir::toNativeSeparators(binary.absoluteFilePath()).toStdWString(); + sp.arguments = arguments.toStdWString(); + sp.currentDirectory = QDir::toNativeSeparators(currentDirectory.absolutePath()).toStdWString(); + sp.suspended = true; + sp.hooked = hooked; + sp.stdOut = stdOut; + sp.stdErr = stdErr; + HANDLE processHandle, threadHandle; - std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); - std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); + const auto e = spawn(sp, processHandle, threadHandle); - try { - if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), - true, hooked, stdOut, stdErr, processHandle, threadHandle)) { - reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; + switch (e) + { + case ERROR_SUCCESS: + { + ::CloseHandle(threadHandle); + return processHandle; } - } catch (const windows_error &e) { - if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { - if (QMessageBox::question(QApplication::activeModalWidget(), QObject::tr("Elevation required"), - QObject::tr("This process requires elevation to run.\n" - "This is a potential security risk so I highly advise you to investigate if\n" - "\"%1\"\n" - "can be installed to work without elevation.\n\n" - "Restart Mod Organizer as an elevated process?\n" - "You will be asked if you want to allow helper.exe to make changes to the system. " - "You will need to relaunch the process above manually.").arg( - QDir::toNativeSeparators(binary.absoluteFilePath())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(::GetLastError())); - cwd[0] = L'\0'; - } - if (!Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) { - return INVALID_HANDLE_VALUE; - } - qApp->exit(0); - } + + case ERROR_ELEVATION_REQUIRED: + { + startBinaryAdmin(sp); return INVALID_HANDLE_VALUE; + } - } else { - reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); + default: + { + spawnFailed(sp, e); return INVALID_HANDLE_VALUE; } } - - ::CloseHandle(threadHandle); - return processHandle; } diff --git a/src/spawn.h b/src/spawn.h index c2d99bdb..9a2dbfbd 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -26,25 +26,6 @@ along with Mod Organizer. If not, see . #include #include - -/** - * @brief a dirty little trick so we can issue a clean restart from startBinary - * @note unused - */ -/*class ExitProxy : public QObject { - Q_OBJECT -public: - static ExitProxy *instance(); - void emitExit(); -signals: - void exit(); -private: - ExitProxy() {} -private: - static ExitProxy *s_Instance; -};*/ - - /** * @brief spawn a binary with Mod Organizer injected * -- cgit v1.3.1 From 971cecf343777894cec5144da11d16ef97d0db31 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Sep 2019 01:09:40 -0400 Subject: split spawnBinaryProcess() into spawn, no changes --- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 286 ++-------------------------------------------- src/spawn.cpp | 310 +++++++++++++++++++++++++++++++++++++++++++++++++- src/spawn.h | 18 +++ 4 files changed, 338 insertions(+), 278 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 054262e9..6737e330 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6434,7 +6434,7 @@ void MainWindow::on_bossButton_clicked() return; } - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), + HANDLE loot = spawn::startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), parameters.join(" "), qApp->applicationDirPath() + "/loot", true, diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 91e16716..9403b47d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -135,34 +135,6 @@ static DWORD getProcessParentID(DWORD pid) return res; } -static void startSteam(QWidget *widget) -{ - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", - QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - exe = QString("\"%1\"").arg(exe); - // See if username and password supplied. If so, pass them into steam. - QStringList args; - QString username; - QString password; - if (Settings::instance().steam().login(username, password)) { - args << "-login"; - args << username; - if (password != "") { - args << password; - } - } - if (!QProcess::startDetached(exe, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); - } else { - QMessageBox::information( - widget, QObject::tr("Waiting"), - QObject::tr("Please press OK once you're logged into steam.")); - } - } -} - template QStringList toStringList(InputIterator current, InputIterator end) { @@ -173,86 +145,6 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } -bool checkService() -{ - SC_HANDLE serviceManagerHandle = NULL; - SC_HANDLE serviceHandle = NULL; - LPSERVICE_STATUS_PROCESS serviceStatus = NULL; - LPQUERY_SERVICE_CONFIG serviceConfig = NULL; - bool serviceRunning = true; - - DWORD bytesNeeded; - - try { - serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceManagerHandle) { - log::warn("failed to open service manager (query status) (error {})", GetLastError()); - throw 1; - } - - serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceHandle) { - log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); - throw 2; - } - - if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service config (error {})", GetLastError()); - throw 3; - } - - DWORD serviceConfigSize = bytesNeeded; - serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); - if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { - log::warn("failed to query service config (error {})", GetLastError()); - throw 4; - } - - if (serviceConfig->dwStartType == SERVICE_DISABLED) { - log::error("Windows Event Log service is disabled!"); - serviceRunning = false; - } - - if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service status (error {})", GetLastError()); - throw 5; - } - - DWORD serviceStatusSize = bytesNeeded; - serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); - if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { - log::warn("failed to query service status (error {})", GetLastError()); - throw 6; - } - - if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { - log::error("Windows Event Log service is not running"); - serviceRunning = false; - } - } - catch (int e) { - UNUSED_VAR(e); - serviceRunning = false; - } - - if (serviceStatus) { - LocalFree(serviceStatus); - } - if (serviceConfig) { - LocalFree(serviceConfig); - } - if (serviceHandle) { - CloseServiceHandle(serviceHandle); - } - if (serviceManagerHandle) { - CloseServiceHandle(serviceManagerHandle); - } - - return serviceRunning; -} - OrganizerCore::OrganizerCore(Settings &settings) : m_UserInterface(nullptr) @@ -361,67 +253,6 @@ void OrganizerCore::storeSettings() } } -bool OrganizerCore::testForSteam(bool *found, bool *access) -{ - HANDLE hProcessSnap; - HANDLE hProcess; - PROCESSENTRY32 pe32; - DWORD lastError; - - if (found == nullptr || access == nullptr) { - return false; - } - - // Take a snapshot of all processes in the system. - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) { - lastError = GetLastError(); - log::error("unable to get snapshot of processes (error {})", lastError); - return false; - } - - // Retrieve information about the first process, - // and exit if unsuccessful - pe32.dwSize = sizeof(PROCESSENTRY32); - if (!Process32First(hProcessSnap, &pe32)) { - lastError = GetLastError(); - log::error("unable to get first process (error {})", lastError); - CloseHandle(hProcessSnap); - return false; - } - - *found = false; - *access = true; - - // Now walk the snapshot of processes, and - // display information about each process in turn - do { - if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) || - (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) { - - *found = true; - - // Try to open the process to determine if MO has the proper access - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, pe32.th32ProcessID); - if (hProcess == NULL) { - lastError = GetLastError(); - if (lastError == ERROR_ACCESS_DENIED) { - *access = false; - } - } else { - CloseHandle(hProcess); - } - break; - } - -} while(Process32Next(hProcessSnap, &pe32)); - -CloseHandle(hProcessSnap); -return true; - -} - void OrganizerCore::updateExecutablesList() { if (m_PluginContainer == nullptr) { @@ -1453,93 +1284,18 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, { prepareStart(); - if (!binary.exists()) { - reportError( - tr("Executable not found: %1").arg(qUtf8Printable(binary.absoluteFilePath()))); - return INVALID_HANDLE_VALUE; - } - - if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); - } else { - ::SetEnvironmentVariableW(L"SteamAPPId", - ToWString(m_Settings.steam().appID()).c_str()); - } - QWidget *window = qApp->activeWindow(); if ((window != nullptr) && (!window->isVisible())) { window = nullptr; } - // This could possibly be extracted somewhere else but it's probably for when - // we have more than one provider of game registration. - if ((QFileInfo( - managedGame()->gameDirectory().absoluteFilePath("steam_api.dll")) - .exists() - || QFileInfo(managedGame()->gameDirectory().absoluteFilePath( - "steam_api64.dll")) - .exists()) - && (m_Settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { - - bool steamFound = true; - bool steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } - - if (!steamFound) { - QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(window, "steamQuery", binary.fileName(), - tr("Start Steam?"), - tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); - if (result == QDialogButtonBox::Yes) { - startSteam(window); - - // double-check that Steam is started and MO has access - steamFound = true; - steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } else if (!steamFound) { - log::error("could not find Steam"); - } - } else if (result == QDialogButtonBox::Cancel) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkBinary(binary)) { + return INVALID_HANDLE_VALUE; + } - if (!steamAccess) { - QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(window, "steamAdminQuery", binary.fileName(), - tr("Steam: Access Denied"), - tr("MO was denied access to the Steam process. This normally indicates that " - "Steam is being run as administrator while MO is not. This can cause issues " - "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely necessary.\n\n" - "Restart MO as administrator?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); - if (result == QDialogButtonBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - log::error("unable to get current directory (error {})", GetLastError()); - cwd[0] = L'\0'; - } - if (!Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) { - log::error("unable to relaunch MO as admin"); - return INVALID_HANDLE_VALUE; - } - qApp->exit(0); - return INVALID_HANDLE_VALUE; - } else if (result == QDialogButtonBox::Cancel) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkSteam(window, managedGame()->gameDirectory(), binary, steamAppID, m_Settings)) { + return INVALID_HANDLE_VALUE; } while (m_DirectoryUpdate) { @@ -1566,32 +1322,12 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, return INVALID_HANDLE_VALUE; } - // Check if the Windows Event Logging service is running. For some reason, this seems to be - // critical to the successful running of usvfs. - if (!checkService()) { - if (QuestionBoxMemory::query(window, QString("eventLogService"), binary.fileName(), - tr("Windows Event Log Error"), - tr("The Windows Event Log service is disabled and/or not running. This prevents" - " USVFS from running properly. Your mods may not be working in the executable" - " that you are launching. Note that you may have to restart MO and/or your PC" - " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return INVALID_HANDLE_VALUE; - } + if (!spawn::checkEnvironment(window, binary)) { + return INVALID_HANDLE_VALUE; } - for (auto exec : settings().executablesBlacklist().split(";")) { - if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) { - if (QuestionBoxMemory::query(window, QString("blacklistedExecutable"), binary.fileName(), - tr("Blacklisted Executable"), - tr("The executable you are attempted to launch is blacklisted in the virtual file" - " system. This will likely prevent the executable, and any executables that are" - " launched by this one, from seeing any mods. This could extend to INI files, save" - " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return INVALID_HANDLE_VALUE; - } - } + if (!spawn::checkBlacklist(window, m_Settings, binary)) { + return INVALID_HANDLE_VALUE; } QString modsPath = settings().paths().mods(); @@ -1628,11 +1364,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, log::debug("Spawning proxyed process <{}>", cmdline); - return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), + return spawn::startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, QCoreApplication::applicationDirPath(), true); } else { log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return startBinary(binary, arguments, currentDirectory, true); + return spawn::startBinary(binary, arguments, currentDirectory, true); } } else { log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); diff --git a/src/spawn.cpp b/src/spawn.cpp index 614bc92f..4376e5bf 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "env.h" #include "envwindows.h" #include "envsecurity.h" +#include "settings.h" #include #include #include @@ -41,6 +42,10 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +namespace spawn +{ + +// details namespace { @@ -320,8 +325,6 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) return (r == QMessageBox::Yes); } -} // namespace - void startBinaryAdmin(const SpawnParameters& sp) { if (!confirmRestartAsAdmin(sp)) { @@ -344,6 +347,307 @@ void startBinaryAdmin(const SpawnParameters& sp) } } +} // namespace + + +bool checkBinary(const QFileInfo& binary) +{ + if (!binary.exists()) { + reportError( + QObject::tr("Executable not found: %1") + .arg(qUtf8Printable(binary.absoluteFilePath()))); + + return false; + } + + return true; +} + +bool testForSteam(bool *found, bool *access) +{ + HANDLE hProcessSnap; + HANDLE hProcess; + PROCESSENTRY32 pe32; + DWORD lastError; + + if (found == nullptr || access == nullptr) { + return false; + } + + // Take a snapshot of all processes in the system. + hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap == INVALID_HANDLE_VALUE) { + lastError = GetLastError(); + log::error("unable to get snapshot of processes (error {})", lastError); + return false; + } + + // Retrieve information about the first process, + // and exit if unsuccessful + pe32.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(hProcessSnap, &pe32)) { + lastError = GetLastError(); + log::error("unable to get first process (error {})", lastError); + CloseHandle(hProcessSnap); + return false; + } + + *found = false; + *access = true; + + // Now walk the snapshot of processes, and + // display information about each process in turn + do { + if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) || + (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) { + + *found = true; + + // Try to open the process to determine if MO has the proper access + hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, pe32.th32ProcessID); + if (hProcess == NULL) { + lastError = GetLastError(); + if (lastError == ERROR_ACCESS_DENIED) { + *access = false; + } + } else { + CloseHandle(hProcess); + } + break; + } + + } while(Process32Next(hProcessSnap, &pe32)); + + CloseHandle(hProcessSnap); + return true; +} + +void startSteam(QWidget *widget) +{ + QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", + QSettings::NativeFormat); + QString exe = steamSettings.value("SteamExe", "").toString(); + if (!exe.isEmpty()) { + exe = QString("\"%1\"").arg(exe); + // See if username and password supplied. If so, pass them into steam. + QStringList args; + QString username; + QString password; + if (Settings::instance().steam().login(username, password)) { + args << "-login"; + args << username; + if (password != "") { + args << password; + } + } + if (!QProcess::startDetached(exe, args)) { + reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); + } else { + QMessageBox::information( + widget, QObject::tr("Waiting"), + QObject::tr("Please press OK once you're logged into steam.")); + } + } +} + +bool checkSteam( + QWidget* parent, const QDir& gameDirectory, + const QFileInfo &binary, const QString &steamAppID, const Settings& settings) +{ + if (!steamAppID.isEmpty()) { + ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); + } else { + ::SetEnvironmentVariableW(L"SteamAPPId", + ToWString(settings.steam().appID()).c_str()); + } + + if ((QFileInfo(gameDirectory.absoluteFilePath("steam_api.dll")).exists() || + QFileInfo(gameDirectory.absoluteFilePath("steam_api64.dll")).exists()) + && (settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { + + bool steamFound = true; + bool steamAccess = true; + if (!testForSteam(&steamFound, &steamAccess)) { + log::error("unable to determine state of Steam"); + } + + if (!steamFound) { + QDialogButtonBox::StandardButton result; + result = QuestionBoxMemory::query(parent, "steamQuery", binary.fileName(), + QObject::tr("Start Steam?"), + QObject::tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); + if (result == QDialogButtonBox::Yes) { + startSteam(parent); + + // double-check that Steam is started and MO has access + steamFound = true; + steamAccess = true; + if (!testForSteam(&steamFound, &steamAccess)) { + log::error("unable to determine state of Steam"); + } else if (!steamFound) { + log::error("could not find Steam"); + } + + } else if (result == QDialogButtonBox::Cancel) { + return false; + } + } + + if (!steamAccess) { + QDialogButtonBox::StandardButton result; + result = QuestionBoxMemory::query(parent, "steamAdminQuery", binary.fileName(), + QObject::tr("Steam: Access Denied"), + QObject::tr("MO was denied access to the Steam process. This normally indicates that " + "Steam is being run as administrator while MO is not. This can cause issues " + "launching the game. It is recommended to not run Steam as administrator unless " + "absolutely necessary.\n\n" + "Restart MO as administrator?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); + if (result == QDialogButtonBox::Yes) { + WCHAR cwd[MAX_PATH]; + if (!GetCurrentDirectory(MAX_PATH, cwd)) { + log::error("unable to get current directory (error {})", GetLastError()); + cwd[0] = L'\0'; + } + if (!Helper::adminLaunch( + qApp->applicationDirPath().toStdWString(), + qApp->applicationFilePath().toStdWString(), + std::wstring(cwd))) { + log::error("unable to relaunch MO as admin"); + return false; + } + qApp->exit(0); + return false; + } else if (result == QDialogButtonBox::Cancel) { + return false; + } + } + } + + return true; +} + +bool checkService() +{ + SC_HANDLE serviceManagerHandle = NULL; + SC_HANDLE serviceHandle = NULL; + LPSERVICE_STATUS_PROCESS serviceStatus = NULL; + LPQUERY_SERVICE_CONFIG serviceConfig = NULL; + bool serviceRunning = true; + + DWORD bytesNeeded; + + try { + serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); + if (!serviceManagerHandle) { + log::warn("failed to open service manager (query status) (error {})", GetLastError()); + throw 1; + } + + serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); + if (!serviceHandle) { + log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); + throw 2; + } + + if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) + || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + log::warn("failed to get size of service config (error {})", GetLastError()); + throw 3; + } + + DWORD serviceConfigSize = bytesNeeded; + serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); + if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { + log::warn("failed to query service config (error {})", GetLastError()); + throw 4; + } + + if (serviceConfig->dwStartType == SERVICE_DISABLED) { + log::error("Windows Event Log service is disabled!"); + serviceRunning = false; + } + + if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) + || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { + log::warn("failed to get size of service status (error {})", GetLastError()); + throw 5; + } + + DWORD serviceStatusSize = bytesNeeded; + serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); + if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { + log::warn("failed to query service status (error {})", GetLastError()); + throw 6; + } + + if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { + log::error("Windows Event Log service is not running"); + serviceRunning = false; + } + } + catch (int) { + serviceRunning = false; + } + + if (serviceStatus) { + LocalFree(serviceStatus); + } + if (serviceConfig) { + LocalFree(serviceConfig); + } + if (serviceHandle) { + CloseServiceHandle(serviceHandle); + } + if (serviceManagerHandle) { + CloseServiceHandle(serviceManagerHandle); + } + + return serviceRunning; +} + +bool checkEnvironment(QWidget* parent, const QFileInfo& binary) +{ + // Check if the Windows Event Logging service is running. For some reason, this seems to be + // critical to the successful running of usvfs. + if (!checkService()) { + if (QuestionBoxMemory::query(parent, QString("eventLogService"), binary.fileName(), + QObject::tr("Windows Event Log Error"), + QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" + " USVFS from running properly. Your mods may not be working in the executable" + " that you are launching. Note that you may have to restart MO and/or your PC" + " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { + return false; + } + } + + return true; +} + +bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& binary) +{ + for (auto exec : settings.executablesBlacklist().split(";")) { + if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) { + if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), binary.fileName(), + QObject::tr("Blacklisted Executable"), + QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file" + " system. This will likely prevent the executable, and any executables that are" + " launched by this one, from seeing any mods. This could extend to INI files, save" + " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { + return false; + } + } + } + + return true; +} + + HANDLE startBinary( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool hooked, HANDLE stdOut, HANDLE stdErr) @@ -382,3 +686,5 @@ HANDLE startBinary( } } } + +} // namespace \ No newline at end of file diff --git a/src/spawn.h b/src/spawn.h index 9a2dbfbd..9a5afb4a 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -26,6 +26,22 @@ along with Mod Organizer. If not, see . #include #include +class Settings; + +namespace spawn +{ + +bool checkBinary(const QFileInfo& binary); + +bool checkSteam( + QWidget* parent, const QDir& gameDirectory, + const QFileInfo &binary, const QString &steamAppID, const Settings& settings); + +bool checkEnvironment(QWidget* parent, const QFileInfo& binary); + +bool checkBlacklist( + QWidget* parent, const Settings& settings, const QFileInfo& binary); + /** * @brief spawn a binary with Mod Organizer injected * @@ -45,5 +61,7 @@ HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool hooked, HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); +} // namespace + #endif // SPAWN_H -- cgit v1.3.1 From bf3d7527801bcba16ba01735efd3d5acc052f485 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Sep 2019 01:48:48 -0400 Subject: made SpawnParameters public, changed to Qt types removed 'suspended', not used --- src/mainwindow.cpp | 12 ++++--- src/organizercore.cpp | 24 ++++++++++---- src/spawn.cpp | 92 ++++++++++++++++++--------------------------------- src/spawn.h | 46 +++++++++++++++----------- 4 files changed, 83 insertions(+), 91 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6737e330..7774d87d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6434,11 +6434,13 @@ void MainWindow::on_bossButton_clicked() return; } - HANDLE loot = spawn::startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), - parameters.join(" "), - qApp->applicationDirPath() + "/loot", - true, - stdOutWrite); + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.stdOut = stdOutWrite; + + HANDLE loot = spawn::startBinary(this, sp); // we don't use the write end ::CloseHandle(stdOutWrite); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 9403b47d..dd4edbce 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1282,6 +1282,13 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, const QString &customOverwrite, const QList &forcedLibraries) { + spawn::SpawnParameters sp; + sp.binary = binary; + sp.arguments = arguments; + sp.currentDirectory = currentDirectory; + sp.hooked = true; + + prepareStart(); QWidget *window = qApp->activeWindow(); @@ -1290,11 +1297,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } - if (!spawn::checkBinary(binary)) { + if (!spawn::checkBinary(window, sp)) { return INVALID_HANDLE_VALUE; } - if (!spawn::checkSteam(window, managedGame()->gameDirectory(), binary, steamAppID, m_Settings)) { + if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) { return INVALID_HANDLE_VALUE; } @@ -1322,11 +1329,11 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, return INVALID_HANDLE_VALUE; } - if (!spawn::checkEnvironment(window, binary)) { + if (!spawn::checkEnvironment(window, sp)) { return INVALID_HANDLE_VALUE; } - if (!spawn::checkBlacklist(window, m_Settings, binary)) { + if (!spawn::checkBlacklist(window, sp, m_Settings)) { return INVALID_HANDLE_VALUE; } @@ -1364,11 +1371,14 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, log::debug("Spawning proxyed process <{}>", cmdline); - return spawn::startBinary(QFileInfo(QCoreApplication::applicationFilePath()), - cmdline, QCoreApplication::applicationDirPath(), true); + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + + return spawn::startBinary(window, sp); } else { log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return spawn::startBinary(binary, arguments, currentDirectory, true); + return spawn::startBinary(window, sp); } } else { log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); diff --git a/src/spawn.cpp b/src/spawn.cpp index 4376e5bf..a56df23a 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -49,17 +49,6 @@ namespace spawn namespace { -struct SpawnParameters -{ - std::wstring binary; - std::wstring arguments; - std::wstring currentDirectory; - bool suspended = false; - bool hooked = false; - HANDLE stdOut = INVALID_HANDLE_VALUE; - HANDLE stdErr = INVALID_HANDLE_VALUE; -}; - std::wstring pathEnv() { @@ -103,12 +92,11 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand si.dwFlags |= STARTF_USESTDHANDLES; } - std::wstring commandLine; + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); + std::wstring commandLine = L"\"" + bin + L"\""; if (sp.arguments[0] != L'\0') { - commandLine = L"\"" + sp.binary + L"\" " + sp.arguments; - } else { - commandLine = L"\"" + sp.binary + L"\""; + commandLine += L" " + sp.arguments.toStdWString(); } QString moPath = QCoreApplication::applicationDirPath(); @@ -119,16 +107,18 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand PROCESS_INFORMATION pi; BOOL success = FALSE; + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); + if (sp.hooked) { success = ::CreateProcessHooked( nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - sp.currentDirectory.c_str(), &si, &pi); + cwd.c_str(), &si, &pi); } else { success = ::CreateProcess( nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - sp.currentDirectory.c_str(), &si, &pi); + cwd.c_str(), &si, &pi); } const auto e = GetLastError(); @@ -164,11 +154,10 @@ std::wstring makeRightsDetails(const env::FileSecurity& fs) std::wstring makeDetails(const SpawnParameters& sp, DWORD code) { - const QFileInfo bin(QString::fromStdWString(sp.binary)); std::wstring owner, rights; - if (bin.isFile()) { - const auto fs = env::getFileSecurity(bin.absoluteFilePath()); + if (sp.binary.isFile()) { + const auto fs = env::getFileSecurity(sp.binary.absoluteFilePath()); if (fs.error.isEmpty()) { owner = fs.owner.toStdWString(); @@ -182,8 +171,8 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) rights = L"(file not found)"; } - const bool cwdExists = (sp.currentDirectory.empty() ? - true : QFileInfo(QString::fromStdWString(sp.currentDirectory)).isDir()); + const bool cwdExists = (sp.currentDirectory.isEmpty() ? + true : sp.currentDirectory.exists()); const auto appDir = QCoreApplication::applicationDirPath(); const auto sep = QDir::separator(); @@ -214,21 +203,20 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) L" . rights: {rights}\n" L" . arguments: '{args}'\n" L" . cwd: '{cwd}'{cwdexists}\n" - L" . stdout: {stdout}, stderr: {stderr}, suspended: {susp}, hooked: {hooked}\n" + L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n" L" . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}\n" L" . MO elevated: {elevated}", fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), - fmt::arg(L"bin", sp.binary), + fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), fmt::arg(L"owner", owner), fmt::arg(L"rights", rights), fmt::arg(L"error", formatSystemMessage(code)), - fmt::arg(L"args", sp.arguments), - fmt::arg(L"cwd", sp.currentDirectory), + fmt::arg(L"args", sp.arguments.toStdWString()), + fmt::arg(L"cwd", QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString()), fmt::arg(L"cwdexists", (cwdExists ? L"" : L" (not found)")), fmt::arg(L"stdout", (sp.stdOut == INVALID_HANDLE_VALUE ? L"no" : L"yes")), fmt::arg(L"stderr", (sp.stdErr == INVALID_HANDLE_VALUE ? L"no" : L"yes")), - fmt::arg(L"susp", (sp.suspended ? L"yes" : L"no")), fmt::arg(L"hooked", (sp.hooked ? L"yes" : L"no")), fmt::arg(L"x86_dll", usvfs_x86_dll), fmt::arg(L"x64_dll", usvfs_x64_dll), @@ -242,12 +230,10 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) const auto details = QString::fromStdWString(makeDetails(sp, code)); log::error("{}", details); - const auto binary = QFileInfo(QString::fromStdWString(sp.binary)); - const auto title = QObject::tr("Cannot launch program"); const auto mainText = QObject::tr("Cannot start %1") - .arg(binary.fileName()); + .arg(sp.binary.fileName()); QString content; @@ -286,12 +272,10 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) log::error("{}", details); - const auto binary = QFileInfo(QString::fromStdWString(sp.binary)); - const auto title = QObject::tr("Elevation required"); const auto mainText = QObject::tr("Cannot start %1") - .arg(binary.fileName()); + .arg(sp.binary.fileName()); const auto content = QObject::tr( "This program is requesting to run as administrator but Mod Organizer " @@ -350,12 +334,12 @@ void startBinaryAdmin(const SpawnParameters& sp) } // namespace -bool checkBinary(const QFileInfo& binary) +bool checkBinary(QWidget* parent, const SpawnParameters& sp) { - if (!binary.exists()) { + if (!sp.binary.exists()) { reportError( QObject::tr("Executable not found: %1") - .arg(qUtf8Printable(binary.absoluteFilePath()))); + .arg(sp.binary.absoluteFilePath())); return false; } @@ -452,8 +436,8 @@ void startSteam(QWidget *widget) } bool checkSteam( - QWidget* parent, const QDir& gameDirectory, - const QFileInfo &binary, const QString &steamAppID, const Settings& settings) + QWidget* parent, const SpawnParameters& sp, + const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) { if (!steamAppID.isEmpty()) { ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); @@ -474,7 +458,7 @@ bool checkSteam( if (!steamFound) { QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(parent, "steamQuery", binary.fileName(), + result = QuestionBoxMemory::query(parent, "steamQuery", sp.binary.fileName(), QObject::tr("Start Steam?"), QObject::tr("Steam is required to be running already to correctly start the game. " "Should MO try to start steam now?"), @@ -498,7 +482,7 @@ bool checkSteam( if (!steamAccess) { QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(parent, "steamAdminQuery", binary.fileName(), + result = QuestionBoxMemory::query(parent, "steamAdminQuery", sp.binary.fileName(), QObject::tr("Steam: Access Denied"), QObject::tr("MO was denied access to the Steam process. This normally indicates that " "Steam is being run as administrator while MO is not. This can cause issues " @@ -609,17 +593,17 @@ bool checkService() return serviceRunning; } -bool checkEnvironment(QWidget* parent, const QFileInfo& binary) +bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) { // Check if the Windows Event Logging service is running. For some reason, this seems to be // critical to the successful running of usvfs. if (!checkService()) { - if (QuestionBoxMemory::query(parent, QString("eventLogService"), binary.fileName(), + if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(), QObject::tr("Windows Event Log Error"), QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" " USVFS from running properly. Your mods may not be working in the executable" " that you are launching. Note that you may have to restart MO and/or your PC" - " after the service is fixed.\n\nContinue launching %1?").arg(binary.fileName()), + " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()), QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { return false; } @@ -628,16 +612,16 @@ bool checkEnvironment(QWidget* parent, const QFileInfo& binary) return true; } -bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& binary) +bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings) { for (auto exec : settings.executablesBlacklist().split(";")) { - if (exec.compare(binary.fileName(), Qt::CaseInsensitive) == 0) { - if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), binary.fileName(), + if (exec.compare(sp.binary.fileName(), Qt::CaseInsensitive) == 0) { + if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), sp.binary.fileName(), QObject::tr("Blacklisted Executable"), QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file" " system. This will likely prevent the executable, and any executables that are" " launched by this one, from seeing any mods. This could extend to INI files, save" - " games and any other virtualized files.\n\nContinue launching %1?").arg(binary.fileName()), + " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()), QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { return false; } @@ -648,20 +632,8 @@ bool checkBlacklist(QWidget* parent, const Settings& settings, const QFileInfo& } -HANDLE startBinary( - const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, - bool hooked, HANDLE stdOut, HANDLE stdErr) +HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { - SpawnParameters sp; - - sp.binary = QDir::toNativeSeparators(binary.absoluteFilePath()).toStdWString(); - sp.arguments = arguments.toStdWString(); - sp.currentDirectory = QDir::toNativeSeparators(currentDirectory.absolutePath()).toStdWString(); - sp.suspended = true; - sp.hooked = hooked; - sp.stdOut = stdOut; - sp.stdErr = stdErr; - HANDLE processHandle, threadHandle; const auto e = spawn(sp, processHandle, threadHandle); diff --git a/src/spawn.h b/src/spawn.h index 9a5afb4a..9398b6cc 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -31,20 +31,7 @@ class Settings; namespace spawn { -bool checkBinary(const QFileInfo& binary); - -bool checkSteam( - QWidget* parent, const QDir& gameDirectory, - const QFileInfo &binary, const QString &steamAppID, const Settings& settings); - -bool checkEnvironment(QWidget* parent, const QFileInfo& binary); - -bool checkBlacklist( - QWidget* parent, const Settings& settings, const QFileInfo& binary); - -/** - * @brief spawn a binary with Mod Organizer injected - * +/* * @param binary the binary to spawn * @param arguments arguments to pass to the binary * @param profileName name of the active profile @@ -53,13 +40,34 @@ bool checkBlacklist( * @param hooked if set, the binary is started with mo injected * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process +*/ +struct SpawnParameters +{ + QFileInfo binary; + QString arguments; + QDir currentDirectory; + bool hooked = false; + HANDLE stdOut = INVALID_HANDLE_VALUE; + HANDLE stdErr = INVALID_HANDLE_VALUE; +}; + + +bool checkBinary(QWidget* parent, const SpawnParameters& sp); + +bool checkSteam( + QWidget* parent, const SpawnParameters& sp, + const QDir& gameDirectory, const QString &steamAppID, const Settings& settings); + +bool checkEnvironment(QWidget* parent, const SpawnParameters& sp); + +bool checkBlacklist( + QWidget* parent, const SpawnParameters& sp, const Settings& settings); + +/** + * @brief spawn a binary with Mod Organizer injected * @return the process handle - * @todo is the profile name even used any more? - * @todo is the hooked parameter used? **/ -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, bool hooked, - HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); +HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); } // namespace -- cgit v1.3.1 From ac6bc5fd01e115d523de65a02e46b2cde1188d37 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Sep 2019 02:45:03 -0400 Subject: testForSteam() now uses env to get processes moved processes from env.cpp to envmodule.cpp, merged what crash dumps did with what was in testForSteam() --- src/env.cpp | 105 ++++++------------------------------------------------ src/env.h | 5 +++ src/envmodule.cpp | 81 +++++++++++++++++++++++++++++++++++++++++ src/envmodule.h | 24 ++++++++++++- src/spawn.cpp | 62 +++++++------------------------- 5 files changed, 131 insertions(+), 146 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index e2b85560..34f53294 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -72,6 +72,11 @@ const std::vector& Environment::loadedModules() const return m_modules; } +std::vector Environment::runningProcesses() const +{ + return getRunningProcesses(); +} + const WindowsInfo& Environment::windowsInfo() const { if (!m_windows) { @@ -166,18 +171,6 @@ void Environment::dumpDisks(const Settings& s) const } -struct Process -{ - std::wstring filename; - DWORD pid; - - Process(std::wstring f, DWORD id) - : filename(std::move(f)), pid(id) - { - } -}; - - // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) @@ -233,84 +226,6 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) return {}; } -std::vector runningProcessesIds() -{ - // double the buffer size 10 times - const int MaxTries = 10; - - // initial size of 300 processes, unlikely to be more than that - std::size_t size = 300; - - for (int tries=0; tries(size); - std::fill(ids.get(), ids.get() + size, 0); - - DWORD bytesGiven = static_cast(size * sizeof(ids[0])); - DWORD bytesWritten = 0; - - if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) - { - const auto e = GetLastError(); - - std::wcerr - << L"failed to enumerate processes, " - << formatSystemMessage(e) << L"\n"; - - return {}; - } - - if (bytesWritten == bytesGiven) { - // no way to distinguish between an exact fit and not enough space, - // just try again - size *= 2; - continue; - } - - const auto count = bytesWritten / sizeof(ids[0]); - return std::vector(ids.get(), ids.get() + count); - } - - std::cerr << L"too many processes to enumerate"; - return {}; -} - -std::vector runningProcesses() -{ - const auto pids = runningProcessesIds(); - std::vector v; - - for (const auto& pid : pids) { - if (pid == 0) { - // the idle process has pid 0 and seems to be picked up by EnumProcesses() - continue; - } - - HandlePtr h(OpenProcess( - PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); - - if (!h) { - const auto e = GetLastError(); - - if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot for system processes, even - // when elevated - std::wcerr - << L"failed to open process " << pid << L", " - << formatSystemMessage(e) << L"\n"; - } - - continue; - } - - auto filename = processFilename(h.get()); - if (!filename.empty()) { - v.emplace_back(std::move(filename), pid); - } - } - - return v; -} - DWORD findOtherPid() { const std::wstring defaultName = L"ModOrganizer.exe"; @@ -322,7 +237,7 @@ DWORD findOtherPid() std::wclog << L"this process id is " << thisPid << L"\n"; // getting the filename for this process, assumes the other process has the - // smae one + // same one auto filename = processFilename(); if (filename.empty()) { std::wcerr @@ -335,15 +250,15 @@ DWORD findOtherPid() } // getting all running processes - const auto processes = runningProcesses(); + const auto processes = getRunningProcesses(); std::wclog << L"there are " << processes.size() << L" processes running\n"; // going through processes, trying to find one with the same name and a // different pid than this process has for (const auto& p : processes) { - if (p.filename == filename) { - if (p.pid != thisPid) { - return p.pid; + if (p.name() == filename) { + if (p.pid() != thisPid) { + return p.pid(); } } } diff --git a/src/env.h b/src/env.h index 46095ca3..6222c86b 100644 --- a/src/env.h +++ b/src/env.h @@ -7,6 +7,7 @@ namespace env { class Module; +class Process; class SecurityProduct; class WindowsInfo; class Metrics; @@ -129,6 +130,10 @@ public: // const std::vector& loadedModules() const; + // list of running processes; not cached + // + std::vector runningProcesses() const; + // information about the operating system // const WindowsInfo& windowsInfo() const; diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 8cea414a..3f1f8912 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -320,6 +320,40 @@ QString Module::getMD5() const } +Process::Process(DWORD pid, QString name) + : m_pid(pid), m_name(std::move(name)) +{ +} + +DWORD Process::pid() const +{ + return m_pid; +} + +const QString& Process::name() const +{ + return m_name; +} + +// whether this process can be accessed; fails if the current process doesn't +// have the proper permissions +// +bool Process::canAccess() const +{ + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_pid)); + + if (!h) { + const auto e = GetLastError(); + if (e == ERROR_ACCESS_DENIED) { + return false; + } + } + + return true; +} + + std::vector getLoadedModules() { HandlePtr snapshot(CreateToolhelp32Snapshot( @@ -373,4 +407,51 @@ std::vector getLoadedModules() return v; } + +std::vector getRunningProcesses() +{ + HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); + return {}; + } + + PROCESSENTRY32 entry = {}; + entry.dwSize = sizeof(entry); + + // first process, this shouldn't fail because there's at least one process + // running + if (!Process32First(snapshot.get(), &entry)) { + const auto e = GetLastError(); + log::error("Process32First() failed, {}", formatSystemMessage(e)); + return {}; + } + + std::vector v; + + for (;;) + { + v.push_back(Process( + entry.th32ProcessID, + QString::fromStdWString(entry.szExeFile))); + + // next process + if (!Process32Next(snapshot.get(), &entry)) + { + const auto e = GetLastError(); + + // no more processes is not an error + if (e != ERROR_NO_MORE_FILES) + log::error("Process32Next() failed, {}", formatSystemMessage(e)); + + break; + } + } + + return v; +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index 3f0f99ab..deb7520f 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -12,7 +12,7 @@ namespace env class Module { public: - explicit Module(QString path, std::size_t fileSize); + Module(QString path, std::size_t fileSize); // returns the module's path // @@ -96,6 +96,28 @@ private: }; +// represents one process +// +class Process +{ +public: + Process(DWORD pid, QString name); + + DWORD pid() const; + const QString& name() const; + + // whether this process can be accessed; fails if the current process doesn't + // have the proper permissions + // + bool canAccess() const; + +private: + DWORD m_pid; + QString m_name; +}; + + +std::vector getRunningProcesses(); std::vector getLoadedModules(); } // namespace env diff --git a/src/spawn.cpp b/src/spawn.cpp index a56df23a..604f9ffa 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "env.h" #include "envwindows.h" #include "envsecurity.h" +#include "envmodule.h" #include "settings.h" #include #include @@ -49,7 +50,6 @@ namespace spawn namespace { - std::wstring pathEnv() { std::wstring s(4000, L' '); @@ -249,6 +249,9 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) "This error typically happens because an antivirus is preventing Mod " "Organizer from starting programs. Add an exclusion for Mod Organizer's " "installation folder in your antivirus and try again."); + } else if (code == ERROR_FILE_NOT_FOUND) { + content = QObject::tr("The file '%1' does not exist.") + .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath())); } else { content = QString::fromStdWString(formatSystemMessage(code)); } @@ -337,10 +340,7 @@ void startBinaryAdmin(const SpawnParameters& sp) bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { - reportError( - QObject::tr("Executable not found: %1") - .arg(sp.binary.absoluteFilePath())); - + spawnFailed(sp, ERROR_FILE_NOT_FOUND); return false; } @@ -349,61 +349,23 @@ bool checkBinary(QWidget* parent, const SpawnParameters& sp) bool testForSteam(bool *found, bool *access) { - HANDLE hProcessSnap; - HANDLE hProcess; - PROCESSENTRY32 pe32; - DWORD lastError; - if (found == nullptr || access == nullptr) { return false; } - // Take a snapshot of all processes in the system. - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) { - lastError = GetLastError(); - log::error("unable to get snapshot of processes (error {})", lastError); - return false; - } - - // Retrieve information about the first process, - // and exit if unsuccessful - pe32.dwSize = sizeof(PROCESSENTRY32); - if (!Process32First(hProcessSnap, &pe32)) { - lastError = GetLastError(); - log::error("unable to get first process (error {})", lastError); - CloseHandle(hProcessSnap); - return false; - } - + const auto ps = env::Environment().runningProcesses(); *found = false; - *access = true; - - // Now walk the snapshot of processes, and - // display information about each process in turn - do { - if ((_tcsicmp(pe32.szExeFile, L"Steam.exe") == 0) || - (_tcsicmp(pe32.szExeFile, L"SteamService.exe") == 0)) { + for (const auto& p : ps) { + if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) || + (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0)) + { *found = true; - - // Try to open the process to determine if MO has the proper access - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, - FALSE, pe32.th32ProcessID); - if (hProcess == NULL) { - lastError = GetLastError(); - if (lastError == ERROR_ACCESS_DENIED) { - *access = false; - } - } else { - CloseHandle(hProcess); - } + *access = p.canAccess(); break; } + } - } while(Process32Next(hProcessSnap, &pe32)); - - CloseHandle(hProcessSnap); return true; } -- cgit v1.3.1 From b867b0bccdb32d94e1646d8f3f8e8a39d4b2446e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Sep 2019 03:49:17 -0400 Subject: refactored steam handling --- src/spawn.cpp | 247 +++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 159 insertions(+), 88 deletions(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index 604f9ffa..80f82a28 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -329,7 +329,9 @@ void startBinaryAdmin(const SpawnParameters& sp) if (Helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) { + std::wstring(cwd))) + { + log::debug("exiting MO"); qApp->exit(0); } } @@ -347,129 +349,198 @@ bool checkBinary(QWidget* parent, const SpawnParameters& sp) return true; } -bool testForSteam(bool *found, bool *access) +struct SteamStatus { - if (found == nullptr || access == nullptr) { - return false; - } + bool running=false; + bool accessible=false; +}; + +SteamStatus getSteamStatus() +{ + SteamStatus ss; const auto ps = env::Environment().runningProcesses(); - *found = false; for (const auto& p : ps) { if ((p.name().compare("Steam.exe", Qt::CaseInsensitive) == 0) || (p.name().compare("SteamService.exe", Qt::CaseInsensitive) == 0)) { - *found = true; - *access = p.canAccess(); + ss.running = true; + ss.accessible = p.canAccess(); + + log::debug( + "'{}' is running, accessible={}", + p.name(), (ss.accessible ? "yes" : "no")); + break; } } - return true; + return ss; } -void startSteam(QWidget *widget) +bool startSteam(QWidget *widget) { - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", - QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - exe = QString("\"%1\"").arg(exe); - // See if username and password supplied. If so, pass them into steam. - QStringList args; - QString username; - QString password; - if (Settings::instance().steam().login(username, password)) { - args << "-login"; - args << username; - if (password != "") { - args << password; - } + log::debug("starting steam"); + + const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam"; + const QString valueName = "SteamExe"; + + const QSettings steamSettings(keyName, QSettings::NativeFormat); + const QString exe = steamSettings.value(valueName, "").toString(); + + if (exe.isEmpty()) { + log::error( + "can't start steam, registry value at '{}' is empty", + keyName + "\\" + valueName); + + return false; + } + + const QString program = QString("\"%1\"").arg(exe); + + // See if username and password supplied. If so, pass them into steam. + QStringList args; + QString username, password; + if (Settings::instance().steam().login(username, password)) { + args.push_back("-login"); + args.push_back(username); + + if (password != "") { + args.push_back(password); } - if (!QProcess::startDetached(exe, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); - } else { - QMessageBox::information( - widget, QObject::tr("Waiting"), - QObject::tr("Please press OK once you're logged into steam.")); + } + + log::debug( + "starting steam process:\n" + " . program: '{}'\n" + " . username={}, password={}", + program, + (username.isEmpty() ? "no" : "yes"), + (password.isEmpty() ? "no" : "yes")); + + if (!QProcess::startDetached(program, args)) { + reportError(QObject::tr("Failed to start \"%1\"").arg(program)); + return false; + } + + QMessageBox::information( + widget, QObject::tr("Waiting"), + QObject::tr("Please press OK once you're logged into steam.")); + + return true; +} + +bool gameRequiresSteam(const QDir& gameDirectory, const Settings& settings) +{ + static const std::vector files = { + "steam_api.dll", "steam_api64.dll" + }; + + for (const auto& file : files) { + const QFileInfo fi(gameDirectory.absoluteFilePath(file)); + if (fi.exists()) { + log::debug("found '{}'", fi.absoluteFilePath()); + return true; } } + + return false; +} + +QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) +{ + return QuestionBoxMemory::query( + parent, "steamQuery", sp.binary.fileName(), + QObject::tr("Start Steam?"), + QObject::tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); +} + +QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) +{ + return QuestionBoxMemory::query( + parent, "steamAdminQuery", sp.binary.fileName(), + QObject::tr("Steam: Access Denied"), + QObject::tr("MO was denied access to the Steam process. This normally indicates that " + "Steam is being run as administrator while MO is not. This can cause issues " + "launching the game. It is recommended to not run Steam as administrator unless " + "absolutely necessary.\n\n" + "Restart MO as administrator?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); } bool checkSteam( QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) { + log::debug("checking steam"); + if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); + ::SetEnvironmentVariableW(L"SteamAPPId", steamAppID.toStdWString().c_str()); } else { - ::SetEnvironmentVariableW(L"SteamAPPId", - ToWString(settings.steam().appID()).c_str()); + ::SetEnvironmentVariableW(L"SteamAPPId", settings.steam().appID().toStdWString().c_str()); } - if ((QFileInfo(gameDirectory.absoluteFilePath("steam_api.dll")).exists() || - QFileInfo(gameDirectory.absoluteFilePath("steam_api64.dll")).exists()) - && (settings.game().loadMechanismType() == LoadMechanism::LOAD_MODORGANIZER)) { + if (!gameRequiresSteam(gameDirectory, settings)) { + log::debug("games doesn't seem to require steam"); + return true; + } - bool steamFound = true; - bool steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } + auto ss = getSteamStatus(); - if (!steamFound) { - QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(parent, "steamQuery", sp.binary.fileName(), - QObject::tr("Start Steam?"), - QObject::tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); - if (result == QDialogButtonBox::Yes) { - startSteam(parent); - - // double-check that Steam is started and MO has access - steamFound = true; - steamAccess = true; - if (!testForSteam(&steamFound, &steamAccess)) { - log::error("unable to determine state of Steam"); - } else if (!steamFound) { - log::error("could not find Steam"); - } - - } else if (result == QDialogButtonBox::Cancel) { - return false; + if (!ss.running) { + log::debug("steam isn't running, asking to start steam"); + const auto c = confirmStartSteam(parent, sp); + + if (c == QDialogButtonBox::Yes) { + log::debug("user wants to start steam"); + startSteam(parent); + + // double-check that Steam is started + ss = getSteamStatus(); + if (!ss.running) { + log::error("could not start steam, continuing and hoping for the best"); + return true; } + } else if (c == QDialogButtonBox::No) { + log::debug("user declined to start steam"); + return true; + } else { + log::debug("user cancelled"); + return false; } + } + + if (ss.running && !ss.accessible) { + log::debug("steam is running but is not accessible, asking to restart MO"); + const auto c = confirmRestartAsAdminForSteam(parent, sp); - if (!steamAccess) { - QDialogButtonBox::StandardButton result; - result = QuestionBoxMemory::query(parent, "steamAdminQuery", sp.binary.fileName(), - QObject::tr("Steam: Access Denied"), - QObject::tr("MO was denied access to the Steam process. This normally indicates that " - "Steam is being run as administrator while MO is not. This can cause issues " - "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely necessary.\n\n" - "Restart MO as administrator?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); - if (result == QDialogButtonBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - log::error("unable to get current directory (error {})", GetLastError()); - cwd[0] = L'\0'; - } - if (!Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) { - log::error("unable to relaunch MO as admin"); - return false; - } + if (c == QDialogButtonBox::Yes) { + WCHAR cwd[MAX_PATH]; + if (!GetCurrentDirectory(MAX_PATH, cwd)) { + cwd[0] = L'\0'; + } + + if (Helper::adminLaunch( + qApp->applicationDirPath().toStdWString(), + qApp->applicationFilePath().toStdWString(), + std::wstring(cwd))) + { + log::debug("exiting MO"); qApp->exit(0); return false; - } else if (result == QDialogButtonBox::Cancel) { - return false; } + + log::error("unable to relaunch MO as admin"); + return false; + } else if (c == QDialogButtonBox::No) { + log::debug("user declined to restart MO, continuing"); + return true; + } else { + log::debug("user cancelled"); + return false; } } -- cgit v1.3.1 From 09b95e39434b9efc49a606957c94cd42309b7fb6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 13 Sep 2019 15:15:18 -0400 Subject: moved all spawn dialogs into a namespace starting steam with spawn() instead of QProcess dialogs for bad steam registry key and failure refactored credentials code, added logging add environment variables to env --- src/env.cpp | 42 +++++ src/env.h | 12 +- src/organizercore.cpp | 4 - src/settingsutilities.cpp | 148 ++++++++++++----- src/settingsutilities.h | 4 +- src/spawn.cpp | 410 +++++++++++++++++++++++++--------------------- 6 files changed, 383 insertions(+), 237 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 34f53294..1aaaa8ef 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -171,6 +171,48 @@ void Environment::dumpDisks(const Settings& s) const } +QString path() +{ + return get("PATH"); +} + +QString addPath(const QString& s) +{ + auto old = path(); + set("PATH", get("PATH") + ";" + s); + return old; +} + +QString setPath(const QString& s) +{ + return set("PATH", s); +} + +QString get(const QString& name) +{ + std::wstring s(4000, L' '); + + DWORD realSize = ::GetEnvironmentVariableW( + name.toStdWString().c_str(), s.data(), static_cast(s.size())); + + if (realSize > s.size()) { + s.resize(realSize); + + ::GetEnvironmentVariableW( + name.toStdWString().c_str(), s.data(), static_cast(s.size())); + } + + return QString::fromStdWString(s); +} + +QString set(const QString& n, const QString& v) +{ + auto old = get(n); + ::SetEnvironmentVariableW(n.toStdWString().c_str(), v.toStdWString().c_str()); + return old; +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index 6222c86b..7bdc9b85 100644 --- a/src/env.h +++ b/src/env.h @@ -162,6 +162,16 @@ private: }; +// environment variables +// +QString get(const QString& name); +QString set(const QString& name, const QString& value); + +QString path(); +QString addPath(const QString& s); +QString setPath(const QString& s); + + enum class CoreDumpTypes { Mini = 1, @@ -169,7 +179,7 @@ enum class CoreDumpTypes Full }; -// creates a minidump file for the given process +// creates a minidump file for this process // bool coredump(CoreDumpTypes type); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dd4edbce..fbc9083b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1288,7 +1288,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, sp.currentDirectory = currentDirectory; sp.hooked = true; - prepareStart(); QWidget *window = qApp->activeWindow(); @@ -1296,7 +1295,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, window = nullptr; } - if (!spawn::checkBinary(window, sp)) { return INVALID_HANDLE_VALUE; } @@ -1369,8 +1367,6 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, .arg(QDir::toNativeSeparators(cwdPath), QDir::toNativeSeparators(binPath), arguments); - log::debug("Spawning proxyed process <{}>", cmdline); - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); sp.arguments = cmdline; sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 7a9dcc35..6c99a602 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -213,55 +213,117 @@ void warnIfNotCheckable(const QAbstractButton* b) } -bool setWindowsCredential(const QString key, const QString data) +QString credentialName(const QString& key) { - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - bool result = false; - if (data.isEmpty()) { - result = CredDeleteW(keyData, CRED_TYPE_GENERIC, 0); - if (!result) - if (GetLastError() == ERROR_NOT_FOUND) - result = true; - } else { - wchar_t* charData = new wchar_t[data.size()]; - data.toWCharArray(charData); - - CREDENTIALW cred = {}; - cred.Flags = 0; - cred.Type = CRED_TYPE_GENERIC; - cred.TargetName = keyData; - cred.CredentialBlob = (LPBYTE)charData; - cred.CredentialBlobSize = sizeof(wchar_t) * data.size(); - cred.Persist = CRED_PERSIST_LOCAL_MACHINE; - - result = CredWriteW(&cred, 0); - delete[] charData; + return "ModOrganizer2_" + key; +} + +bool deleteWindowsCredential(const QString& key) +{ + const auto credName = credentialName(key); + + if (!CredDeleteW(credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0)) { + const auto e = GetLastError(); + if (e == ERROR_NOT_FOUND) { + // not an error if the key already doesn't exist + log::debug("can't delete windows credential {}, doesn't exist", credName); + return true; + } else { + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + return false; + } } - delete[] keyData; - return result; + + log::debug("deleted windows credential {}", credName); + + return true; } -QString getWindowsCredential(const QString key) -{ - QString result; - QString finalKey("ModOrganizer2_" + key); - wchar_t* keyData = new wchar_t[finalKey.size()+1]; - finalKey.toWCharArray(keyData); - keyData[finalKey.size()] = L'\0'; - PCREDENTIALW creds; - if (CredReadW(keyData, 1, 0, &creds)) { - wchar_t *charData = (wchar_t *)creds->CredentialBlob; - result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); - CredFree(creds); - } else { +bool addWindowsCredential(const QString& key, const QString& data) +{ + const auto credName = credentialName(key); + + const auto wname = credName.toStdWString(); + const auto wdata = data.toStdWString(); + + const auto* blob = reinterpret_cast(wdata.data()); + const auto blobSize = wdata.size() * sizeof(decltype(wdata)::value_type); + + CREDENTIALW cred = {}; + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = const_cast(wname.c_str()); + cred.CredentialBlob = const_cast(blob); + cred.CredentialBlobSize = static_cast(blobSize); + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + + if (!CredWriteW(&cred, 0)) { const auto e = GetLastError(); + + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + + return false; + } + + log::debug("added windows credential {}", credName); + + return true; +} + +struct CredentialFreer +{ + void operator()(CREDENTIALW* c) + { + if (c) { + CredFree(c); + } + } +}; + +using CredentialPtr = std::unique_ptr; + +QString getWindowsCredential(const QString& key) +{ + const QString credName = credentialName(key); + + CREDENTIALW* rawCreds = nullptr; + + const auto ret = CredReadW( + credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0, &rawCreds); + + CredentialPtr creds(rawCreds); + + if (!ret) { + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { - log::error("Retrieving encrypted data failed: {}", formatSystemMessage(e)); + log::error( + "failed to retrieve windows credential {}: {}", + credName, formatSystemMessage(e)); } + + return {}; + } + + QString value; + if (creds->CredentialBlob) { + value = QString::fromWCharArray( + reinterpret_cast(creds->CredentialBlob), + creds->CredentialBlobSize / sizeof(wchar_t)); + } + + return value; +} + +bool setWindowsCredential(const QString& key, const QString& data) +{ + if (data.isEmpty()) { + return deleteWindowsCredential(key); + } else { + return addWindowsCredential(key, data); } - delete[] keyData; - return result; } diff --git a/src/settingsutilities.h b/src/settingsutilities.h index c3eef12f..a6737144 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -270,7 +270,7 @@ QString checkedSettingName(const QAbstractButton* b); void warnIfNotCheckable(const QAbstractButton* b); -bool setWindowsCredential(const QString key, const QString data); -QString getWindowsCredential(const QString key); +bool setWindowsCredential(const QString& key, const QString& data); +QString getWindowsCredential(const QString& key); #endif // SETTINGSUTILITIES_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 80f82a28..94737871 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -43,96 +43,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -namespace spawn -{ - -// details -namespace -{ - -std::wstring pathEnv() +namespace spawn::dialogs { - std::wstring s(4000, L' '); - - DWORD realSize = ::GetEnvironmentVariableW( - L"PATH", s.data(), static_cast(s.size())); - - if (realSize > s.size()) { - s.resize(realSize); - - ::GetEnvironmentVariableW( - TEXT("PATH"), s.data(), static_cast(s.size())); - } - - return s; -} - -void setPathEnv(const std::wstring& s) -{ - ::SetEnvironmentVariableW(L"PATH", s.c_str()); -} - -DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) -{ - BOOL inheritHandles = FALSE; - - STARTUPINFO si = {}; - si.cb = sizeof(si); - - // inherit handles if we plan to use stdout or stderr reroute - if (sp.stdOut != INVALID_HANDLE_VALUE) { - si.hStdOutput = sp.stdOut; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - - if (sp.stdErr != INVALID_HANDLE_VALUE) { - si.hStdError = sp.stdErr; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - - const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); - - std::wstring commandLine = L"\"" + bin + L"\""; - if (sp.arguments[0] != L'\0') { - commandLine += L" " + sp.arguments.toStdWString(); - } - - QString moPath = QCoreApplication::applicationDirPath(); - - const auto oldPath = pathEnv(); - setPathEnv(oldPath + L";" + QDir::toNativeSeparators(moPath).toStdWString()); - - PROCESS_INFORMATION pi; - BOOL success = FALSE; - - const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); - - if (sp.hooked) { - success = ::CreateProcessHooked( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); - } else { - success = ::CreateProcess( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); - } - - const auto e = GetLastError(); - setPathEnv(oldPath); - - if (!success) { - return e; - } - - processHandle = pi.hProcess; - threadHandle = pi.hThread; - - return ERROR_SUCCESS; -} std::wstring makeRightsDetails(const env::FileSecurity& fs) { @@ -196,7 +108,7 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) elevated = L"(not available)"; } - return fmt::format( + std::wstring f = L"Error {code} {codename}: {error}\n" L" . binary: '{bin}'\n" L" . owner: {owner}\n" @@ -204,8 +116,13 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) L" . arguments: '{args}'\n" L" . cwd: '{cwd}'{cwdexists}\n" L" . stdout: {stdout}, stderr: {stderr}, hooked: {hooked}\n" - L" . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}\n" - L" . MO elevated: {elevated}", + L" . MO elevated: {elevated}"; + + if (sp.hooked) { + f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}"; + } + + return fmt::format(f, fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), @@ -225,36 +142,82 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) fmt::arg(L"elevated", elevated)); } -void spawnFailed(const SpawnParameters& sp, DWORD code) +QString makeContent(const SpawnParameters& sp, DWORD code) { - const auto details = QString::fromStdWString(makeDetails(sp, code)); - log::error("{}", details); - - const auto title = QObject::tr("Cannot launch program"); - - const auto mainText = QObject::tr("Cannot start %1") - .arg(sp.binary.fileName()); - - QString content; - if (code == ERROR_INVALID_PARAMETER) { - content = QObject::tr( + return QObject::tr( "This error typically happens because an antivirus has deleted critical " "files from Mod Organizer's installation folder or has made them " "generally inaccessible. Add an exclusion for Mod Organizer's " "installation folder in your antivirus, reinstall Mod Organizer and try " "again."); } else if (code == ERROR_ACCESS_DENIED) { - content = QObject::tr( + return QObject::tr( "This error typically happens because an antivirus is preventing Mod " "Organizer from starting programs. Add an exclusion for Mod Organizer's " "installation folder in your antivirus and try again."); } else if (code == ERROR_FILE_NOT_FOUND) { - content = QObject::tr("The file '%1' does not exist.") + return QObject::tr("The file '%1' does not exist.") .arg(QDir::toNativeSeparators(sp.binary.absoluteFilePath())); } else { - content = QString::fromStdWString(formatSystemMessage(code)); + return QString::fromStdWString(formatSystemMessage(code)); } +} + +QMessageBox::StandardButton badSteamReg( + QWidget* parent, const QString& keyName, const QString& valueName) +{ + const auto details = QString( + "can't start steam, registry value at '%1' is empty or doesn't exist") + .arg(keyName + "\\" + valueName); + + log::error("{}", details); + + return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) + .main(QObject::tr("Cannot start Steam")) + .content(QObject::tr( + "The path to the Steam executable cannot be found. You might try " + "reinstalling Steam.")) + .details(details) + .button({ + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); +} + +QMessageBox::StandardButton startSteamFailed( + QWidget* parent, const SpawnParameters& sp, DWORD e) +{ + const auto details = makeDetails(sp, e); + log::error("{}", details); + + return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) + .main(QObject::tr("Cannot start Steam")) + .content(makeContent(sp, e)) + .details(QString::fromStdWString(details)) + .button({ + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); +} + +void spawnFailed(const SpawnParameters& sp, DWORD code) +{ + const auto details = QString::fromStdWString(makeDetails(sp, code)); + log::error("{}", details); + + const auto title = QObject::tr("Cannot launch program"); + + const auto mainText = QObject::tr("Cannot start %1") + .arg(sp.binary.fileName()); QWidget *window = qApp->activeWindow(); if ((window != nullptr) && (!window->isVisible())) { @@ -263,7 +226,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) MOBase::TaskDialog(window, title) .main(mainText) - .content(content) + .content(makeContent(sp, code)) .details(details) .exec(); } @@ -301,48 +264,143 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) .content(content) .details(details) .button({ - QObject::tr("Restart Mod Organizer as administrator"), - QObject::tr("You must allow \"helper.exe\" to make changes to the system."), - QMessageBox::Yes}) + QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr("You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); return (r == QMessageBox::Yes); } -void startBinaryAdmin(const SpawnParameters& sp) +QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) { - if (!confirmRestartAsAdmin(sp)) { - log::debug("user declined"); - return; + return QuestionBoxMemory::query( + parent, "steamQuery", sp.binary.fileName(), + QObject::tr("Start Steam?"), + QObject::tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); +} + +QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) +{ + return QuestionBoxMemory::query( + parent, "steamAdminQuery", sp.binary.fileName(), + QObject::tr("Steam: Access Denied"), + QObject::tr("MO was denied access to the Steam process. This normally indicates that " + "Steam is being run as administrator while MO is not. This can cause issues " + "launching the game. It is recommended to not run Steam as administrator unless " + "absolutely necessary.\n\n" + "Restart MO as administrator?"), + QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); +} + +} // namepsace + + +namespace spawn +{ + +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) +{ + BOOL inheritHandles = FALSE; + + STARTUPINFO si = {}; + si.cb = sizeof(si); + + // inherit handles if we plan to use stdout or stderr reroute + if (sp.stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = sp.stdOut; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; } - log::info("restarting MO as administrator"); + if (sp.stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = sp.stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); + + std::wstring commandLine = L"\"" + bin + L"\""; + if (sp.arguments[0] != L'\0') { + commandLine += L" " + sp.arguments.toStdWString(); + } + + QString moPath = QCoreApplication::applicationDirPath(); + const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); + + PROCESS_INFORMATION pi; + BOOL success = FALSE; + + if (sp.hooked) { + success = ::CreateProcessHooked( + nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, + cwd.c_str(), &si, &pi); + } else { + success = ::CreateProcess( + nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, + cwd.c_str(), &si, &pi); + } + + const auto e = GetLastError(); + env::setPath(oldPath); + + if (!success) { + return e; + } + processHandle = pi.hProcess; + threadHandle = pi.hThread; + + return ERROR_SUCCESS; +} + +bool restartAsAdmin() +{ WCHAR cwd[MAX_PATH] = {}; if (!GetCurrentDirectory(MAX_PATH, cwd)) { cwd[0] = L'\0'; } - if (Helper::adminLaunch( + if (!Helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - log::debug("exiting MO"); - qApp->exit(0); + // todo + log::error("admin launch failed"); + return false; } + + log::debug("exiting MO"); + qApp->exit(0); + return true; } -} // namespace +void startBinaryAdmin(const SpawnParameters& sp) +{ + if (!dialogs::confirmRestartAsAdmin(sp)) { + log::debug("user declined"); + return; + } + + log::info("restarting MO as administrator"); + restartAsAdmin(); +} bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { - spawnFailed(sp, ERROR_FILE_NOT_FOUND); + dialogs::spawnFailed(sp, ERROR_FILE_NOT_FOUND); return false; } @@ -379,10 +437,23 @@ SteamStatus getSteamStatus() return ss; } -bool startSteam(QWidget *widget) +QString makeSteamArguments(const QString& username, const QString& password) { - log::debug("starting steam"); + QString args; + + if (username != "") { + args += "-login " + username; + + if (password != "") { + args += " " + password; + } + } + + return args; +} +bool startSteam(QWidget* parent) +{ const QString keyName = "HKEY_CURRENT_USER\\Software\\Valve\\Steam"; const QString valueName = "SteamExe"; @@ -390,42 +461,41 @@ bool startSteam(QWidget *widget) const QString exe = steamSettings.value(valueName, "").toString(); if (exe.isEmpty()) { - log::error( - "can't start steam, registry value at '{}' is empty", - keyName + "\\" + valueName); - - return false; + return (dialogs::badSteamReg(parent, keyName, valueName) == QMessageBox::Yes); } - const QString program = QString("\"%1\"").arg(exe); + SpawnParameters sp; + sp.binary = exe; // See if username and password supplied. If so, pass them into steam. - QStringList args; QString username, password; if (Settings::instance().steam().login(username, password)) { - args.push_back("-login"); - args.push_back(username); - - if (password != "") { - args.push_back(password); - } + sp.arguments = makeSteamArguments(username, password); } log::debug( "starting steam process:\n" " . program: '{}'\n" " . username={}, password={}", - program, + sp.binary.filePath().toStdString(), (username.isEmpty() ? "no" : "yes"), (password.isEmpty() ? "no" : "yes")); - if (!QProcess::startDetached(program, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(program)); - return false; + HANDLE ph = INVALID_HANDLE_VALUE; + HANDLE th = INVALID_HANDLE_VALUE; + const auto e = spawn(sp, ph, th); + + if (e != ERROR_SUCCESS) { + // make sure username and passwords are not shown + sp.arguments = makeSteamArguments( + (username.isEmpty() ? "" : "USERNAME"), + (password.isEmpty() ? "" : "PASSWORD")); + + return (dialogs::startSteamFailed(parent, sp, e) == QMessageBox::Yes); } QMessageBox::information( - widget, QObject::tr("Waiting"), + parent, QObject::tr("Waiting"), QObject::tr("Please press OK once you're logged into steam.")); return true; @@ -448,29 +518,6 @@ bool gameRequiresSteam(const QDir& gameDirectory, const Settings& settings) return false; } -QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) -{ - return QuestionBoxMemory::query( - parent, "steamQuery", sp.binary.fileName(), - QObject::tr("Start Steam?"), - QObject::tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); -} - -QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) -{ - return QuestionBoxMemory::query( - parent, "steamAdminQuery", sp.binary.fileName(), - QObject::tr("Steam: Access Denied"), - QObject::tr("MO was denied access to the Steam process. This normally indicates that " - "Steam is being run as administrator while MO is not. This can cause issues " - "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely necessary.\n\n" - "Restart MO as administrator?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); -} - bool checkSteam( QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) @@ -492,16 +539,20 @@ bool checkSteam( if (!ss.running) { log::debug("steam isn't running, asking to start steam"); - const auto c = confirmStartSteam(parent, sp); + const auto c = dialogs::confirmStartSteam(parent, sp); if (c == QDialogButtonBox::Yes) { log::debug("user wants to start steam"); - startSteam(parent); + + if (!startSteam(parent)) { + // cancel + return false; + } // double-check that Steam is started ss = getSteamStatus(); if (!ss.running) { - log::error("could not start steam, continuing and hoping for the best"); + log::error("steam is still not running, continuing and hoping for the best"); return true; } } else if (c == QDialogButtonBox::No) { @@ -515,25 +566,10 @@ bool checkSteam( if (ss.running && !ss.accessible) { log::debug("steam is running but is not accessible, asking to restart MO"); - const auto c = confirmRestartAsAdminForSteam(parent, sp); + const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp); if (c == QDialogButtonBox::Yes) { - WCHAR cwd[MAX_PATH]; - if (!GetCurrentDirectory(MAX_PATH, cwd)) { - cwd[0] = L'\0'; - } - - if (Helper::adminLaunch( - qApp->applicationDirPath().toStdWString(), - qApp->applicationFilePath().toStdWString(), - std::wstring(cwd))) - { - log::debug("exiting MO"); - qApp->exit(0); - return false; - } - - log::error("unable to relaunch MO as admin"); + restartAsAdmin(); return false; } else if (c == QDialogButtonBox::No) { log::debug("user declined to restart MO, continuing"); @@ -686,7 +722,7 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) default: { - spawnFailed(sp, e); + dialogs::spawnFailed(sp, e); return INVALID_HANDLE_VALUE; } } -- cgit v1.3.1 From 0ecac8f4c49d99f072481114e886b2e05851e704 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Mon, 16 Sep 2019 16:45:11 -0500 Subject: Add placeholder text to notification dialog A lot of new users miss the fact you can click on a notification to get more details about that notification. This directs them to click on stuff. --- src/problemsdialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/problemsdialog.ui b/src/problemsdialog.ui index a1712d4a..a474f73b 100644 --- a/src/problemsdialog.ui +++ b/src/problemsdialog.ui @@ -50,7 +50,7 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click a notification above to get more details...</p></body></html>
-- cgit v1.3.1 From 1fec9da2d3b89710d508f79fee583187cf88f549 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 17 Sep 2019 00:08:11 -0500 Subject: Automatically fill in the name of new executables When the binary browse button is used to select a binary, this will use the binary name to fill in the executable name if the previous executable name stated with "New Executable". --- src/editexecutablesdialog.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 7823fadc..d7ae4e77 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -619,8 +619,9 @@ void EditExecutablesDialog::on_browseBinary_clicked() ui->binary->setText(QDir::toNativeSeparators(binaryName)); } - // setting title if currently empty - if (ui->title->text().isEmpty()) { + // setting title if currently empty or some variation of "New Executable" + if (ui->title->text().isEmpty() || + ui->title->text().startsWith("New Executable", Qt::CaseInsensitive)) { const auto prefix = QFileInfo(binaryName).baseName(); const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); -- cgit v1.3.1 From ea203352408865b1d13a00463151ec7c17a99096 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 17 Sep 2019 00:19:12 -0500 Subject: Add translation to "New Executable" --- src/editexecutablesdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index d7ae4e77..0d6367b8 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -621,7 +621,7 @@ void EditExecutablesDialog::on_browseBinary_clicked() // setting title if currently empty or some variation of "New Executable" if (ui->title->text().isEmpty() || - ui->title->text().startsWith("New Executable", Qt::CaseInsensitive)) { + ui->title->text().startsWith(tr("New Executable"), Qt::CaseInsensitive)) { const auto prefix = QFileInfo(binaryName).baseName(); const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); -- cgit v1.3.1 From f69559fe0bd40629e66ecde6e362b73595d9bd2e Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 18 Sep 2019 15:31:43 -0500 Subject: Update version to 2.2.2alpha1 --- src/version.rc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 92370853..9bf6cc83 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,1 -#define VER_FILEVERSION_STR "2.2.1rc1\0" +#define VER_FILEVERSION 2,2,2 +#define VER_FILEVERSION_STR "2.2.2alpha1\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From c603681115b6071f241f6931685d36a92b6403f8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 10:07:52 -0400 Subject: moved helper stuff to spawn so it can reuse error handling removed unused helper::init() removed logging when deleting a credential that doesn't exist, happens all the time --- src/envsecurity.cpp | 2 +- src/helper.cpp | 107 +--------------------------- src/helper.h | 42 +---------- src/settingsdialogworkarounds.cpp | 2 +- src/settingsutilities.cpp | 18 ++--- src/spawn.cpp | 145 +++++++++++++++++++++++++++++++++++--- src/spawn.h | 25 +++++++ 7 files changed, 175 insertions(+), 166 deletions(-) (limited to 'src') diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 6e3fadbe..3b4cdcaa 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -275,7 +275,7 @@ std::vector getSecurityProductsFromWMI() } if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - log::error("productState is a {}, is not a VT_UI4", prop.vt); + log::error("productState is a {}, not a VT_UI4", prop.vt); return; } diff --git a/src/helper.cpp b/src/helper.cpp index 59a2d3d1..24446cb8 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -17,109 +17,4 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "helper.h" -#include "utility.h" -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include - -#include -#include - -using MOBase::reportError; - - -namespace Helper { - - -static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine, BOOL async) -{ - wchar_t fileName[MAX_PATH]; - _snwprintf(fileName, MAX_PATH, L"%ls\\helper.exe", moDirectory); - - SHELLEXECUTEINFOW execInfo = {0}; - - execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); - execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - execInfo.hwnd = nullptr; - execInfo.lpVerb = L"runas"; - execInfo.lpFile = fileName; - execInfo.lpParameters = commandLine; - execInfo.lpDirectory = moDirectory; - execInfo.nShow = SW_SHOW; - - ::ShellExecuteExW(&execInfo); - - if (execInfo.hProcess == 0) { - reportError(QObject::tr("helper failed")); - return false; - } - - if (async) { - return true; - } - - if (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0) { - reportError(QObject::tr("helper failed")); - return false; - } - - DWORD exitCode; - GetExitCodeProcess(execInfo.hProcess, &exitCode); - return exitCode == NOERROR; -} - - -bool init(const std::wstring &moPath, const std::wstring &dataPath) -{ - DWORD userNameLen = UNLEN + 1; - wchar_t userName[UNLEN + 1]; - - if (!GetUserName(userName, &userNameLen)) { - reportError(QObject::tr("failed to determine account name")); - return false; - } - wchar_t *commandLine = new wchar_t[32768]; - - _snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"", - dataPath.c_str(), userName); - - bool res = helperExec(moPath.c_str(), commandLine, FALSE); - delete [] commandLine; - - return res; -} - - -bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) -{ - wchar_t *commandLine = new wchar_t[32768]; - _snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"", - dataPath.c_str()); - - bool res = helperExec(moPath.c_str(), commandLine, FALSE); - delete [] commandLine; - - return res; -} - - -bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir) -{ - wchar_t *commandLine = new wchar_t[32768]; - _snwprintf(commandLine, 32768, L"adminLaunch %d \"%ls\" \"%ls\"", - ::GetCurrentProcessId(), - moFile.c_str(), - workingDir.c_str() - ); - - bool res = helperExec(moPath.c_str(), commandLine, TRUE); - delete [] commandLine; - - return res; -} - - -} // namespace +// moved to spawn.cpp diff --git a/src/helper.h b/src/helper.h index f6667a84..335b8647 100644 --- a/src/helper.h +++ b/src/helper.h @@ -20,45 +20,7 @@ along with Mod Organizer. If not, see . #ifndef HELPER_H #define HELPER_H - -#include - - -/** - * @brief Convenience functions to work with the external helper program. - * - * The mo_helper program is used to make changes on the system that require administrative - * rights, so that ModOrganizer itself can run without special privileges - **/ -namespace Helper { - -/** - * @brief initialise the specified directory for use with mod organizer. - * - * This will create all required sub-directories and give the user running ModOrganizer - * write-access - * - * @param moPath absolute path to the ModOrganizer base directory - * @return true on success - **/ -bool init(const std::wstring &moPath, const std::wstring &dataPath); - -/** - * @brief sets the last modified time for all .bsa-files in the target directory well into the past - * @param moPath absolute path to the modOrganizer base directory - * @param dataPath the path taht contains the .bsa-files, usually the data directory of the game - **/ -bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath); - -/** - * @brief waits for the current process to exit and restarts it as an administrator - * @param moPath absolute path to the modOrganizer base directory - * @param moFile file name of modOrganizer - * @param workingDir current working directory - **/ -bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir); - -} - +// all helper code moved to spawn.h and spawn.cpp +#include "spawn.h" #endif // HELPER_H diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 4d811e40..f89b021c 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -83,7 +83,7 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() const auto* game = qApp->property("managed_game").value(); QDir dir = game->dataDirectory(); - Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), + helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), dir.absolutePath().toStdWString()); } diff --git a/src/settingsutilities.cpp b/src/settingsutilities.cpp index 6c99a602..db7c1818 100644 --- a/src/settingsutilities.cpp +++ b/src/settingsutilities.cpp @@ -224,16 +224,18 @@ bool deleteWindowsCredential(const QString& key) if (!CredDeleteW(credName.toStdWString().c_str(), CRED_TYPE_GENERIC, 0)) { const auto e = GetLastError(); + + // not an error if the key already doesn't exist, and don't log it because + // it happens all the time when the settings dialog is closed since it + // doesn't check first if (e == ERROR_NOT_FOUND) { - // not an error if the key already doesn't exist - log::debug("can't delete windows credential {}, doesn't exist", credName); return true; - } else { - log::error( - "failed to delete windows credential {}, {}", - credName, formatSystemMessage(e)); - return false; } + + log::error( + "failed to delete windows credential {}, {}", + credName, formatSystemMessage(e)); + return false; } log::debug("deleted windows credential {}", credName); @@ -269,7 +271,7 @@ bool addWindowsCredential(const QString& key, const QString& data) return false; } - log::debug("added windows credential {}", credName); + log::debug("set windows credential {}", credName); return true; } diff --git a/src/spawn.cpp b/src/spawn.cpp index 94737871..6c524681 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -64,7 +64,7 @@ std::wstring makeRightsDetails(const env::FileSecurity& fs) return s; } -std::wstring makeDetails(const SpawnParameters& sp, DWORD code) +QString makeDetails(const SpawnParameters& sp, DWORD code, const QString& more={}) { std::wstring owner, rights; @@ -109,7 +109,7 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) } std::wstring f = - L"Error {code} {codename}: {error}\n" + L"Error {code} {codename}{more}: {error}\n" L" . binary: '{bin}'\n" L" . owner: {owner}\n" L" . rights: {rights}\n" @@ -122,9 +122,12 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) f += L"\n . usvfs x86:{x86_dll} x64:{x64_dll} proxy_x86:{x86_proxy} proxy_x64:{x64_proxy}"; } - return fmt::format(f, + const std::wstring wmore = (more.isEmpty() ? L"" : (", " + more).toStdWString()); + + const auto s = fmt::format(f, fmt::arg(L"code", code), fmt::arg(L"codename", errorCodeName(code)), + fmt::arg(L"more", wmore), fmt::arg(L"bin", QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString()), fmt::arg(L"owner", owner), fmt::arg(L"rights", rights), @@ -140,6 +143,8 @@ std::wstring makeDetails(const SpawnParameters& sp, DWORD code) fmt::arg(L"x86_proxy", usvfs_x86_proxy), fmt::arg(L"x64_proxy", usvfs_x64_proxy), fmt::arg(L"elevated", elevated)); + + return QString::fromStdWString(s); } QString makeContent(const SpawnParameters& sp, DWORD code) @@ -198,7 +203,7 @@ QMessageBox::StandardButton startSteamFailed( return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) .main(QObject::tr("Cannot start Steam")) .content(makeContent(sp, e)) - .details(QString::fromStdWString(details)) + .details(details) .button({ QObject::tr("Continue without starting Steam"), QObject::tr("The program may fail to launch."), @@ -211,7 +216,7 @@ QMessageBox::StandardButton startSteamFailed( void spawnFailed(const SpawnParameters& sp, DWORD code) { - const auto details = QString::fromStdWString(makeDetails(sp, code)); + const auto details = makeDetails(sp, code); log::error("{}", details); const auto title = QObject::tr("Cannot launch program"); @@ -231,10 +236,38 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) .exec(); } +void helperFailed( + DWORD code, const QString& why, const std::wstring& binary, + const std::wstring& cwd, const std::wstring& args) +{ + SpawnParameters sp; + sp.binary = QString::fromStdWString(binary); + sp.currentDirectory.setPath(QString::fromStdWString(cwd)); + sp.arguments = QString::fromStdWString(args); + + const auto details = makeDetails(sp, code, "in " + why); + log::error("{}", details); + + const auto title = QObject::tr("Cannot launch helper"); + + const auto mainText = QObject::tr("Cannot start %1") + .arg(sp.binary.fileName()); + + QWidget *window = qApp->activeWindow(); + if ((window != nullptr) && (!window->isVisible())) { + window = nullptr; + } + + MOBase::TaskDialog(window, title) + .main(mainText) + .content(makeContent(sp, code)) + .details(details) + .exec(); +} + bool confirmRestartAsAdmin(const SpawnParameters& sp) { - const auto details = QString::fromStdWString( - makeDetails(sp, ERROR_ELEVATION_REQUIRED)); + const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED); log::error("{}", details); @@ -370,18 +403,18 @@ bool restartAsAdmin() cwd[0] = L'\0'; } - if (!Helper::adminLaunch( + if (!helper::adminLaunch( qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) { - // todo log::error("admin launch failed"); return false; } log::debug("exiting MO"); qApp->exit(0); + return true; } @@ -728,4 +761,96 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } -} // namespace \ No newline at end of file +} // namespace + + + +namespace helper +{ + +bool helperExec( + const std::wstring& moDirectory, const std::wstring& commandLine, BOOL async) +{ + const std::wstring fileName = moDirectory + L"\\helper.exe"; + + env::HandlePtr process; + + { + SHELLEXECUTEINFOW execInfo = {}; + + ULONG flags = SEE_MASK_FLAG_NO_UI ; + if (!async) + flags |= SEE_MASK_NOCLOSEPROCESS; + + execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); + execInfo.fMask = flags; + execInfo.hwnd = 0; + execInfo.lpVerb = L"runas"; + execInfo.lpFile = fileName.c_str(); + execInfo.lpParameters = commandLine.c_str(); + execInfo.lpDirectory = moDirectory.c_str(); + execInfo.nShow = SW_SHOW; + + if (!::ShellExecuteExW(&execInfo) && execInfo.hProcess == 0) { + const auto e = GetLastError(); + + spawn::dialogs::helperFailed( + e, "ShellExecuteExW()", fileName, moDirectory, commandLine); + + return false; + } + + if (async) { + return true; + } + + process.reset(execInfo.hProcess); + } + + const auto r = ::WaitForSingleObject(process.get(), INFINITE); + + if (r != WAIT_OBJECT_0) { + // for WAIT_ABANDONED, the documentation doesn't mention that GetLastError() + // returns something meaningful, but code ERROR_ABANDONED_WAIT_0 exists, so + // use that instead + const auto code = (r == WAIT_ABANDONED ? + ERROR_ABANDONED_WAIT_0 : GetLastError()); + + spawn::dialogs::helperFailed( + code, "WaitForSingleObject()", fileName, moDirectory, commandLine); + + return false; + } + + DWORD exitCode = 0; + if (!GetExitCodeProcess(process.get(), &exitCode)) { + const auto e = GetLastError(); + + spawn::dialogs::helperFailed( + e, "GetExitCodeProcess()", fileName, moDirectory, commandLine); + + return false; + } + + return (exitCode == 0); +} + +bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) +{ + const std::wstring commandLine = fmt::format( + L"backdateBSA \"{}\"", dataPath); + + return helperExec(moPath, commandLine, FALSE); +} + + +bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir) +{ + const std::wstring commandLine = fmt::format( + L"adminLaunch {} \"{}\" \"{}\"", + ::GetCurrentProcessId(), moFile, workingDir); + + return helperExec(moPath, commandLine, true); +} + +} // namespace diff --git a/src/spawn.h b/src/spawn.h index 9398b6cc..da626329 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -71,5 +71,30 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); } // namespace + +// convenience functions to work with the external helper program, which is used +// to make changes on the system that require administrative rights, so that +// ModOrganizer itself can run without special privileges +// +namespace helper +{ + +/** +* @brief sets the last modified time for all .bsa-files in the target directory well into the past +* @param moPath absolute path to the modOrganizer base directory +* @param dataPath the path taht contains the .bsa-files, usually the data directory of the game +**/ +bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath); + +/** +* @brief waits for the current process to exit and restarts it as an administrator +* @param moPath absolute path to the modOrganizer base directory +* @param moFile file name of modOrganizer +* @param workingDir current working directory +**/ +bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir); + +} // namespace + #endif // SPAWN_H -- cgit v1.3.1 From c50722100c485d2945082d573158a7083efe2f23 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 10:11:56 -0400 Subject: removed helper.h and helper.cpp, merged into spawn --- src/CMakeLists.txt | 3 --- src/helper.cpp | 20 -------------------- src/helper.h | 26 -------------------------- src/main.cpp | 1 - src/organizercore.cpp | 1 - src/settingsdialogworkarounds.cpp | 2 +- src/spawn.cpp | 1 - 7 files changed, 1 insertion(+), 53 deletions(-) delete mode 100644 src/helper.cpp delete mode 100644 src/helper.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 60822834..7c29ff48 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -87,7 +87,6 @@ SET(organizer_SRCS waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp - helper.cpp filedialogmemory.cpp executableslist.cpp editexecutablesdialog.cpp @@ -208,7 +207,6 @@ SET(organizer_HDRS waitingonclosedialog.h loadmechanism.h installationmanager.h - helper.h filedialogmemory.h executableslist.h editexecutablesdialog.h @@ -464,7 +462,6 @@ set(utilities csvbuilder shared/error_report eventfilter - helper shared/leaktrace persistentcookiejar serverinfo diff --git a/src/helper.cpp b/src/helper.cpp deleted file mode 100644 index 24446cb8..00000000 --- a/src/helper.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -// moved to spawn.cpp diff --git a/src/helper.h b/src/helper.h deleted file mode 100644 index 335b8647..00000000 --- a/src/helper.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef HELPER_H -#define HELPER_H - -// all helper code moved to spawn.h and spawn.cpp -#include "spawn.h" - -#endif // HELPER_H diff --git a/src/main.cpp b/src/main.cpp index 74b04970..ba988ae3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,7 +38,6 @@ along with Mod Organizer. If not, see . #include "executableslist.h" #include "singleinstance.h" #include "utility.h" -#include "helper.h" #include "loglist.h" #include "selectiondialog.h" #include "moapplication.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index fbc9083b..0da5b604 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -33,7 +33,6 @@ #include "lockeddialog.h" #include "instancemanager.h" #include -#include "helper.h" #include "previewdialog.h" #include diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index f89b021c..ccbfcbfe 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -1,6 +1,6 @@ #include "settingsdialogworkarounds.h" #include "ui_settingsdialog.h" -#include "helper.h" +#include "spawn.h" #include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) diff --git a/src/spawn.cpp b/src/spawn.cpp index 6c524681..64766adf 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -33,7 +33,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include "helper.h" #include #include #include -- cgit v1.3.1 From c1ab18b614aa6212f942d8d91afa0b191802f599 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 11:14:14 -0400 Subject: moved the content of checkService() to env::getService(), refactored it --- src/env.cpp | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 58 +++++++++++++++ src/spawn.cpp | 92 ++++-------------------- 3 files changed, 301 insertions(+), 77 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 1aaaa8ef..78b5dc96 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -213,6 +213,234 @@ QString set(const QString& n, const QString& v) } +Service::Service(QString name) + : Service(std::move(name), StartType::None, Status::None) +{ +} + +Service::Service(QString name, StartType st, Status s) + : m_name(std::move(name)), m_startType(st), m_status(s) +{ +} + +const QString& Service::name() const +{ + return m_name; +} + +bool Service::isValid() const +{ + return (m_startType != StartType::None) && (m_status != Status::None); +} + +Service::StartType Service::startType() const +{ + return m_startType; +} + +Service::Status Service::status() const +{ + return m_status; +} + +QString Service::toString() const +{ + return QString("service '%1', start=%2, status=%3") + .arg(m_name) + .arg(env::toString(m_startType)) + .arg(env::toString(m_status)); +} + + +QString toString(Service::StartType st) +{ + using ST = Service::StartType; + + switch (st) + { + case ST::None: + return "none"; + + case ST::Disabled: + return "disabled"; + + case ST::Enabled: + return "enabled"; + + default: + return QString("unknown %1").arg(static_cast(st)); + } +} + +QString toString(Service::Status st) +{ + using S = Service::Status; + + switch (st) + { + case S::None: + return "none"; + + case S::Stopped: + return "stopped"; + + case S::Running: + return "running"; + + default: + return QString("unknown %1").arg(static_cast(st)); + } +} + +Service::StartType getServiceStartType(SC_HANDLE s, const QString& name) +{ + DWORD needed = 0; + + if (!QueryServiceConfig(s, NULL, 0, &needed)) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + log::error( + "QueryServiceConfig() for size for '{}' failed, {}", + name, GetLastError()); + + return Service::StartType::None; + } + } + + const auto size = needed; + MallocPtr config( + static_cast(std::malloc(size))); + + if (!QueryServiceConfig(s, config.get(), size, &needed)) { + const auto e = GetLastError(); + + log::error( + "QueryServiceConfig() for '{}' failed", name, formatSystemMessage(e)); + + return Service::StartType::None; + } + + + switch (config->dwStartType) + { + case SERVICE_AUTO_START: // fall-through + case SERVICE_BOOT_START: + case SERVICE_DEMAND_START: + case SERVICE_SYSTEM_START: + { + return Service::StartType::Enabled; + } + + case SERVICE_DISABLED: + { + return Service::StartType::Disabled; + } + + default: + { + log::error( + "unknown service start type {} for '{}'", + config->dwStartType, name); + + return Service::StartType::None; + } + } +} + +Service::Status getServiceStatus(SC_HANDLE s, const QString& name) +{ + DWORD needed = 0; + + if (!QueryServiceStatusEx(s, SC_STATUS_PROCESS_INFO, NULL, 0, &needed)) { + const auto e = GetLastError(); + + if (e != ERROR_INSUFFICIENT_BUFFER) { + log::error( + "QueryServiceStatusEx() for size for '{}' failed, {}", + name, GetLastError()); + + return Service::Status::None; + } + } + + const auto size = needed; + MallocPtr status( + static_cast(std::malloc(size))); + + const auto r = QueryServiceStatusEx( + s, SC_STATUS_PROCESS_INFO, reinterpret_cast(status.get()), + size, &needed); + + if (!r) { + const auto e = GetLastError(); + + log::error( + "QueryServiceStatusEx() failed for '{}', {}", + name, formatSystemMessage(e)); + + return Service::Status::None; + } + + + switch (status->dwCurrentState) + { + case SERVICE_START_PENDING: // fall-through + case SERVICE_CONTINUE_PENDING: + case SERVICE_RUNNING: + { + return Service::Status::Running; + } + + case SERVICE_STOPPED: // fall-through + case SERVICE_STOP_PENDING: + case SERVICE_PAUSE_PENDING: + case SERVICE_PAUSED: + { + return Service::Status::Stopped; + } + + default: + { + log::error( + "unknown service status {} for '{}'", + status->dwCurrentState, name); + + return Service::Status::None; + } + } +} + +Service getService(const QString& name) +{ + // service manager + const LocalPtr scm(OpenSCManager( + NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); + + if (!scm) { + const auto e = GetLastError(); + log::error("OpenSCManager() failed, {}", formatSystemMessage(e)); + return Service(name); + } + + // service + const LocalPtr s(OpenService( + scm.get(), name.toStdWString().c_str(), + SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG)); + + if (!s) { + const auto e = GetLastError(); + log::error("OpenService() failed for '{}', {}", name, formatSystemMessage(e)); + return Service(name); + } + + const auto startType = getServiceStartType(s.get(), name); + const auto status = getServiceStatus(s.get(), name); + + return {name, startType, status}; +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index 7bdc9b85..1760c7fe 100644 --- a/src/env.h +++ b/src/env.h @@ -93,6 +93,24 @@ struct MallocFreer template using MallocPtr = std::unique_ptr; + +// used by LocalPtr, calls LocalFree() as the deleter +// +template +struct LocalFreer +{ + using pointer = T; + + void operator()(T p) + { + ::LocalFree(p); + } +}; + +template +using LocalPtr = std::unique_ptr>; + + // creates a console in the constructor and destroys it in the destructor, // also redirects standard streams // @@ -172,6 +190,46 @@ QString addPath(const QString& s); QString setPath(const QString& s); +class Service +{ +public: + enum class StartType + { + None = 0, + Disabled, + Enabled + }; + + enum class Status + { + None = 0, + Stopped, + Running + }; + + + explicit Service(QString name); + Service(QString name, StartType st, Status s); + + bool isValid() const; + + const QString& name() const; + StartType startType() const; + Status status() const; + + QString toString() const; + +private: + QString m_name; + StartType m_startType; + Status m_status; +}; + + +Service getService(const QString& name); +QString toString(Service::StartType st); +QString toString(Service::Status st); + enum class CoreDumpTypes { Mini = 1, diff --git a/src/spawn.cpp b/src/spawn.cpp index 64766adf..551a5adb 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -428,7 +428,6 @@ void startBinaryAdmin(const SpawnParameters& sp) restartAsAdmin(); } - bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { @@ -557,9 +556,9 @@ bool checkSteam( log::debug("checking steam"); if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", steamAppID.toStdWString().c_str()); + env::set("SteamAPPId", steamAppID); } else { - ::SetEnvironmentVariableW(L"SteamAPPId", settings.steam().appID().toStdWString().c_str()); + env::set("SteamAPPId", settings.steam().appID()); } if (!gameRequiresSteam(gameDirectory, settings)) { @@ -584,7 +583,7 @@ bool checkSteam( // double-check that Steam is started ss = getSteamStatus(); if (!ss.running) { - log::error("steam is still not running, continuing and hoping for the best"); + log::error("steam is still not running, hoping for the best"); return true; } } else if (c == QDialogButtonBox::No) { @@ -615,90 +614,29 @@ bool checkSteam( return true; } -bool checkService() +bool checkEventLogService() { - SC_HANDLE serviceManagerHandle = NULL; - SC_HANDLE serviceHandle = NULL; - LPSERVICE_STATUS_PROCESS serviceStatus = NULL; - LPQUERY_SERVICE_CONFIG serviceConfig = NULL; - bool serviceRunning = true; - - DWORD bytesNeeded; - - try { - serviceManagerHandle = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceManagerHandle) { - log::warn("failed to open service manager (query status) (error {})", GetLastError()); - throw 1; - } - - serviceHandle = OpenService(serviceManagerHandle, L"EventLog", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); - if (!serviceHandle) { - log::warn("failed to open EventLog service (query status) (error {})", GetLastError()); - throw 2; - } - - if (QueryServiceConfig(serviceHandle, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service config (error {})", GetLastError()); - throw 3; - } - - DWORD serviceConfigSize = bytesNeeded; - serviceConfig = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, serviceConfigSize); - if (!QueryServiceConfig(serviceHandle, serviceConfig, serviceConfigSize, &bytesNeeded)) { - log::warn("failed to query service config (error {})", GetLastError()); - throw 4; - } - - if (serviceConfig->dwStartType == SERVICE_DISABLED) { - log::error("Windows Event Log service is disabled!"); - serviceRunning = false; - } - - if (QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, NULL, 0, &bytesNeeded) - || (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { - log::warn("failed to get size of service status (error {})", GetLastError()); - throw 5; - } + const auto s = env::getService("EventLog"); - DWORD serviceStatusSize = bytesNeeded; - serviceStatus = (LPSERVICE_STATUS_PROCESS)LocalAlloc(LMEM_FIXED, serviceStatusSize); - if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_PROCESS_INFO, (LPBYTE)serviceStatus, serviceStatusSize, &bytesNeeded)) { - log::warn("failed to query service status (error {})", GetLastError()); - throw 6; - } - - if (serviceStatus->dwCurrentState != SERVICE_RUNNING) { - log::error("Windows Event Log service is not running"); - serviceRunning = false; - } - } - catch (int) { - serviceRunning = false; + if (!s.isValid()) { + log::error("cannot determine the status of the EventLog, continuing"); + return true; } - if (serviceStatus) { - LocalFree(serviceStatus); - } - if (serviceConfig) { - LocalFree(serviceConfig); - } - if (serviceHandle) { - CloseServiceHandle(serviceHandle); - } - if (serviceManagerHandle) { - CloseServiceHandle(serviceManagerHandle); + if (s.status() == env::Service::Status::Running) { + log::debug("{}", s.toString()); + return true; + } else { + log::error("{}", s.toString()); + return false; } - - return serviceRunning; } bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) { // Check if the Windows Event Logging service is running. For some reason, this seems to be // critical to the successful running of usvfs. - if (!checkService()) { + if (!checkEventLogService()) { if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(), QObject::tr("Windows Event Log Error"), QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" -- cgit v1.3.1 From 8bc67a86d64c86cf7f1eeb2c656dd414c0716d0b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 11:17:52 -0400 Subject: moved event log warning to dialogs --- src/spawn.cpp | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index 551a5adb..c9025d98 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -330,7 +330,22 @@ QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const S QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); } -} // namepsace +bool eventLogNotRunning( + QWidget* parent, const env::Service& s, const SpawnParameters& sp) +{ + const auto r = QuestionBoxMemory::query( + parent, QString("eventLogService"), sp.binary.fileName(), + QObject::tr("Windows Event Log Error"), + QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" + " USVFS from running properly. Your mods may not be working in the executable" + " that you are launching. Note that you may have to restart MO and/or your PC" + " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()), + QDialogButtonBox::Yes | QDialogButtonBox::No); + + return (r != QDialogButtonBox::No); +} + +} // namespace namespace spawn @@ -614,8 +629,11 @@ bool checkSteam( return true; } -bool checkEventLogService() +bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) { + // check if the Windows Event Logging service is running; for some reason, + // this seems to be critical to the successful running of usvfs. + const auto s = env::getService("EventLog"); if (!s.isValid()) { @@ -626,29 +644,10 @@ bool checkEventLogService() if (s.status() == env::Service::Status::Running) { log::debug("{}", s.toString()); return true; - } else { - log::error("{}", s.toString()); - return false; - } -} - -bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) -{ - // Check if the Windows Event Logging service is running. For some reason, this seems to be - // critical to the successful running of usvfs. - if (!checkEventLogService()) { - if (QuestionBoxMemory::query(parent, QString("eventLogService"), sp.binary.fileName(), - QObject::tr("Windows Event Log Error"), - QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" - " USVFS from running properly. Your mods may not be working in the executable" - " that you are launching. Note that you may have to restart MO and/or your PC" - " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return false; - } } - return true; + log::error("{}", s.toString()); + return dialogs::eventLogNotRunning(parent, s, sp); } bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings) -- cgit v1.3.1 From a5db7ed864ac58657bf9bfbbc292cdccbeeaa38b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 11:23:21 -0400 Subject: added Settings::isExecutableBlacklisted() moved blacklisted confirmation to dialogs --- src/settings.cpp | 11 +++++++++++ src/settings.h | 1 + src/spawn.cpp | 29 ++++++++++++++++------------- 3 files changed, 28 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 7fdda2bf..ae487c18 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -222,6 +222,17 @@ QString Settings::executablesBlacklist() const return get(m_Settings, "Settings", "executable_blacklist", def); } +bool Settings::isExecutableBlacklisted(const QString& s) const +{ + for (auto exec : executablesBlacklist().split(";")) { + if (exec.compare(s, Qt::CaseInsensitive) == 0) { + return true; + } + } + + return false; +} + void Settings::setExecutablesBlacklist(const QString& s) { set(m_Settings, "Settings", "executable_blacklist", s); diff --git a/src/settings.h b/src/settings.h index 815ed160..cd478a5b 100644 --- a/src/settings.h +++ b/src/settings.h @@ -678,6 +678,7 @@ public: // by MO but given to usvfs when starting an executable // QString executablesBlacklist() const; + bool isExecutableBlacklisted(const QString& s) const; void setExecutablesBlacklist(const QString& s); // ? looks obsolete, only used by dead code diff --git a/src/spawn.cpp b/src/spawn.cpp index c9025d98..45324d79 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -345,6 +345,20 @@ bool eventLogNotRunning( return (r != QDialogButtonBox::No); } +bool confirmBlacklisted(QWidget* parent, const SpawnParameters& sp) +{ + const auto r = QuestionBoxMemory::query( + parent, QString("blacklistedExecutable"), sp.binary.fileName(), + QObject::tr("Blacklisted Executable"), + QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file" + " system. This will likely prevent the executable, and any executables that are" + " launched by this one, from seeing any mods. This could extend to INI files, save" + " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()), + QDialogButtonBox::Yes | QDialogButtonBox::No); + + return (r != QDialogButtonBox::No); +} + } // namespace @@ -652,18 +666,8 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings) { - for (auto exec : settings.executablesBlacklist().split(";")) { - if (exec.compare(sp.binary.fileName(), Qt::CaseInsensitive) == 0) { - if (QuestionBoxMemory::query(parent, QString("blacklistedExecutable"), sp.binary.fileName(), - QObject::tr("Blacklisted Executable"), - QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file" - " system. This will likely prevent the executable, and any executables that are" - " launched by this one, from seeing any mods. This could extend to INI files, save" - " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::No) { - return false; - } - } + if (settings.isExecutableBlacklisted(sp.binary.fileName())) { + return dialogs::confirmBlacklisted(parent, sp); } return true; @@ -779,7 +783,6 @@ bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) return helperExec(moPath, commandLine, FALSE); } - bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir) { const std::wstring commandLine = fmt::format( -- cgit v1.3.1 From 9bac57e3e864bd300fadccfaa194a6f3d28c9de2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 15:03:03 -0400 Subject: steam confirmation now using TaskDialog fixed dialog choices not remembering files --- src/settings.cpp | 2 +- src/settingsdialog.ui | 2 +- src/spawn.cpp | 95 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 65 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index ae487c18..7cea52fb 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -946,7 +946,7 @@ QuestionBoxMemory::Button WidgetSettings::questionButton( if (!filename.isEmpty()) { const auto fileSetting = windowName + "/" + filename; - if (auto v=getOptional(m_Settings, sectionName, filename)) { + if (auto v=getOptional(m_Settings, sectionName, fileSetting)) { return static_cast(*v); } } diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 1a3726fb..40079441 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -191,7 +191,7 @@ p, li { white-space: pre-wrap; } This will make all dialogs show up again where you checked the "Remember selection"-box. - Reset Dialogs + Reset Dialog Choices
diff --git a/src/spawn.cpp b/src/spawn.cpp index 45324d79..e18e6bb3 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -183,6 +183,7 @@ QMessageBox::StandardButton badSteamReg( "The path to the Steam executable cannot be found. You might try " "reinstalling Steam.")) .details(details) + .icon(QMessageBox::Critical) .button({ QObject::tr("Continue without starting Steam"), QObject::tr("The program may fail to launch."), @@ -203,6 +204,7 @@ QMessageBox::StandardButton startSteamFailed( .main(QObject::tr("Cannot start Steam")) .content(makeContent(sp, e)) .details(details) + .icon(QMessageBox::Critical) .button({ QObject::tr("Continue without starting Steam"), QObject::tr("The program may fail to launch."), @@ -232,6 +234,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) .main(mainText) .content(makeContent(sp, code)) .details(details) + .icon(QMessageBox::Critical) .exec(); } @@ -261,6 +264,7 @@ void helperFailed( .main(mainText) .content(makeContent(sp, code)) .details(details) + .icon(QMessageBox::Critical) .exec(); } @@ -295,26 +299,45 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) .main(mainText) .content(content) .details(details) + .icon(QMessageBox::Question) .button({ - QObject::tr("Restart Mod Organizer as administrator"), - QObject::tr("You must allow \"helper.exe\" to make changes to the system."), - QMessageBox::Yes}) + QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr("You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); return (r == QMessageBox::Yes); } -QuestionBoxMemory::Button confirmStartSteam(QWidget* parent, const SpawnParameters& sp) +QMessageBox::StandardButton confirmStartSteam( + QWidget* window, const SpawnParameters& sp, const QString& details) { - return QuestionBoxMemory::query( - parent, "steamQuery", sp.binary.fileName(), - QObject::tr("Start Steam?"), - QObject::tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); + const auto title = QObject::tr("Launch Steam"); + const auto mainText = QObject::tr("This program requires Steam"); + const auto content = QObject::tr( + "Mod Organizer has detected that this program likely requires Steam to be " + "running to function properly."); + + return MOBase::TaskDialog(window, title) + .main(mainText) + .content(content) + .details(details) + .icon(QMessageBox::Question) + .button({ + QObject::tr("Start Steam"), + QMessageBox::Yes}) + .button({ + QObject::tr("Continue without starting Steam"), + QObject::tr("The program might fail to run."), + QMessageBox::No}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .remember("steamQuery", sp.binary.fileName()) + .exec(); } QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) @@ -561,27 +584,14 @@ bool startSteam(QWidget* parent) return true; } -bool gameRequiresSteam(const QDir& gameDirectory, const Settings& settings) -{ - static const std::vector files = { - "steam_api.dll", "steam_api64.dll" - }; - - for (const auto& file : files) { - const QFileInfo fi(gameDirectory.absoluteFilePath(file)); - if (fi.exists()) { - log::debug("found '{}'", fi.absoluteFilePath()); - return true; - } - } - - return false; -} - bool checkSteam( QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString &steamAppID, const Settings& settings) { + static const std::vector steamFiles = { + "steam_api.dll", "steam_api64.dll" + }; + log::debug("checking steam"); if (!steamAppID.isEmpty()) { @@ -590,16 +600,37 @@ bool checkSteam( env::set("SteamAPPId", settings.steam().appID()); } - if (!gameRequiresSteam(gameDirectory, settings)) { - log::debug("games doesn't seem to require steam"); + + bool steamRequired = false; + QString details; + + for (const auto& file : steamFiles) { + const QFileInfo fi(gameDirectory.absoluteFilePath(file)); + if (fi.exists()) { + details = QString( + "managed game is located at '%1' and file '%2' exists") + .arg(gameDirectory.absolutePath()) + .arg(fi.absoluteFilePath()); + + log::debug("{}", details); + steamRequired = true; + + break; + } + } + + if (!steamRequired) { + log::debug("program doesn't seem to require steam"); return true; } + auto ss = getSteamStatus(); if (!ss.running) { log::debug("steam isn't running, asking to start steam"); - const auto c = dialogs::confirmStartSteam(parent, sp); + + const auto c = dialogs::confirmStartSteam(parent, sp, details); if (c == QDialogButtonBox::Yes) { log::debug("user wants to start steam"); -- cgit v1.3.1 From f92e2c376d36132a9676b30f0b08543f27a13064 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 16:27:41 -0400 Subject: TaskDialog for restarting as admin for steam added a parent widget parameter to a bunch of places fixed paths still getting changed even if folders can't be created made private the member variables that were temporarily public during rework --- src/settingsdialog.cpp | 44 ++++++++++------ src/settingsdialog.h | 20 +++---- src/settingsdialoggeneral.cpp | 7 ++- src/settingsdialognexus.cpp | 14 ++--- src/settingsdialogpaths.cpp | 6 ++- src/settingsdialogworkarounds.cpp | 6 ++- src/spawn.cpp | 106 ++++++++++++++++++++------------------ src/spawn.h | 7 ++- 8 files changed, 121 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 1d3d4a39..8fb25b1c 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) - , m_PluginContainer(pluginContainer) - , m_keyChanged(false) + , m_pluginContainer(pluginContainer) + , m_restartNeeded(false) { ui->setupUi(this); @@ -47,6 +47,25 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti m_tabs.push_back(std::unique_ptr(new WorkaroundsSettingsTab(settings, *this))); } +PluginContainer* SettingsDialog::pluginContainer() +{ + return m_pluginContainer; +} + +QWidget* SettingsDialog::parentWidgetForDialogs() +{ + if (isVisible()) { + return this; + } else { + return parentWidget(); + } +} + +void SettingsDialog::setRestartNeeded() +{ + m_restartNeeded = true; +} + int SettingsDialog::exec() { GeometrySaver gs(m_settings, this); @@ -68,13 +87,8 @@ int SettingsDialog::exec() } } - bool restartNeeded = false; - if (getApiKeyChanged()) { - restartNeeded = true; - } - - if (restartNeeded) { - if (QMessageBox::question(nullptr, + if (m_restartNeeded) { + if (QMessageBox::question(parentWidgetForDialogs(), tr("Restart Mod Organizer?"), tr("In order to finish configuration changes, MO must be restarted.\n" "Restart it now?"), @@ -111,7 +125,7 @@ void SettingsDialog::accept() QDir::fromNativeSeparators( Settings::instance().paths().mods(true))) && (QMessageBox::question( - nullptr, tr("Confirm"), + parentWidgetForDialogs(), tr("Confirm"), tr("Changing the mod directory affects all your profiles! " "Mods not present (or named differently) in the new location " "will be disabled in all profiles. " @@ -124,11 +138,6 @@ void SettingsDialog::accept() TutorableDialog::accept(); } -bool SettingsDialog::getApiKeyChanged() -{ - return m_keyChanged; -} - SettingsTab::SettingsTab(Settings& s, SettingsDialog& d) : ui(d.ui), m_settings(s), m_dialog(d) @@ -146,3 +155,8 @@ SettingsDialog& SettingsTab::dialog() { return m_dialog; } + +QWidget* SettingsTab::parentWidget() +{ + return m_dialog.parentWidgetForDialogs(); +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 6a99cb8d..e89da665 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -42,6 +42,7 @@ protected: Settings& settings(); SettingsDialog& dialog(); + QWidget* parentWidget(); private: Settings& m_settings; @@ -56,11 +57,12 @@ private: **/ class SettingsDialog : public MOBase::TutorableDialog { - Q_OBJECT + Q_OBJECT; + friend class SettingsTab; public: explicit SettingsDialog( - PluginContainer *pluginContainer, Settings& settings, QWidget *parent = 0); + PluginContainer* pluginContainer, Settings& settings, QWidget* parent = 0); ~SettingsDialog(); @@ -70,23 +72,21 @@ public: */ QString getColoredButtonStyleSheet() const; - // temp - Ui::SettingsDialog *ui; - bool m_keyChanged; - PluginContainer *m_PluginContainer; + PluginContainer* pluginContainer(); + QWidget* parentWidgetForDialogs(); + void setRestartNeeded(); int exec() override; public slots: virtual void accept(); -public: - bool getApiKeyChanged(); - private: Settings& m_settings; std::vector> m_tabs; - + Ui::SettingsDialog* ui; + bool m_restartNeeded; + PluginContainer* m_pluginContainer; }; #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 3f7ece38..8ecdcbb9 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -238,8 +238,11 @@ void GeneralSettingsTab::on_resetColorsBtn_clicked() void GeneralSettingsTab::on_resetDialogsButton_clicked() { - if (QMessageBox::question(&dialog(), QObject::tr("Confirm?"), - QObject::tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), + if (QMessageBox::question( + parentWidget(), QObject::tr("Confirm?"), + QObject::tr( + "This will reset all the choices you made to dialogs and make them all " + "visible again. Continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { resetDialogs(); } diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 0b08f13f..826075c0 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -226,7 +226,7 @@ void NexusSettingsTab::on_nexusDisconnect_clicked() void NexusSettingsTab::on_clearCacheButton_clicked() { QDir(Settings::instance().paths().cache()).removeRecursively(); - NexusInterface::instance(dialog().m_PluginContainer)->clearCache(); + NexusInterface::instance(dialog().pluginContainer())->clearCache(); } void NexusSettingsTab::on_associateButton_clicked() @@ -238,7 +238,7 @@ void NexusSettingsTab::validateKey(const QString& key) { if (!m_nexusValidator) { m_nexusValidator.reset(new NexusKeyValidator( - *NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager())); + *NexusInterface::instance(dialog().pluginContainer())->getAccessManager())); m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ onValidatorStateChanged(s, e); @@ -294,7 +294,7 @@ void NexusSettingsTab::onValidatorStateChanged( void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user) { - NexusInterface::instance(dialog().m_PluginContainer)->setUserAccount(user); + NexusInterface::instance(dialog().pluginContainer())->setUserAccount(user); if (!user.apiKey().isEmpty()) { if (setKey(user.apiKey())) { @@ -311,7 +311,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - dialog().m_keyChanged = true; + dialog().setRestartNeeded(); const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; @@ -319,10 +319,10 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { - dialog().m_keyChanged = true; + dialog().setRestartNeeded(); const auto ret = settings().nexus().clearApiKey(); - NexusInterface::instance(dialog().m_PluginContainer)->getAccessManager()->clearApiKey(); + NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey(); updateNexusState(); return ret; @@ -371,7 +371,7 @@ void NexusSettingsTab::updateNexusButtons() void NexusSettingsTab::updateNexusData() { - const auto user = NexusInterface::instance(dialog().m_PluginContainer) + const auto user = NexusInterface::instance(dialog().pluginContainer()) ->getAPIUserAccount(); if (user.isValid()) { diff --git a/src/settingsdialogpaths.cpp b/src/settingsdialogpaths.cpp index aeb4dd5d..c6fd40a7 100644 --- a/src/settingsdialogpaths.cpp +++ b/src/settingsdialogpaths.cpp @@ -68,10 +68,12 @@ void PathsSettingsTab::update() if (!QDir(realPath).exists()) { if (!QDir().mkpath(realPath)) { - QMessageBox::warning(qApp->activeWindow(), QObject::tr("Error"), + QMessageBox::warning(parentWidget(), QObject::tr("Error"), QObject::tr("Failed to create \"%1\", you may not have the " - "necessary permission. path remains unchanged.") + "necessary permissions. Path remains unchanged.") .arg(realPath)); + + continue; } } diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index ccbfcbfe..5e70e5a6 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -83,7 +83,9 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() const auto* game = qApp->property("managed_game").value(); QDir dir = game->dataDirectory(); - helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), + helper::backdateBSAs( + parentWidget(), + qApp->applicationDirPath().toStdWString(), dir.absolutePath().toStdWString()); } @@ -95,7 +97,7 @@ void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() "Restart now?"); const auto res = QMessageBox::question( - nullptr, caption, text, QMessageBox::Yes | QMessageBox::Cancel); + parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel); if (res == QMessageBox::Yes) { settings().geometry().requestReset(); diff --git a/src/spawn.cpp b/src/spawn.cpp index e18e6bb3..a0cf9fb6 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -215,7 +215,7 @@ QMessageBox::StandardButton startSteamFailed( .exec(); } -void spawnFailed(const SpawnParameters& sp, DWORD code) +void spawnFailed(QWidget* parent, const SpawnParameters& sp, DWORD code) { const auto details = makeDetails(sp, code); log::error("{}", details); @@ -225,12 +225,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) const auto mainText = QObject::tr("Cannot start %1") .arg(sp.binary.fileName()); - QWidget *window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } - - MOBase::TaskDialog(window, title) + MOBase::TaskDialog(parent, title) .main(mainText) .content(makeContent(sp, code)) .details(details) @@ -239,7 +234,7 @@ void spawnFailed(const SpawnParameters& sp, DWORD code) } void helperFailed( - DWORD code, const QString& why, const std::wstring& binary, + QWidget* parent, DWORD code, const QString& why, const std::wstring& binary, const std::wstring& cwd, const std::wstring& args) { SpawnParameters sp; @@ -255,12 +250,7 @@ void helperFailed( const auto mainText = QObject::tr("Cannot start %1") .arg(sp.binary.fileName()); - QWidget *window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } - - MOBase::TaskDialog(window, title) + MOBase::TaskDialog(parent, title) .main(mainText) .content(makeContent(sp, code)) .details(details) @@ -268,7 +258,7 @@ void helperFailed( .exec(); } -bool confirmRestartAsAdmin(const SpawnParameters& sp) +bool confirmRestartAsAdmin(QWidget* parent, const SpawnParameters& sp) { const auto details = makeDetails(sp, ERROR_ELEVATION_REQUIRED); @@ -287,15 +277,9 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) "You can restart Mod Organizer as administrator and try launching the " "program again."); - - QWidget *window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } - log::debug("asking user to restart MO as administrator"); - const auto r = MOBase::TaskDialog(window, title) + const auto r = MOBase::TaskDialog(parent, title) .main(mainText) .content(content) .details(details) @@ -313,7 +297,7 @@ bool confirmRestartAsAdmin(const SpawnParameters& sp) } QMessageBox::StandardButton confirmStartSteam( - QWidget* window, const SpawnParameters& sp, const QString& details) + QWidget* parent, const SpawnParameters& sp, const QString& details) { const auto title = QObject::tr("Launch Steam"); const auto mainText = QObject::tr("This program requires Steam"); @@ -321,7 +305,7 @@ QMessageBox::StandardButton confirmStartSteam( "Mod Organizer has detected that this program likely requires Steam to be " "running to function properly."); - return MOBase::TaskDialog(window, title) + return MOBase::TaskDialog(parent, title) .main(mainText) .content(content) .details(details) @@ -340,17 +324,35 @@ QMessageBox::StandardButton confirmStartSteam( .exec(); } -QuestionBoxMemory::Button confirmRestartAsAdminForSteam(QWidget* parent, const SpawnParameters& sp) +QMessageBox::StandardButton confirmRestartAsAdminForSteam( + QWidget* parent, const SpawnParameters& sp) { - return QuestionBoxMemory::query( - parent, "steamAdminQuery", sp.binary.fileName(), - QObject::tr("Steam: Access Denied"), - QObject::tr("MO was denied access to the Steam process. This normally indicates that " - "Steam is being run as administrator while MO is not. This can cause issues " - "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely necessary.\n\n" - "Restart MO as administrator?"), - QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); + const auto title = QObject::tr("Elevation required"); + const auto mainText = QObject::tr("Steam is running as administrator"); + const auto content = QObject::tr( + "Running Steam as administrator is typically unnecessary and can cause " + "problems when Mod Organizer is not running as administrato\r\n\r\n" + "You can restart Mod Organizer as administrator and try launching the " + "program again."); + + return MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .details("") + .icon(QMessageBox::Question) + .button({ + QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr("You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) + .button({ + QObject::tr("Continue"), + QObject::tr("The program might fail to run."), + QMessageBox::No}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .remember("steamAdminQuery", sp.binary.fileName()) + .exec(); } bool eventLogNotRunning( @@ -447,7 +449,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand return ERROR_SUCCESS; } -bool restartAsAdmin() +bool restartAsAdmin(QWidget* parent) { WCHAR cwd[MAX_PATH] = {}; if (!GetCurrentDirectory(MAX_PATH, cwd)) { @@ -455,6 +457,7 @@ bool restartAsAdmin() } if (!helper::adminLaunch( + parent, qApp->applicationDirPath().toStdWString(), qApp->applicationFilePath().toStdWString(), std::wstring(cwd))) @@ -469,21 +472,21 @@ bool restartAsAdmin() return true; } -void startBinaryAdmin(const SpawnParameters& sp) +void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp) { - if (!dialogs::confirmRestartAsAdmin(sp)) { + if (!dialogs::confirmRestartAsAdmin(parent, sp)) { log::debug("user declined"); return; } log::info("restarting MO as administrator"); - restartAsAdmin(); + restartAsAdmin(parent); } bool checkBinary(QWidget* parent, const SpawnParameters& sp) { if (!sp.binary.exists()) { - dialogs::spawnFailed(sp, ERROR_FILE_NOT_FOUND); + dialogs::spawnFailed(parent, sp, ERROR_FILE_NOT_FOUND); return false; } @@ -660,7 +663,7 @@ bool checkSteam( const auto c = dialogs::confirmRestartAsAdminForSteam(parent, sp); if (c == QDialogButtonBox::Yes) { - restartAsAdmin(); + restartAsAdmin(parent); return false; } else if (c == QDialogButtonBox::No) { log::debug("user declined to restart MO, continuing"); @@ -720,13 +723,13 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) case ERROR_ELEVATION_REQUIRED: { - startBinaryAdmin(sp); + startBinaryAdmin(parent, sp); return INVALID_HANDLE_VALUE; } default: { - dialogs::spawnFailed(sp, e); + dialogs::spawnFailed(parent, sp, e); return INVALID_HANDLE_VALUE; } } @@ -740,6 +743,7 @@ namespace helper { bool helperExec( + QWidget* parent, const std::wstring& moDirectory, const std::wstring& commandLine, BOOL async) { const std::wstring fileName = moDirectory + L"\\helper.exe"; @@ -766,7 +770,7 @@ bool helperExec( const auto e = GetLastError(); spawn::dialogs::helperFailed( - e, "ShellExecuteExW()", fileName, moDirectory, commandLine); + parent, e, "ShellExecuteExW()", fileName, moDirectory, commandLine); return false; } @@ -788,7 +792,8 @@ bool helperExec( ERROR_ABANDONED_WAIT_0 : GetLastError()); spawn::dialogs::helperFailed( - code, "WaitForSingleObject()", fileName, moDirectory, commandLine); + parent, code, "WaitForSingleObject()", + fileName, moDirectory, commandLine); return false; } @@ -798,7 +803,7 @@ bool helperExec( const auto e = GetLastError(); spawn::dialogs::helperFailed( - e, "GetExitCodeProcess()", fileName, moDirectory, commandLine); + parent, e, "GetExitCodeProcess()", fileName, moDirectory, commandLine); return false; } @@ -806,21 +811,24 @@ bool helperExec( return (exitCode == 0); } -bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) +bool backdateBSAs( + QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath) { const std::wstring commandLine = fmt::format( L"backdateBSA \"{}\"", dataPath); - return helperExec(moPath, commandLine, FALSE); + return helperExec(parent, moPath, commandLine, FALSE); } -bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir) +bool adminLaunch( + QWidget* parent, const std::wstring &moPath, + const std::wstring &moFile, const std::wstring &workingDir) { const std::wstring commandLine = fmt::format( L"adminLaunch {} \"{}\" \"{}\"", ::GetCurrentProcessId(), moFile, workingDir); - return helperExec(moPath, commandLine, true); + return helperExec(parent, moPath, commandLine, true); } } // namespace diff --git a/src/spawn.h b/src/spawn.h index da626329..9e1e2539 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -84,7 +84,8 @@ namespace helper * @param moPath absolute path to the modOrganizer base directory * @param dataPath the path taht contains the .bsa-files, usually the data directory of the game **/ -bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath); +bool backdateBSAs( + QWidget* parent, const std::wstring &moPath, const std::wstring &dataPath); /** * @brief waits for the current process to exit and restarts it as an administrator @@ -92,7 +93,9 @@ bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath); * @param moFile file name of modOrganizer * @param workingDir current working directory **/ -bool adminLaunch(const std::wstring &moPath, const std::wstring &moFile, const std::wstring &workingDir); +bool adminLaunch( + QWidget* parent, const std::wstring &moPath, + const std::wstring &moFile, const std::wstring &workingDir); } // namespace -- cgit v1.3.1 From cbfd3692ce95f43daa081c5f16a5c9160cb7c459 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 16:41:36 -0400 Subject: TaskDialog for event log not running --- src/spawn.cpp | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index a0cf9fb6..b7aa90c0 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -338,12 +338,11 @@ QMessageBox::StandardButton confirmRestartAsAdminForSteam( return MOBase::TaskDialog(parent, title) .main(mainText) .content(content) - .details("") .icon(QMessageBox::Question) .button({ - QObject::tr("Restart Mod Organizer as administrator"), - QObject::tr("You must allow \"helper.exe\" to make changes to the system."), - QMessageBox::Yes}) + QObject::tr("Restart Mod Organizer as administrator"), + QObject::tr("You must allow \"helper.exe\" to make changes to the system."), + QMessageBox::Yes}) .button({ QObject::tr("Continue"), QObject::tr("The program might fail to run."), @@ -358,16 +357,29 @@ QMessageBox::StandardButton confirmRestartAsAdminForSteam( bool eventLogNotRunning( QWidget* parent, const env::Service& s, const SpawnParameters& sp) { - const auto r = QuestionBoxMemory::query( - parent, QString("eventLogService"), sp.binary.fileName(), - QObject::tr("Windows Event Log Error"), - QObject::tr("The Windows Event Log service is disabled and/or not running. This prevents" - " USVFS from running properly. Your mods may not be working in the executable" - " that you are launching. Note that you may have to restart MO and/or your PC" - " after the service is fixed.\n\nContinue launching %1?").arg(sp.binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No); + const auto title = QObject::tr("Event Log not running"); + const auto mainText = QObject::tr("The Event Log service is not running"); + const auto content = QObject::tr( + "The Windows Event Log service is not running. This can prevent USVFS from " + "running properly and your mods may not be recognized by the program being " + "launched."); - return (r != QDialogButtonBox::No); + const auto r = MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .details(s.toString()) + .icon(QMessageBox::Question) + .remember("eventLogService", sp.binary.fileName()) + .button({ + QObject::tr("Continue"), + QObject::tr("Your mods might not work."), + QMessageBox::Yes}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); + + return (r == QDialogButtonBox::Yes); } bool confirmBlacklisted(QWidget* parent, const SpawnParameters& sp) @@ -681,11 +693,15 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) { // check if the Windows Event Logging service is running; for some reason, // this seems to be critical to the successful running of usvfs. + const auto serviceName = "EventLog"; - const auto s = env::getService("EventLog"); + const auto s = env::getService(serviceName); if (!s.isValid()) { - log::error("cannot determine the status of the EventLog, continuing"); + log::error( + "cannot determine the status of the {} service, continuing", + serviceName); + return true; } -- cgit v1.3.1 From 94b0c4634290b41398915c6635982dc7b3928f60 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 17:10:30 -0400 Subject: TaskDialog for blacklisted, with button to change the blacklist --- src/settingsdialogworkarounds.cpp | 51 +++++++++++++++++++++++-------- src/settingsdialogworkarounds.h | 15 ++++++++-- src/spawn.cpp | 63 +++++++++++++++++++++++++++++---------- 3 files changed, 98 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 5e70e5a6..0e31fc4b 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -1,6 +1,7 @@ #include "settingsdialogworkarounds.h" #include "ui_settingsdialog.h" #include "spawn.h" +#include "settings.h" #include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) @@ -26,7 +27,7 @@ WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) ui->lockGUIBox->setChecked(settings().interface().lockGUI()); ui->enableArchiveParsingBox->setChecked(settings().archiveParsing()); - setExecutableBlacklist(settings().executablesBlacklist()); + m_ExecutableBlacklist = settings().executablesBlacklist(); QObject::connect(ui->bsaDateBtn, &QPushButton::clicked, [&]{ on_bsaDateBtn_clicked(); }); QObject::connect(ui->execBlacklistBtn, &QPushButton::clicked, [&]{ on_execBlacklistBtn_clicked(); }); @@ -49,14 +50,29 @@ void WorkaroundsSettingsTab::update() settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked()); settings().interface().setLockGUI(ui->lockGUIBox->isChecked()); settings().setArchiveParsing(ui->enableArchiveParsingBox->isChecked()); - settings().setExecutablesBlacklist(getExecutableBlacklist()); + settings().setExecutablesBlacklist(m_ExecutableBlacklist); } -void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() +bool WorkaroundsSettingsTab::changeBlacklistNow( + QWidget* parent, Settings& settings) +{ + const auto current = settings.executablesBlacklist(); + + if (auto s=changeBlacklistLater(parent, current)) { + settings.setExecutablesBlacklist(*s); + return true; + } + + return false; +} + +std::optional WorkaroundsSettingsTab::changeBlacklistLater( + QWidget* parent, const QString& current) { bool ok = false; + QString result = QInputDialog::getMultiLineText( - &dialog(), + parent, QObject::tr("Executables Blacklist"), QObject::tr("Enter one executable per line to be blacklisted from the virtual file system.\n" "Mods and other virtualized files will not be visible to these executables and\n" @@ -64,17 +80,28 @@ void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() "Example:\n" " Chrome.exe\n" " Firefox.exe"), - m_ExecutableBlacklist.split(";").join("\n"), + current.split(";").join("\n"), &ok ); - if (ok) { - QStringList blacklist; - for (auto exec : result.split("\n")) { - if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) { - blacklist << exec.trimmed(); - } + + if (!ok) { + return {}; + } + + QStringList blacklist; + for (auto exec : result.split("\n")) { + if (exec.trimmed().endsWith(".exe", Qt::CaseInsensitive)) { + blacklist << exec.trimmed(); } - m_ExecutableBlacklist = blacklist.join(";"); + } + + return blacklist.join(";"); +} + +void WorkaroundsSettingsTab::on_execBlacklistBtn_clicked() +{ + if (auto s=changeBlacklistLater(parentWidget(), m_ExecutableBlacklist)) { + m_ExecutableBlacklist = *s; } } diff --git a/src/settingsdialogworkarounds.h b/src/settingsdialogworkarounds.h index d5d6815f..cffc54a0 100644 --- a/src/settingsdialogworkarounds.h +++ b/src/settingsdialogworkarounds.h @@ -8,6 +8,18 @@ class WorkaroundsSettingsTab : public SettingsTab { public: WorkaroundsSettingsTab(Settings& settings, SettingsDialog& dialog); + + // shows the blacklist dialog from the given settings, and changes the + // settings when the user accepts it + // + static bool changeBlacklistNow(QWidget* parent, Settings& settings); + + // shows the blacklist dialog from the given string and returns the new + // blacklist if the user accepted it + // + static std::optional changeBlacklistLater( + QWidget* parent, const QString& current); + void update(); private: @@ -16,9 +28,6 @@ private: void on_bsaDateBtn_clicked(); void on_execBlacklistBtn_clicked(); void on_resetGeometryBtn_clicked(); - - QString getExecutableBlacklist() { return m_ExecutableBlacklist; } - void setExecutableBlacklist(QString blacklist) { m_ExecutableBlacklist = blacklist; } }; #endif // SETTINGSDIALOGWORKAROUNDS_H diff --git a/src/spawn.cpp b/src/spawn.cpp index b7aa90c0..ab9d90a0 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include "envsecurity.h" #include "envmodule.h" #include "settings.h" +#include "settingsdialogworkarounds.h" #include #include #include @@ -379,21 +380,45 @@ bool eventLogNotRunning( QMessageBox::Cancel}) .exec(); - return (r == QDialogButtonBox::Yes); + return (r == QMessageBox::Yes); } -bool confirmBlacklisted(QWidget* parent, const SpawnParameters& sp) +QMessageBox::StandardButton confirmBlacklisted( + QWidget* parent, const SpawnParameters& sp) { - const auto r = QuestionBoxMemory::query( - parent, QString("blacklistedExecutable"), sp.binary.fileName(), - QObject::tr("Blacklisted Executable"), - QObject::tr("The executable you are attempted to launch is blacklisted in the virtual file" - " system. This will likely prevent the executable, and any executables that are" - " launched by this one, from seeing any mods. This could extend to INI files, save" - " games and any other virtualized files.\n\nContinue launching %1?").arg(sp.binary.fileName()), - QDialogButtonBox::Yes | QDialogButtonBox::No); - - return (r != QDialogButtonBox::No); + const auto title = QObject::tr("Blacklisted program"); + const auto mainText = QObject::tr("The program %1 is blacklisted") + .arg(sp.binary.fileName()); + const auto content = QObject::tr( + "The program you are attempting to launch is blacklisted in the virtual " + "filesystem. This will likely prevent it from seeing any mods, INI files " + "or any other virtualized files."); + + auto r = MOBase::TaskDialog(parent, title) + .main(mainText) + .content(content) + .details("") + .icon(QMessageBox::Question) + .remember("blacklistedExecutable", sp.binary.fileName()) + .button({ + QObject::tr("Continue"), + QObject::tr("Your mods might not work"), + QMessageBox::Yes}) + .button({ + QObject::tr("Change the blacklist"), + QMessageBox::Retry}) + .button({ + QObject::tr("Cancel"), + QMessageBox::Cancel}) + .exec(); + + if (r == QMessageBox::Retry) { + if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, Settings::instance())) { + r = QMessageBox::Cancel; + } + } + + return r; } } // namespace @@ -716,11 +741,17 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings) { - if (settings.isExecutableBlacklisted(sp.binary.fileName())) { - return dialogs::confirmBlacklisted(parent, sp); - } + for (;;) { + if (!settings.isExecutableBlacklisted(sp.binary.fileName())) { + return true; + } - return true; + const auto r = dialogs::confirmBlacklisted(parent, sp); + + if (r != QMessageBox::Retry) { + return (r == QMessageBox::Yes); + } + } } -- cgit v1.3.1 From 54871e7b56a326f81e3de17f53604150d770793b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 19 Sep 2019 17:14:09 -0400 Subject: added details to blacklist dialog --- src/spawn.cpp | 15 ++++++++++----- src/spawn.h | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index ab9d90a0..d2c8ddd5 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -384,7 +384,7 @@ bool eventLogNotRunning( } QMessageBox::StandardButton confirmBlacklisted( - QWidget* parent, const SpawnParameters& sp) + QWidget* parent, const SpawnParameters& sp, Settings& settings) { const auto title = QObject::tr("Blacklisted program"); const auto mainText = QObject::tr("The program %1 is blacklisted") @@ -394,10 +394,14 @@ QMessageBox::StandardButton confirmBlacklisted( "filesystem. This will likely prevent it from seeing any mods, INI files " "or any other virtualized files."); + const auto details = + "Executable: " + sp.binary.fileName() + "\n" + "Current blacklist: " + settings.executablesBlacklist(); + auto r = MOBase::TaskDialog(parent, title) .main(mainText) .content(content) - .details("") + .details(details) .icon(QMessageBox::Question) .remember("blacklistedExecutable", sp.binary.fileName()) .button({ @@ -413,7 +417,7 @@ QMessageBox::StandardButton confirmBlacklisted( .exec(); if (r == QMessageBox::Retry) { - if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, Settings::instance())) { + if (!WorkaroundsSettingsTab::changeBlacklistNow(parent, settings)) { r = QMessageBox::Cancel; } } @@ -739,14 +743,15 @@ bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) return dialogs::eventLogNotRunning(parent, s, sp); } -bool checkBlacklist(QWidget* parent, const SpawnParameters& sp, const Settings& settings) +bool checkBlacklist( + QWidget* parent, const SpawnParameters& sp, Settings& settings) { for (;;) { if (!settings.isExecutableBlacklisted(sp.binary.fileName())) { return true; } - const auto r = dialogs::confirmBlacklisted(parent, sp); + const auto r = dialogs::confirmBlacklisted(parent, sp, settings); if (r != QMessageBox::Retry) { return (r == QMessageBox::Yes); diff --git a/src/spawn.h b/src/spawn.h index 9e1e2539..31b44739 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -61,7 +61,7 @@ bool checkSteam( bool checkEnvironment(QWidget* parent, const SpawnParameters& sp); bool checkBlacklist( - QWidget* parent, const SpawnParameters& sp, const Settings& settings); + QWidget* parent, const SpawnParameters& sp, Settings& settings); /** * @brief spawn a binary with Mod Organizer injected -- cgit v1.3.1 From b45565911487e91c11f641f5cca685c231f91faf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 20 Sep 2019 00:45:22 -0400 Subject: registry details when steam fails to start typos --- src/spawn.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index d2c8ddd5..1b46c479 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -196,9 +196,17 @@ QMessageBox::StandardButton badSteamReg( } QMessageBox::StandardButton startSteamFailed( - QWidget* parent, const SpawnParameters& sp, DWORD e) + QWidget* parent, + const QString& keyName, const QString& valueName, const QString& exe, + const SpawnParameters& sp, DWORD e) { - const auto details = makeDetails(sp, e); + auto details = QString( + "a steam install was found in the registry at '%1': '%2'\n\n") + .arg(keyName + "\\" + valueName) + .arg(exe); + + details += makeDetails(sp, e); + log::error("{}", details); return MOBase::TaskDialog(parent, QObject::tr("Cannot start Steam")) @@ -332,7 +340,8 @@ QMessageBox::StandardButton confirmRestartAsAdminForSteam( const auto mainText = QObject::tr("Steam is running as administrator"); const auto content = QObject::tr( "Running Steam as administrator is typically unnecessary and can cause " - "problems when Mod Organizer is not running as administrato\r\n\r\n" + "problems when Mod Organizer itself is not running as administrator." + "\r\n\r\n" "You can restart Mod Organizer as administrator and try launching the " "program again."); @@ -618,7 +627,10 @@ bool startSteam(QWidget* parent) (username.isEmpty() ? "" : "USERNAME"), (password.isEmpty() ? "" : "PASSWORD")); - return (dialogs::startSteamFailed(parent, sp, e) == QMessageBox::Yes); + const auto r = dialogs::startSteamFailed( + parent, keyName, valueName, exe, sp, e); + + return (r == QMessageBox::Yes); } QMessageBox::information( -- cgit v1.3.1 From 2feaed68b4bd2191aeaf02c33990a0bdf7f8738c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 20 Sep 2019 01:19:01 -0400 Subject: missing period --- src/spawn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index 1b46c479..079677f4 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -415,7 +415,7 @@ QMessageBox::StandardButton confirmBlacklisted( .remember("blacklistedExecutable", sp.binary.fileName()) .button({ QObject::tr("Continue"), - QObject::tr("Your mods might not work"), + QObject::tr("Your mods might not work."), QMessageBox::Yes}) .button({ QObject::tr("Change the blacklist"), -- cgit v1.3.1 From c1a5f2ef73f4435c08876155d4551c045d5e7594 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 21 Sep 2019 21:27:45 -0400 Subject: refactored getSecurityProductsFromWMI() to stop using a lambda security products now only need a guid, handles failures better --- src/envsecurity.cpp | 140 ++++++++++++++++++++++++++++++---------------------- src/envsecurity.h | 4 ++ 2 files changed, 84 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 3b4cdcaa..ffb17c42 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -161,6 +161,11 @@ SecurityProduct::SecurityProduct( { } +const QUuid& SecurityProduct::guid() const +{ + return m_guid; +} + const QString& SecurityProduct::name() const { return m_name; @@ -185,7 +190,13 @@ QString SecurityProduct::toString() const { QString s; - s += m_name + " (" + providerToString() + ")"; + if (m_name.isEmpty()) { + s += "(no name)"; + } else { + s += m_name; + } + + s += " (" + providerToString() + ")"; if (!m_active) { s += ", inactive"; @@ -195,7 +206,9 @@ QString SecurityProduct::toString() const s += ", definitions outdated"; } - if (!m_guid.isNull()) { + if (m_guid.isNull()) { + s += ", (no guid)"; + } else { s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); } @@ -242,91 +255,98 @@ QString SecurityProduct::providerToString() const } -std::vector getSecurityProductsFromWMI() +std::optional handleProduct(IWbemClassObject* o) { - // some products may be present in multiple queries, such as a product marked - // as both antivirus and antispyware, but they'll have the same GUID, so use - // that to avoid duplicating entries - std::map map; + VARIANT prop; - auto handleProduct = [&](auto* o) { - VARIANT prop; - // display name - auto ret = o->Get(L"displayName", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get displayName, {}", formatSystemMessage(ret)); - return; - } + // guid + auto ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); + return {}; + } - if (prop.vt != VT_BSTR) { - log::error("displayName is a {}, not a bstr", prop.vt); - return; - } + if (prop.vt != VT_BSTR) { + log::error("instanceGuid is a {}, not a bstr", prop.vt); + return {}; + } - const std::wstring name = prop.bstrVal; - VariantClear(&prop); + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); - // product state - ret = o->Get(L"productState", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get productState, {}", formatSystemMessage(ret)); - return; - } - if (prop.vt != VT_UI4 && prop.vt != VT_I4) { - log::error("productState is a {}, not a VT_UI4", prop.vt); - return; - } + // display name + QString displayName; + ret = o->Get(L"displayName", 0, &prop, 0, 0); + + if (FAILED(ret)) { + log::error("failed to get displayName, {}", formatSystemMessage(ret)); + } else if (prop.vt != VT_BSTR) { + log::error("displayName is a {}, not a bstr", prop.vt); + } else { + displayName = QString::fromWCharArray(prop.bstrVal); + } + + VariantClear(&prop); + - DWORD state = 0; + // product state + DWORD state = 0; + ret = o->Get(L"productState", 0, &prop, 0, 0); + + if (FAILED(ret)) { + log::error("failed to get productState, {}", formatSystemMessage(ret)); + } else { if (prop.vt == VT_I4) { state = prop.lVal; - } else { + } else if (prop.vt == VT_UI4) { state = prop.ulVal; + } else if (prop.vt == VT_NULL) { + log::warn("productState is null"); + } else { + log::error("productState is a {}, not a VT_I4 or a VT_UI4", prop.vt); } + } - VariantClear(&prop); + VariantClear(&prop); - // guid - ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); - if (FAILED(ret)) { - log::error("failed to get instanceGuid, {}", formatSystemMessage(ret)); - return; - } - if (prop.vt != VT_BSTR) { - log::error("instanceGuid is a {}, is not a bstr", prop.vt); - return; - } + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; - const QUuid guid(QString::fromWCharArray(prop.bstrVal)); - VariantClear(&prop); + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); - const auto provider = static_cast((state >> 16) & 0xff); - const auto scanner = (state >> 8) & 0xff; - const auto definitions = state & 0xff; + return SecurityProduct(guid, displayName, provider, active, upToDate); +} - const bool active = ((scanner & 0x10) != 0); - const bool upToDate = (definitions == 0); +std::vector getSecurityProductsFromWMI() +{ + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map map; - map.insert({ - guid, - {guid, QString::fromStdWString(name), provider, active, upToDate}}); + auto f = [&](auto* o) { + if (auto p=handleProduct(o)) { + map.emplace(p->guid(), std::move(*p)); + } }; { WMI wmi("root\\SecurityCenter2"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + wmi.query("select * from AntivirusProduct", f); + wmi.query("select * from FirewallProduct", f); + wmi.query("select * from AntiSpywareProduct", f); } { WMI wmi("root\\SecurityCenter"); - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + wmi.query("select * from AntivirusProduct", f); + wmi.query("select * from FirewallProduct", f); + wmi.query("select * from AntiSpywareProduct", f); } std::vector v; diff --git a/src/envsecurity.h b/src/envsecurity.h index 436103b7..5f9e5332 100644 --- a/src/envsecurity.h +++ b/src/envsecurity.h @@ -16,6 +16,10 @@ public: QUuid guid, QString name, int provider, bool active, bool upToDate); + // guid + // + const QUuid& guid() const; + // display name of the product // const QString& name() const; -- cgit v1.3.1 From d0099f577e9eabc7575321e68754fd41663827fe Mon Sep 17 00:00:00 2001 From: Al Date: Mon, 23 Sep 2019 17:40:09 +0200 Subject: Changed label of executable dialog to be more clear about application icons. --- src/editexecutablesdialog.ui | 2 +- src/organizer_en.ts | 2974 +++++++++++++++++++++++------------------- 2 files changed, 1616 insertions(+), 1360 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index a42dbeed..c2ff7d31 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -428,7 +428,7 @@ Right now the only case I know of where this needs to be overwritten is for the - Use Application's Icon for shortcuts + Use Application's Icon for desktop shortcuts diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 1aa831a0..465b0c87 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -173,17 +173,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -201,12 +201,12 @@ p, li { white-space: pre-wrap; } - + new - + failed to start download @@ -276,12 +276,12 @@ p, li { white-space: pre-wrap; } - + Add - + Remove @@ -289,32 +289,32 @@ p, li { white-space: pre-wrap; } ConflictsTab - + &Hide - + &Unhide - + &Open/Execute - + &Preview - + Open in &Explorer - + &Go to... @@ -368,114 +368,114 @@ p, li { white-space: pre-wrap; } DownloadList - + Name - + Mod name - + Version - + Nexus ID - + Size - + Status - + Filetime - + < game %1 mod %2 file %3 > - + Unknown - + Pending - + Started - + Canceling - + Pausing - + Canceled - + Paused - + Error - - + + Fetching Info - + Downloaded - + Installed - + Uninstalled - + Pending download - + Information missing, please select "Query Info" from the context menu to re-retrieve. @@ -483,156 +483,156 @@ p, li { white-space: pre-wrap; } DownloadListWidget - + Install - + Query Info - + Visit on Nexus - + Open File - - - + + + Show in Folder - - + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - + Resume - + Delete Installed Downloads... Delete Installed... - + Delete Uninstalled Downloads... Delete Uninstalled... - + Delete All Downloads... Delete All... - + Hide Installed... - + Hide Uninstalled... - + Hide All... - + Un-Hide All... - - - - + + + + Delete Files? - + This will permanently delete the selected download. Are you absolutely sure you want to proceed? - + This will remove all finished downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all installed downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all uninstalled downloads from this list and from disk. Are you absolutely sure you want to proceed? - - - + + + Hide Files? - + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + This will remove all uninstalled downloads from this list (but NOT from disk). @@ -645,37 +645,37 @@ Are you absolutely sure you want to proceed? - + Memory allocation error (in refreshing directory). - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name "%1" has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + There is already a download queued for this file. Mod %1 @@ -683,12 +683,12 @@ File %2 - + Already Queued - + There is already a download started for this file. Mod %1: %2 @@ -696,266 +696,266 @@ File %3: %4 - + Already Started - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - - + + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1147,7 +1147,8 @@ Right now the only case I know of where this needs to be overwritten is for the - Use Application's Icon for shortcuts + Use Application's Icon for desktop shortcuts + Use Application's Icon for shortcuts @@ -1156,42 +1157,43 @@ Right now the only case I know of where this needs to be overwritten is for the - + Reset plugin executables - + 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. - + + New Executable - + Select a binary - + Executable (%1) - + Select a directory - + Java (32-bit) required - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. @@ -1199,73 +1201,73 @@ Right now the only case I know of where this needs to be overwritten is for the FileTreeTab - + &New Folder - + &Open/Execute - + &Preview - + Open in &Explorer - + &Rename - + &Delete - + &Hide - + &Unhide - - + + New Folder - + Failed to create "%1" - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 @@ -1405,83 +1407,83 @@ Right now the only case I know of where this needs to be overwritten is for the - + failed to create backup - + Mod Name - + Name - + Invalid name - + The name you entered is invalid, please enter a different one. - + File format "%1" not supported - + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - + unknown archive error @@ -1523,22 +1525,52 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - LogBuffer + LogList + + + &Copy all + + + + + C&lear all + + + + + &Level + + + + + &Debug + + + + + &Info + + + + + &Warnings + + - - failed to write log to %1: %2 + + &Errors MOApplication - + an error occurred: %1 - + an error occurred @@ -1546,27 +1578,27 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOBase::TextViewer - + Save changes? - + Do you want to save changes to %1? - + failed to write to %1 - + file not found: %1 - + Save @@ -1574,7 +1606,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOBase::TutorialControl - + Tutorial failed to start, please check "mo_interface.log" for details. @@ -1590,48 +1622,48 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - - + + Categories - + Clear - + If checked, only mods that match all selected categories are displayed. - + And - + If checked, all mods that match at least one of the selected categories are displayed. - + Or - + Profile - + Pick a module collection - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1641,84 +1673,84 @@ p, li { white-space: pre-wrap; } - + Open list options... - + Refresh list. This is usually not necessary unless you modified data outside the program. - + Show Open Folders menu... - - + + Restore Backup... - - - + + + Create Backup - - + + Active: - + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available mods. - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - + Clear all Filters - + No groups - + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1728,12 +1760,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1742,17 +1774,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1761,32 +1793,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1795,27 +1827,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1823,72 +1855,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1899,1181 +1931,1170 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Main ToolBar - + &File - - + + &Tools - - - + + + &Help - + &View - + &Toolbars - + &Run - + + + Log + + + + Install &Mod... - + Install &Mod - - + + Install a new mod from an archive - + Ctrl+M - + &Profiles... - + &Profiles - - + + Configure profiles - + Ctrl+P - + &Executables... - + &Executables - - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + &Tool Plugins - + Tools - + Ctrl+I - + &Settings... - + &Settings - - + + Configure settings and workarounds - + Ctrl+S - - + + Visit &Nexus - - + + Visit the Nexus website in your browser for more mods - + Ctrl+N - - + + &Update Mod Organizer - - + + Mod Organizer is up-to-date - + &Notifications... - - + + Open the notifications dialog - + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Show help options - + Ctrl+H - - + + &Endorse ModOrganizer - - - + + + Endorse Mod Organizer - - - Copy &Log - - - - - - Copy log to clipboard - - - - + &Change Game... - + &Change Game - - + + Open the Instance selection dialog to manage a different Game - - + + E&xit - - + + Exits Mod Organizer - + M&ain Toolbar - + &Small Icons - + Lar&ge Icons - + &Icons Only - + &Text Only - + I&cons and Text - + M&edium Icons - + &Menu - + Status &bar St&atus bar - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - - failed to spawn notepad.exe: %1 - - - - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3081,12 +3102,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3094,12 +3115,12 @@ You can also use online editors and converters instead. - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3107,348 +3128,336 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + Thank you for endorsing MO2! :) - - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - - Okay. - - - - - This mod will not be endorsed and will no longer ask you to endorse. - - - - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occurred - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3940,12 +3949,12 @@ p, li { white-space: pre-wrap; } ModInfoRegular - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -4280,7 +4289,7 @@ p, li { white-space: pre-wrap; } ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -4301,50 +4310,16 @@ p, li { white-space: pre-wrap; } MyFileSystemModel - + Overwrites - + not implemented - - NXMAccessManager - - - - Validating Nexus Connection - - - - - There was a timeout during the request - - - - - Unknown error - - - - - Validation failed, please reauthenticate in the Settings -> Nexus tab: %1 - - - - - Could not parse response. Invalid JSON. - - - - - Unknown error. - - - NXMUrl @@ -4356,32 +4331,32 @@ p, li { white-space: pre-wrap; } NexusInterface - + Failed to guess mod id for "%1", please pick the correct one - + You must authorize MO2 in Settings -> Nexus to use the Nexus API. - + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. - + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. - + empty response - + invalid response @@ -4432,27 +4407,27 @@ p, li { white-space: pre-wrap; } NexusTab - + Current Version: %1 - + No update available - + Tracked - + Untracked - + <div style="text-align: center;"> <p>This mod does not have a valid Nexus ID. You can add a custom web @@ -4464,7 +4439,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4472,269 +4447,212 @@ p, li { white-space: pre-wrap; } OrganizerCore - - + Failed to write settings - - An error occurred trying to update MO settings to %1: %2 - - - - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + An error occurred trying to write back MO settings to %1: %2 - - + + Download started - + Download failed - - - - + + + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + The mod was not installed completely. - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - - Executable not found: %1 - - - - - Start Steam? - - - - - Steam is required to be running already to correctly start the game. Should MO try to start steam now? - - - - - Steam: Access Denied - - - - - MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely necessary. - -Restart MO as administrator? - - - - + Error - - Windows Event Log Error - - - - - The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. - -Continue launching %1? - - - - - Blacklisted Executable - - - - - The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. - -Continue launching %1? - - - - + No profile set - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -4742,12 +4660,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -4770,63 +4688,63 @@ Continue? - + &Delete - + &Rename - + &Open - + &New Folder - + mod not found: %1 - + Failed to delete "%1" - - - - + + + + Confirm - - + + Are you sure you want to delete "%1"? - - + + Are you sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" @@ -4834,12 +4752,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -4847,18 +4765,18 @@ Continue? PluginContainer - + Some plugins could not be loaded - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: @@ -4933,68 +4851,68 @@ Continue? - + The file containing locked plugin indices is broken - + <b>Origin</b>: %1 - + <br><b><i>This plugin can't be disabled (enforced by the game).</i></b> - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - + failed to restore load order for %1 @@ -5033,7 +4951,12 @@ Continue? <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click a notification above to get more details...</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html> @@ -5042,18 +4965,18 @@ p, li { white-space: pre-wrap; } - - + + Fix - + No guided fix - + (There are no notifications) @@ -5061,17 +4984,17 @@ p, li { white-space: pre-wrap; } Profile - + invalid profile name: %1 - + failed to create %1 - + failed to write mod list: %1 @@ -5081,53 +5004,51 @@ p, li { white-space: pre-wrap; } - + failed to create tweaked ini: %1 - + failed to open %1 - + "%1" is missing or inaccessible - - - - - - - + + + + + invalid mod index: %1 - + invalid priority %1 - + Delete profile-specific save games? - + Do you want to delete the profile-specific save games? (If you select "No", the save games will show up again if you re-enable profile-specific save games) - + Missing profile-specific game INI files! - + Some of your profile-specific game INI files were missing. They will now be copied from the vanilla game folder. You might want to double-check your settings. Missing files: @@ -5135,12 +5056,12 @@ Missing files: - + Delete profile-specific game INI files? - + Do you want to delete the profile-specific game INI files? (If you select "No", the INI files will be used again if you re-enable profile-specific game INI files.) @@ -5310,83 +5231,83 @@ p, li { white-space: pre-wrap; } - - + + failed to create profile: %1 - + Name - + Please enter a name for the new profile - + failed to copy profile: %1 - + Invalid name - + Invalid profile name - + Deleting active profile - + Unable to delete active profile. Please change to a different profile first. - + Confirm - + Are you sure you want to remove this profile (including profile-specific save games, if any)? - + Profile broken - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + Rename Profile - + New Name - + failed to change archive invalidation state: %1 - + failed to determine if invalidation is active: %1 @@ -5400,12 +5321,12 @@ p, li { white-space: pre-wrap; } - + Mod Organizer is attempting to write to "%1" which is currently set to read-only. Clear the read-only flag to allow the write? - + File is read-only @@ -5413,61 +5334,119 @@ p, li { white-space: pre-wrap; } QObject - - - - - + + + + + + + Error + + + You can reset these choices by clicking "Reset Dialog Choices" in the General tab of the Settings + + + + + Always ask + + + + + + Remember my choice + + + + + Remember my choice for %1 + + failed to open temporary file - + removal of "%1" failed: %2 - + removal of "%1" failed - + "%1" doesn't exist (remove) - - + + failed to create directory "%1" - - + + failed to copy "%1" to "%2" - + + %1 MB + + + + + %1 GB + + + + + %1 TB + + + + + %1 KB + + + + + %1 B/s + + + + + %1 KB/s + + + + + %1 MB/s + + + + Failed to save custom categories - - - - + + + + invalid category index: %1 - + invalid category id: %1 @@ -5512,52 +5491,41 @@ p, li { white-space: pre-wrap; } - + The hidden file "%1" already exists. Replace it? - + The visible file "%1" already exists. Replace it? - + Replace file? - - + + File operation failed - + Failed to remove "%1". Maybe you lack the required file permissions? - + failed to rename %1 to %2 - + Filter - - - - helper failed - - - - - failed to determine account name - - @@ -5570,460 +5538,782 @@ p, li { white-space: pre-wrap; } - + Deleting folder - + I'm about to delete the following folder: "%1". Proceed? - + Choose Instance to Delete - + Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched. - + Are you sure? - + Are you really sure you want to delete the Instance "%1" with all its files? - + Failed to delete Instance - + Could not delete Instance "%1". If the folder was still in use, restart MO and try again. - + Enter a Name for the new Instance - + Enter a new name or select one from the suggested list: (This is just a name for the Instance and can be whatever you wish, the actual game selection will happen on the next screen regardless of chosen name) - - + + Canceled - + Invalid instance name - + The instance name "%1" is invalid. Use the name "%2" instead? - + The instance "%1" already exists. - + Please choose a different instance name, like: "%1 1" . - + Choose Instance - + Each Instance is a full set of MO data files (mods, downloads, profiles, configuration, ...). You can use multiple instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. If your MO folder is writable, you can also store a single instance locally (called a Portable install, and all the MO data files will be inside the installation folder). - + New - + Create a new instance. - + Portable - + Use MO folder for data. - + Manage Instances - + Delete an Instance. - - + + failed to create %1 - + Data directory created - + New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings. - - failed to open %1: %2 + + + Failed to create "%1". Your user account probably lacks permission. - - - file not found: %1 + + Plugin to handle %1 no longer installed - - Failed to delete %1 + + + + The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly. - - Failed to deactivate script extender loading + + Could not use configuration settings for game "%1", path "%2". - - Failed to remove %1: %2 + + + + Please select the installation of %1 to manage - - - Failed to rename %1 to %2 + + + + Please select the game to manage - - Failed to deactivate proxy-dll loading + + Canceled finding %1 in "%2". - - - - Failed to copy %1 to %2 + + Canceled finding game in "%1". - - Failed to set up script extender loading + + %1 not identified in "%2". The directory is required to contain the game binary. - - Failed to delete old proxy-dll %1 + + No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul> - - Failed to overwrite %1 + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - - Failed to set up proxy-dll loading + + failed to start shortcut: %1 - - - Failed to create "%1". Your user account probably lacks permission. + + failed to start application: %1 - - Plugin to handle %1 no longer installed + + + Mod Organizer - - - - The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly. + + An instance of Mod Organizer is already running - - Could not use configuration settings for game "%1", path "%2". + + Failed to set up instance - - - - Please select the installation of %1 to manage + + Please use "Help" from the toolbar to get usage instructions to all elements - - - - Please select the game to manage + + + <Manage...> - - Canceled finding %1 in "%2". + + failed to parse profile %1: %2 - - Canceled finding game in "%1". + + File Exists - - %1 not identified in "%2". The directory is required to contain the game binary. + + A file with that name exists, please enter a new one - - No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul> + + + Failed to move file - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + + Failed to create directory "optional" - - failed to start shortcut: %1 + + Save changes? - - failed to start application: %1 + + Save changes to "%1"? - - - Mod Organizer + + This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. - - An instance of Mod Organizer is already running + + This error typically happens because an antivirus is preventing Mod Organizer from starting programs. Add an exclusion for Mod Organizer's installation folder in your antivirus and try again. - - Failed to set up instance + + The file '%1' does not exist. - - Please use "Help" from the toolbar to get usage instructions to all elements + + + + + Cannot start Steam - - - <Manage...> + + The path to the Steam executable cannot be found. You might try reinstalling Steam. - - failed to parse profile %1: %2 + + + + Continue without starting Steam - - File Exists + + + The program may fail to launch. - - A file with that name exists, please enter a new one + + Cannot launch program - - - Failed to move file + + + + Cannot start %1 - - Failed to create directory "optional" + + Cannot launch helper - - Save changes? + + This program is requesting to run as administrator but Mod Organizer itself is not running as administrator. Running programs as administrator is typically unnecessary as long as the game and Mod Organizer have been installed outside "Program Files". + +You can restart Mod Organizer as administrator and try launching the program again. - - Save changes to "%1"? + + + Restart Mod Organizer as administrator - - Failed to start "%1" + + + You must allow "helper.exe" to make changes to the system. - + + Launch Steam + + + + + This program requires Steam + + + + + Mod Organizer has detected that this program likely requires Steam to be running to function properly. + + + + + Start Steam + + + + + + The program might fail to run. + + + + + Steam is running as administrator + + + + + Running Steam as administrator is typically unnecessary and can cause problems when Mod Organizer itself is not running as administrator. + +You can restart Mod Organizer as administrator and try launching the program again. + + + + + + + Continue + + + + + Event Log not running + + + + + The Event Log service is not running + + + + + The Windows Event Log service is not running. This can prevent USVFS from running properly and your mods may not be recognized by the program being launched. + + + + + + Your mods might not work. + + + + + Blacklisted program + + + + + The program %1 is blacklisted + + + + + The program you are attempting to launch is blacklisted in the virtual filesystem. This will likely prevent it from seeing any mods, INI files or any other virtualized files. + + + + + Change the blacklist + + + + Waiting - + Please press OK once you're logged into steam. - + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is incompatible with MO2's VFS system. - + Select binary - + Binary - + failed to initialize plugin %1: %2 - + Plugin error - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + failed to access %1 - + failed to set file time %1 - + Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! - - Script Extender + + + Elevation required + + + + + This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. + + + + + Loading... + + + + + &Save + + + + + &Word wrap + + + + + &Open in Explorer + + + + + Regular + + + + + Premium + + + + + + None + + + + + + Connecting to Nexus... + + + + + Waiting for Nexus... + + + + + Opened Nexus in browser. +Switch to your browser and accept the request. + + + + + + Finished. + + + + + No answer from Nexus. +A firewall might be blocking Mod Organizer. + + + + + Nexus closed the connection. + + + + + Cancelled. + + + + + Invalid JSON + + + + + Bad response + + + + + There was a timeout during the request + + + + + Cancelled + + + + + Failed to request %1 + + + + + + attempt to store setting for unknown plugin "%1" + + + + + Failed + + + + + Failed to start the helper application + + + + + Debug + + + + + Info (recommended) + + + + + Warning + + + + + Mini (recommended) + + + + + Data + + + + + Full + + + + + Confirm? + + + + + This will reset all the choices you made to dialogs and make them all visible again. Continue? + + + + + Disconnected. + + + + + Checking API key... + + + + + Received API key. + + + + + Linked with Nexus successfully. + + + + + + + + + + + + + Cancel + + + + + + + Enter API Key Manually + + + + + + + Connect to Nexus + + + + + + + + + N/A + + + + + Failed to create "%1", you may not have the necessary permissions. Path remains unchanged. + + + + + Select base directory - - Proxy DLL + + Select download directory - - failed to spawn "%1" + + Select mod directory - - Elevation required + + Select cache directory - - This process requires elevation to run. -This is a potential security risk so I highly advise you to investigate if -"%1" -can be installed to work without elevation. - -Restart Mod Organizer as an elevated process? -You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually. + + Select profiles directory - - - failed to spawn "%1": %2 + + Select overwrite directory - - This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. + + Select game executable - - Loading... + + Executables Blacklist - - &Save + + Enter one executable per line to be blacklisted from the virtual file system. +Mods and other virtualized files will not be visible to these executables and +any executables launched by them. + +Example: + Chrome.exe + Firefox.exe - - &Word wrap + + Restart Mod Organizer? - - &Open in Explorer + + In order to reset the geometry, Mod Organizer must be restarted. +Restart now? @@ -6068,13 +6358,13 @@ You will be asked if you want to allow helper.exe to make changes to the system. QuestionBoxMemory - + Remember selection - - Remember selection only for + + Remember selection only for %1 @@ -6198,46 +6488,6 @@ Select Show Details option to see the full change-log. - - Settings - - - Failed - - - - - Failed to start the helper application - - - - - - attempt to store setting for unknown plugin "%1" - - - - - Restart Mod Organizer? - - - - - In order to finish configuration changes, MO must be restarted. -Restart it now? - - - - - Error - - - - - Failed to create "%1", you may not have the necessary permission. path remains unchanged. - - - SettingsDialog @@ -6291,382 +6541,433 @@ p, li { white-space: pre-wrap; } - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). + If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. -If you use pre-releases, never contact me directly by e-mail or via private messages! +If you use pre-releases, never contact me directly by e-mail or via private messages! - + Install Pre-releases (Betas) - + User interface - + Colors - - + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - + Show mod list separator colors on the scrollbar - + Plugin is Contained in selected Mod - + Is overwritten (loose files) - + Is overwriting (loose files) - + Reset Colors - + Mod Contains selected Plugin - + Is overwritten (archive files) - + Is overwriting (archive files) - - + + Modify the categories available to arrange your mods. - + Configure Mod Categories - + Reset stored information from dialogs. - + This will make all dialogs show up again where you checked the "Remember selection"-box. - - Reset Dialogs - - - - + If checked, the download interface will be more compact. - + Compact Download Interface - + If checked, the download list will display meta information instead of file names. - + Download Meta Information - + Paths - - - - + + + + ... - + Caches - + Overwrite - - + + Directory where downloads are stored. - + Downloads - + Profiles - + Directory where mods are stored. - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Mods - + Managed Game - + Base Directory - + Use %BASE_DIR% to refer to the Base Directory. - + Important: All directories have to be writable! - - + Nexus - - Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things. - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things. Clicking &quot;Connect to Nexus&quot; will open a Nexus webpage to authorise Mod Organizer. You will need to be logged into your Nexus account. The authorisation is stored in the Windows Credential Manager. Your Nexus username and password are not required or stored by Mod Organizer.</p></body></html> - - - - + Connect to Nexus - + Manually enter the API key and try to login - + Enter API Key Manually - + Clear the stored Nexus API key and force reauthorization. - + Disconnect from Nexus - + + + <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> + + + + Remove cache and cookies. - + Clear Cache - + Disable automatic internet features - + + Reset Dialog Choices + + + + + Nexus Account + + + + + User ID: + + + + + id + + + + + Name: + + + + + name + + + + + Account: + + + + + account + + + + + Statistics + + + + + Daily requests: + + + + + daily requests + + + + + Hourly requests: + + + + + hourly requests + + + + + Nexus Connection + + + + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use HTTP Proxy (Uses System Settings) - + Endorsement Integration - - - <html><head/><body><p>By default, a counter is displayed under the mod list. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> - - - - + Hide API Request Counter - + Associate with "Download with manager" links - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + Username - - Password + + <html><head/><body><p>If you save your steam user ID and password here, they will be used when logging into steam.</p></body></html> - - If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. + + Password - + Plugins - + Author: - + Version: - + Description: - + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -6682,17 +6983,17 @@ p, li { white-space: pre-wrap; } - + Load Mechanism - + Select loading mechanism. See help for details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -6703,28 +7004,28 @@ If you use the Steam version of Oblivion the default will NOT work. In this case - + Enforces that inactive ESPs and ESMs are never loaded. - + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + Hide inactive ESPs/ESMs - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -6732,66 +7033,66 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable parsing of Archives (Experimental Feature) - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -6800,48 +7101,48 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Configure Executables Blacklist - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + Max Dumps To Keep - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -6849,12 +7150,12 @@ programs you are intentionally running. - + Hint: right click link and copy link location - + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. @@ -6864,17 +7165,17 @@ programs you are intentionally running. - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -6885,37 +7186,17 @@ programs you are intentionally running. - - None - - - - - Mini (recommended) - - - - - Data - - - - - Full - - - - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. @@ -6923,102 +7204,26 @@ programs you are intentionally running. - - Debug - - - - - Info (recommended) - - - - - Warning + + Restart Mod Organizer? - - - Error + + In order to finish configuration changes, MO must be restarted. +Restart it now? - + Confirm - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? - - - Executables Blacklist - - - - - Enter one executable per line to be blacklisted from the virtual file system. -Mods and other virtualized files will not be visible to these executables and -any executables launched by them. - -Example: - Chrome.exe - Firefox.exe - - - - - Select base directory - - - - - Select download directory - - - - - Select mod directory - - - - - Select cache directory - - - - - Select profiles directory - - - - - Select overwrite directory - - - - - Select game executable - - - - - Confirm? - - - - - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - - - - - Failed to retrieve a Nexus API key! Please try again. A browser window should open asking you to authorize. - - SingleInstance @@ -7061,21 +7266,59 @@ Example: - + <don't sync> - + failed to remove %1 - + failed to move %1 to %2 + + TaskDialog + + + Dialog + + + + + icon + + + + + dummy main text + + + + + dummy content text + + + + + dummy button + + + + + dummy checkbox + + + + + Details + + + TextViewer @@ -7162,22 +7405,22 @@ On Windows XP: - + Characters for profile %1 - + Overwrite - + Overwrite the file "%1" - + Confirm @@ -7185,11 +7428,24 @@ On Windows XP: UsvfsConnector - + Preparing vfs + + ValidationProgressDialog + + + Validating Nexus Connection + + + + + Hide + + + WaitingOnCloseDialog -- cgit v1.3.1 From 829124d8b899101370e55eb2a9cb9164ffd68a55 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 23 Sep 2019 17:52:46 -0400 Subject: ensure windows are on screen --- src/settings.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++++------------ src/settings.h | 51 ++++++++++++++++++++++---------- 2 files changed, 107 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 7cea52fb..19eca5ec 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -22,7 +22,9 @@ along with Mod Organizer. If not, see . #include "serverinfo.h" #include "executableslist.h" #include "appconfig.h" -#include "expanderwidget.h" +#include "env.h" +#include "envmetrics.h" +#include #include #include @@ -604,21 +606,80 @@ void GeometrySettings::resetIfNeeded() removeSection(m_Settings, "Geometry"); } -void GeometrySettings::saveGeometry(const QWidget* w) +void GeometrySettings::saveGeometry(const QMainWindow* w) +{ + saveWindowGeometry(w); +} + +bool GeometrySettings::restoreGeometry(QMainWindow* w) const +{ + return restoreWindowGeometry(w); +} + +void GeometrySettings::saveGeometry(const QDialog* d) +{ + saveWindowGeometry(d); +} + +bool GeometrySettings::restoreGeometry(QDialog* d) const +{ + return restoreWindowGeometry(d); +} + +void GeometrySettings::saveWindowGeometry(const QWidget* w) { set(m_Settings, "Geometry", geoSettingName(w), w->saveGeometry()); } -bool GeometrySettings::restoreGeometry(QWidget* w) const +bool GeometrySettings::restoreWindowGeometry(QWidget* w) const { if (auto v=getOptional(m_Settings, "Geometry", geoSettingName(w))) { w->restoreGeometry(*v); + ensureWindowOnScreen(w); return true; } return false; } +void GeometrySettings::ensureWindowOnScreen(QWidget* w) const +{ + // users report that the main window and/or dialogs are displayed off-screen; + // the usual workaround is keyboard navigation to move it + // + // qt should have code that deals with multiple monitors and off-screen + // geometries, but there seems to be bugs or inconsistencies that can't be + // reproduced + // + // the closest would probably be https://bugreports.qt.io/browse/QTBUG-64498, + // which is about multiple monitors and high dpi, but it seems fixed as of + // 5.12.4, which is shipped with 2.2.1 + // + // without being to reproduce the problem, some simple checks are made in a + // timer, which may mitigate the issues + + QTimer::singleShot(100, w, [w] { + const auto borders = 20; + + // desktop geometry, made smaller to make sure there isn't just a few pixels + const auto originalDg = env::Environment().metrics().desktopGeometry(); + const auto dg = originalDg.adjusted(borders, borders, -borders, -borders); + + const auto g = w->geometry(); + + if (!dg.intersects(g)) { + log::warn( + "window '{}' is offscreen, moving to main monitor; geo={}, desktop={}", + w->objectName(), g, originalDg); + + // widget is off-screen, center it on main monitor + centerOnMonitor(w, -1); + + log::warn("window '{}' now at {}", w->objectName(), w->geometry()); + } + }); +} + void GeometrySettings::saveState(const QMainWindow* w) { set(m_Settings, "Geometry", stateSettingName(w), w->saveState()); @@ -771,12 +832,17 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { const auto monitor = getOptional( - m_Settings, "Geometry", "MainWindow_monitor"); + m_Settings, "Geometry", "MainWindow_monitor").value_or(-1); + + centerOnMonitor(w, monitor); +} +void GeometrySettings::centerOnMonitor(QWidget* w, int monitor) +{ QPoint center; - if (monitor && QGuiApplication::screens().size() > *monitor) { - center = QGuiApplication::screens().at(*monitor)->geometry().center(); + if (monitor >= 0 && monitor < QGuiApplication::screens().size()) { + center = QGuiApplication::screens().at(monitor)->geometry().center(); } else { center = QGuiApplication::primaryScreen()->geometry().center(); } @@ -1887,15 +1953,3 @@ void DiagnosticsSettings::setCrashDumpsMax(int n) { set(m_Settings, "Settings", "crash_dumps_max", n); } - - -GeometrySaver::GeometrySaver(Settings& s, QDialog* dialog) - : m_settings(s), m_dialog(dialog) -{ - m_settings.geometry().restoreGeometry(m_dialog); -} - -GeometrySaver::~GeometrySaver() -{ - m_settings.geometry().saveGeometry(m_dialog); -} diff --git a/src/settings.h b/src/settings.h index cd478a5b..b5366911 100644 --- a/src/settings.h +++ b/src/settings.h @@ -41,20 +41,6 @@ class ServerList; class Settings; -// helper class that calls restoreGeometry() in the constructor and -// saveGeometry() in the destructor -// -class GeometrySaver -{ -public: - GeometrySaver(Settings& s, QDialog* dialog); - ~GeometrySaver(); - -private: - Settings& m_settings; - QDialog* m_dialog; -}; - // setting for the currently managed game // @@ -141,8 +127,11 @@ public: void resetIfNeeded(); - void saveGeometry(const QWidget* w); - bool restoreGeometry(QWidget* w) const; + void saveGeometry(const QMainWindow* w); + bool restoreGeometry(QMainWindow* w) const; + + void saveGeometry(const QDialog* d); + bool restoreGeometry(QDialog* d) const; void saveState(const QMainWindow* window); bool restoreState(QMainWindow* window) const; @@ -182,6 +171,12 @@ public: private: QSettings& m_Settings; bool m_Reset; + + void saveWindowGeometry(const QWidget* w); + bool restoreWindowGeometry(QWidget* w) const; + + void ensureWindowOnScreen(QWidget* w) const; + static void centerOnMonitor(QWidget* w, int monitor); }; @@ -764,4 +759,28 @@ private: DiagnosticsSettings m_Diagnostics; }; + +// helper class that calls restoreGeometry() in the constructor and +// saveGeometry() in the destructor +// +template +class GeometrySaver +{ +public: + GeometrySaver(Settings& s, W* w) + : m_settings(s), m_widget(w) + { + m_settings.geometry().restoreGeometry(m_widget); + } + + ~GeometrySaver() + { + m_settings.geometry().saveGeometry(m_widget); + } + +private: + Settings& m_settings; + W* m_widget; +}; + #endif // SETTINGS_H -- cgit v1.3.1 From 200b5283eb5a0eff5ed18e772930b99eea5e11ef Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 23 Sep 2019 18:21:39 -0400 Subject: added center dialogs option moved download list options to their own group box renamed confusing "Download Meta Information" to "Show Meta Information" --- src/settings.cpp | 34 ++++++- src/settings.h | 6 ++ src/settingsdialog.ui | 208 ++++++++++++++++++++++-------------------- src/settingsdialoggeneral.cpp | 2 + 4 files changed, 149 insertions(+), 101 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 19eca5ec..c3e8781e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -623,7 +623,13 @@ void GeometrySettings::saveGeometry(const QDialog* d) bool GeometrySettings::restoreGeometry(QDialog* d) const { - return restoreWindowGeometry(d); + const auto r = restoreWindowGeometry(d); + + if (centerDialogs()) { + centerOnParent(d); + } + + return r; } void GeometrySettings::saveWindowGeometry(const QWidget* w) @@ -829,6 +835,16 @@ void GeometrySettings::setModInfoTabOrder(const QString& names) set(m_Settings, "Widgets", "ModInfoTabOrder", names); } +bool GeometrySettings::centerDialogs() const +{ + return get(m_Settings, "Settings", "center_dialogs", false); +} + +void GeometrySettings::setCenterDialogs(bool b) +{ + set(m_Settings, "Settings", "center_dialogs", b); +} + void GeometrySettings::centerOnMainWindowMonitor(QWidget* w) { const auto monitor = getOptional( @@ -850,6 +866,22 @@ void GeometrySettings::centerOnMonitor(QWidget* w, int monitor) w->move(center - w->rect().center()); } +void GeometrySettings::centerOnParent(QWidget* w, QWidget* parent) +{ + if (!parent) { + parent = w->parentWidget(); + + if (!parent) { + parent = qApp->activeWindow(); + } + } + + if (parent && parent->isVisible()) { + const auto pr = parent->geometry(); + w->move(pr.center() - w->rect().center()); + } +} + void GeometrySettings::saveMainWindowMonitor(const QMainWindow* w) { if (auto* handle=w->windowHandle()) { diff --git a/src/settings.h b/src/settings.h index b5366911..ee6ff3fe 100644 --- a/src/settings.h +++ b/src/settings.h @@ -160,6 +160,11 @@ public: QStringList modInfoTabOrder() const; void setModInfoTabOrder(const QString& names); + // whether dialogs should be centered on their parent + // + bool centerDialogs() const; + void setCenterDialogs(bool b); + // assumes the given widget is a top-level // void centerOnMainWindowMonitor(QWidget* w); @@ -177,6 +182,7 @@ private: void ensureWindowOnScreen(QWidget* w) const; static void centerOnMonitor(QWidget* w, int monitor); + static void centerOnParent(QWidget* w, QWidget* parent=nullptr); }; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 40079441..9d1e4da1 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -86,97 +86,23 @@ p, li { white-space: pre-wrap; } - User interface + User Interface - - - - - Colors - - - - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - - - Show mod list separator colors on the scrollbar - - - true - - - - - - - Plugin is Contained in selected Mod - - - - - - - Is overwritten (loose files) - - - - - - - Is overwriting (loose files) - - - - - - - Reset Colors - - - - - - - Mod Contains selected Plugin - - - - - - - Is overwritten (archive files) - - - - - - - Is overwriting (archive files) - - - - - - - - - - Modify the categories available to arrange your mods. + + + + + Dialogs will always be centered on the main window, but will remember their size. - Modify the categories available to arrange your mods. + Dialogs will always be centered on the main window, but will remember their size. - Configure Mod Categories + Always center dialogs - + @@ -195,36 +121,119 @@ p, li { white-space: pre-wrap; } - - + + - If checked, the download interface will be more compact. + Modify the categories available to arrange your mods. + + + Modify the categories available to arrange your mods. - Compact Download Interface + Configure Mod Categories - - - - Qt::Vertical + + + + + + + Download List + + + + + + If checked, the download interface will be more compact. - - - 20 - 40 - + + Compact List - + - + If checked, the download list will display meta information instead of file names. - Download Meta Information + Show Meta Information + + + + + + + + + + Colors + + + + + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + + Show mod list separator colors on the scrollbar + + + true + + + + + + + Plugin is Contained in selected Mod + + + + + + + Is overwritten (loose files) + + + + + + + Is overwriting (loose files) + + + + + + + Reset Colors + + + + + + + Mod Contains selected Plugin + + + + + + + Is overwritten (archive files) + + + + + + + Is overwriting (archive files) @@ -1411,7 +1420,6 @@ programs you are intentionally running. styleBox logLevelBox usePrereleaseBox - compactBox categoriesBtn baseDirEdit browseBaseDirBtn diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 8ecdcbb9..ae924393 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -51,6 +51,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) setContainsColor(settings().colors().modlistContainsPlugin()); setContainedColor(settings().colors().pluginListContained()); + ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); @@ -91,6 +92,7 @@ void GeneralSettingsTab::update() settings().colors().setModlistContainsPlugin(getContainsColor()); settings().colors().setPluginListContained(getContainedColor()); + settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); -- cgit v1.3.1 From 088f27fe48cd8f218052090a97e8187eedf0c06e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 23 Sep 2019 19:10:19 -0400 Subject: changed the layout of the general settings tab added option to disable checking for updates removed online check, just try it and see --- src/mainwindow.cpp | 16 ++++ src/mainwindow.h | 1 + src/organizercore.cpp | 36 ++------ src/organizercore.h | 1 + src/selfupdater.cpp | 12 ++- src/selfupdater.h | 14 ++- src/settings.cpp | 10 +++ src/settings.h | 5 ++ src/settingsdialog.ui | 203 ++++++++++++++++++++++++++---------------- src/settingsdialoggeneral.cpp | 2 + 10 files changed, 187 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42cbe919..cd650d2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -263,6 +263,7 @@ MainWindow::MainWindow(Settings &settings setupToolbar(); toggleMO2EndorseState(); + toggleUpdateAction(); TaskProgressManager::instance().tryCreateTaskbar(); @@ -5007,6 +5008,7 @@ void MainWindow::on_actionSettings_triggered() bool oldDisplayForeign(settings.interface().displayForeign()); bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); + const bool oldCheckForUpdates = settings.checkForUpdates(); SettingsDialog dialog(&m_PluginContainer, settings, this); @@ -5084,6 +5086,14 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.cycleDiagnostics(); toggleMO2EndorseState(); + + if (oldCheckForUpdates != settings.checkForUpdates()) { + toggleUpdateAction(); + + if (settings.checkForUpdates()) { + m_OrganizerCore.checkForUpdates(); + } + } } void MainWindow::on_actionNexus_triggered() @@ -5596,6 +5606,12 @@ void MainWindow::toggleMO2EndorseState() ui->actionEndorseMO->setStatusTip(text); } +void MainWindow::toggleUpdateAction() +{ + const auto& s = m_OrganizerCore.settings(); + ui->actionUpdate->setVisible(s.checkForUpdates()); +} + void MainWindow::nxmEndorsementsAvailable(QVariant userData, QVariant resultData, int) { QVariantList data = resultData.toList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 1f997ab1..524e2b6e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -313,6 +313,7 @@ private: void sendSelectedPluginsToPriority(int newPriority); void toggleMO2EndorseState(); + void toggleUpdateAction(); private: diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0da5b604..a4a89c99 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -74,25 +74,6 @@ using namespace MOBase; //static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static bool isOnline() -{ - const auto runningFlags = - QNetworkInterface::IsUp | QNetworkInterface::IsRunning; - - for (auto&& i : QNetworkInterface::allInterfaces()) { - if (!(i.flags() & QNetworkInterface::IsLoopBack)) { - if (i.flags() & runningFlags) { - auto addresses = i.addressEntries(); - if (!addresses.empty()) { - return true; - } - } - } - } - - return false; -} - static std::wstring getProcessName(HANDLE process) { wchar_t buffer[MAX_PATH]; @@ -307,14 +288,15 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, m_InstallationManager.setParentWidget(widget); m_Updater.setUserInterface(widget); - if (userInterface != nullptr) { - // this currently wouldn't work reliably if the ui isn't initialized yet to - // display the result - if (isOnline() && !m_Settings.network().offlineMode()) { - m_Updater.testForUpdate(); - } else { - log::debug("user doesn't seem to be connected to the internet"); - } + checkForUpdates(); +} + +void OrganizerCore::checkForUpdates() +{ + // this currently wouldn't work reliably if the ui isn't initialized yet to + // display the result + if (m_UserInterface != nullptr) { + m_Updater.testForUpdate(m_Settings); } } diff --git a/src/organizercore.h b/src/organizercore.h index a14d79a9..5de550df 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -109,6 +109,7 @@ public: void updateExecutablesList(); + void checkForUpdates(); void startMOUpdate(); Settings &settings(); diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 0ca39b19..8887927a 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -122,8 +122,18 @@ void SelfUpdater::setPluginContainer(PluginContainer *pluginContainer) m_Interface->setPluginContainer(pluginContainer); } -void SelfUpdater::testForUpdate() +void SelfUpdater::testForUpdate(const Settings& settings) { + if (settings.network().offlineMode()) { + log::debug("not checking for updates, in offline mode"); + return; + } + + if (!settings.checkForUpdates()) { + log::debug("not checking for updates, disabled"); + return; + } + // TODO: if prereleases are disabled we could just request the latest release // directly try { diff --git a/src/selfupdater.h b/src/selfupdater.h index bce49495..0c81efc5 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -37,7 +37,7 @@ namespace MOBase { class IPluginGame; } class QNetworkReply; class QProgressDialog; - +class Settings; /** * @brief manages updates for Mod Organizer itself @@ -80,6 +80,11 @@ public: void setPluginContainer(PluginContainer *pluginContainer); + /** + * @brief request information about the current version + **/ + void testForUpdate(const Settings& settings); + /** * @brief start the update process * @note this should not be called if there is no update available @@ -91,13 +96,6 @@ public: **/ MOBase::VersionInfo getVersion() const { return m_MOVersion; } -public slots: - - /** - * @brief request information about the current version - **/ - void testForUpdate(); - signals: /** diff --git a/src/settings.cpp b/src/settings.cpp index c3e8781e..462cd92a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -178,6 +178,16 @@ QString Settings::filename() const return m_Settings.fileName(); } +bool Settings::checkForUpdates() const +{ + return get(m_Settings, "Settings", "check_for_updates", true); +} + +void Settings::setCheckForUpdates(bool b) +{ + set(m_Settings, "Settings", "check_for_updates", b); +} + bool Settings::usePrereleases() const { return get(m_Settings, "Settings", "use_prereleases", false); diff --git a/src/settings.h b/src/settings.h index ee6ff3fe..1556ba1e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -692,6 +692,11 @@ public: bool archiveParsing() const; void setArchiveParsing(bool b); + // whether the user wants to check for updates + // + bool checkForUpdates() const; + void setCheckForUpdates(bool b); + // whether the user wants to upgrade to pre-releases // bool usePrereleases() const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 9d1e4da1..78bae6d7 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -7,7 +7,7 @@ 0 0 586 - 486 + 491 @@ -23,75 +23,67 @@ General - - - - - - - Language - - - - - - - The display language - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - - - - - - - - - - - Style - - - - - - - graphical style - - - graphical style of the MO user interface - - - - - - - - - Update to non-stable releases. - - - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + + + + + Qt::Vertical - - Install Pre-releases (Betas) + + + 0 + 0 + - + - + User Interface - - + + + + + Style + + + + + + + Language + + + + + + + The display language + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + + + + + + + graphical style + + + graphical style of the MO user interface + + + + - + Dialogs will always be centered on the main window, but will remember their size. @@ -102,7 +94,7 @@ p, li { white-space: pre-wrap; } - + @@ -121,7 +113,7 @@ p, li { white-space: pre-wrap; } - + Modify the categories available to arrange your mods. @@ -137,36 +129,49 @@ p, li { white-space: pre-wrap; } - + Download List - + - + - If checked, the download interface will be more compact. + If checked, the download list will display meta information instead of file names. - Compact List + Show Meta Information - + - If checked, the download list will display meta information instead of file names. + If checked, the download interface will be more compact. - Show Meta Information + Compact List + + + + Qt::Vertical + + + + 0 + 0 + + + + - + Colors @@ -240,6 +245,54 @@ p, li { white-space: pre-wrap; } + + + + Updates + + + + + + Mod Organizer checks for updates on Github on startup. + + + Mod Organizer checks for updates on Github on startup. + + + Check for updates + + + + + + + Update to non-stable releases. + + + If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + + + Install Pre-releases (Betas) + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + @@ -1416,11 +1469,7 @@ programs you are intentionally running. - languageBox - styleBox logLevelBox - usePrereleaseBox - categoriesBtn baseDirEdit browseBaseDirBtn downloadDirEdit diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index ae924393..07aff4a1 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -54,6 +54,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); + ui->checkForUpdates->setChecked(settings().checkForUpdates()); ui->usePrereleaseBox->setChecked(settings().usePrereleases()); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); @@ -95,6 +96,7 @@ void GeneralSettingsTab::update() settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); + settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } -- cgit v1.3.1 From 01b2a8201dbbb3e5ba8c09246a45cde4f11ed2bc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 25 Sep 2019 18:24:08 -0400 Subject: replaced color buttons by a QTableWidget --- src/settingsdialog.ui | 237 ++++++++++++++++++----------- src/settingsdialoggeneral.cpp | 347 +++++++++++++++++++++++++++--------------- src/settingsdialoggeneral.h | 42 ++--- 3 files changed, 381 insertions(+), 245 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 78bae6d7..1ecf19f9 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -42,46 +42,64 @@ User Interface - - - - - Style - - - - - - - Language - - - - - - - The display language - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Language + + + + + + + The display language + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - - - - - - - graphical style - - - graphical style of the MO user interface - + + + + + + + Style + + + + + + + graphical style + + + graphical style of the MO user interface + + + + - + Dialogs will always be centered on the main window, but will remember their size. @@ -94,7 +112,7 @@ p, li { white-space: pre-wrap; } - + @@ -113,7 +131,7 @@ p, li { white-space: pre-wrap; } - + Modify the categories available to arrange your mods. @@ -126,6 +144,19 @@ p, li { white-space: pre-wrap; } + + + + Qt::Vertical + + + + 0 + 0 + + + + @@ -176,70 +207,90 @@ p, li { white-space: pre-wrap; } Colors - - - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. - - - Show mod list separator colors on the scrollbar + + + + + QAbstractItemView::NoEditTriggers - - true + + QAbstractItemView::SingleSelection - - - - - - Plugin is Contained in selected Mod + + QAbstractItemView::SelectRows - - - - - - Is overwritten (loose files) + + QAbstractItemView::ScrollPerPixel - - - - - - Is overwriting (loose files) - - - - - - - Reset Colors - - - - - - - Mod Contains selected Plugin - - - - - - - Is overwritten (archive files) + + false + + false + + + false + + + true + + + false + - - - - Is overwriting (archive files) - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + + When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + + Show mod list separator colors on the scrollbar + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reset Colors + + + + diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 07aff4a1..be57101e 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -6,64 +6,121 @@ using MOBase::QuestionBoxMemory; -GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) - : SettingsTab(s, d) + +class ColorItem : public QTableWidgetItem { - addLanguages(); +public: + ColorItem( + const QColor& defaultColor, + std::function get, + std::function commit) + : m_default(defaultColor), m_get(get), m_commit(commit) + { + set(get()); + } + + QColor get() const + { + return m_temp; + } + + bool set(const QColor& c) { - QString languageCode = settings().interface().language(); - int currentID = ui->languageBox->findData(languageCode); - // I made a mess. :( Most languages are stored with only the iso country - // code (2 characters like "de") but chinese - // with the exact language variant (zh_TW) so I have to search for both - // variants - if (currentID == -1) { - currentID = ui->languageBox->findData(languageCode.mid(0, 2)); + if (m_temp != c) { + m_temp = c; + return true; } - if (currentID != -1) { - ui->languageBox->setCurrentIndex(currentID); + + return false; + } + + void commit() + { + m_commit(m_temp); + } + + bool reset() + { + return set(m_default); + } + +private: + const QColor m_default; + std::function m_get; + std::function m_commit; + QColor m_temp; +}; + + +class ColorDelegate : public QStyledItemDelegate +{ +public: + ColorDelegate(QTableWidget* table) + : m_table(table) + { + } + +protected: + void paint( + QPainter* p, const QStyleOptionViewItem& option, + const QModelIndex& index) const override + { + if (!paintColor(p, option, index)) { + QStyledItemDelegate::paint(p, option, index); } } - addStyles(); +private: + QTableWidget* m_table; + bool paintColor( + QPainter* p, const QStyleOptionViewItem& option, + const QModelIndex& index) const { - const int currentID = ui->styleBox->findData( - settings().interface().styleName().value_or("")); + if (index.column() != 1) { + return false; + } + + const auto* item = dynamic_cast( + m_table->item(index.row(), index.column())); + + if (!item) { + return false; + } + + p->save(); + p->fillRect(option.rect, item->get()); + p->restore(); + + return true; + } +}; - if (currentID != -1) { - ui->styleBox->setCurrentIndex(currentID); + +template +void forEachColorItem(QTableWidget* table, F&& f) +{ + const auto rowCount = table->rowCount(); + + for (int i=0; i(table->item(i, 1))) { + f(item); } } +} + + +GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) + : SettingsTab(s, d) +{ + addLanguages(); + selectLanguage(); + + addStyles(); + selectStyle(); + + setColorTable(); - //version with stylesheet - setButtonColor(ui->overwritingBtn, settings().colors().modlistOverwritingLoose()); - setButtonColor(ui->overwrittenBtn, settings().colors().modlistOverwrittenLoose()); - setButtonColor(ui->overwritingArchiveBtn, settings().colors().modlistOverwritingArchive()); - setButtonColor(ui->overwrittenArchiveBtn, settings().colors().modlistOverwrittenArchive()); - setButtonColor(ui->containsBtn, settings().colors().modlistContainsPlugin()); - setButtonColor(ui->containedBtn, settings().colors().pluginListContained()); - - setOverwritingColor(settings().colors().modlistOverwritingLoose()); - setOverwrittenColor(settings().colors().modlistOverwrittenLoose()); - setOverwritingArchiveColor(settings().colors().modlistOverwritingArchive()); - setOverwrittenArchiveColor(settings().colors().modlistOverwrittenArchive()); - setContainsColor(settings().colors().modlistContainsPlugin()); - setContainedColor(settings().colors().pluginListContained()); - - ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); - ui->compactBox->setChecked(settings().interface().compactDownloads()); - ui->showMetaBox->setChecked(settings().interface().metaDownloads()); - ui->checkForUpdates->setChecked(settings().checkForUpdates()); - ui->usePrereleaseBox->setChecked(settings().usePrereleases()); - ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); - - QObject::connect(ui->overwritingArchiveBtn, &QPushButton::clicked, [&]{ on_overwritingArchiveBtn_clicked(); }); - QObject::connect(ui->overwritingBtn, &QPushButton::clicked, [&]{ on_overwritingBtn_clicked(); }); - QObject::connect(ui->overwrittenArchiveBtn, &QPushButton::clicked, [&]{ on_overwrittenArchiveBtn_clicked(); }); - QObject::connect(ui->overwrittenBtn, &QPushButton::clicked, [&]{ on_overwrittenBtn_clicked(); }); - QObject::connect(ui->containedBtn, &QPushButton::clicked, [&]{ on_containedBtn_clicked(); }); - QObject::connect(ui->containsBtn, &QPushButton::clicked, [&]{ on_containsBtn_clicked(); }); QObject::connect(ui->categoriesBtn, &QPushButton::clicked, [&]{ on_categoriesBtn_clicked(); }); QObject::connect(ui->resetColorsBtn, &QPushButton::clicked, [&]{ on_resetColorsBtn_clicked(); }); QObject::connect(ui->resetDialogsButton, &QPushButton::clicked, [&]{ on_resetDialogsButton_clicked(); }); @@ -86,18 +143,16 @@ void GeneralSettingsTab::update() emit settings().styleChanged(newStyle); } - settings().colors().setModlistOverwritingLoose(getOverwritingColor()); - settings().colors().setModlistOverwrittenLoose(getOverwrittenColor()); - settings().colors().setModlistOverwritingArchive(getOverwritingArchiveColor()); - settings().colors().setModlistOverwrittenArchive(getOverwrittenArchiveColor()); - settings().colors().setModlistContainsPlugin(getContainsColor()); - settings().colors().setPluginListContained(getContainedColor()); - settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); + + forEachColorItem(ui->colorTable, [](auto* item) { + item->commit(); + }); + settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } @@ -134,6 +189,22 @@ void GeneralSettingsTab::addLanguages() } } +void GeneralSettingsTab::selectLanguage() +{ + QString languageCode = settings().interface().language(); + int currentID = ui->languageBox->findData(languageCode); + // I made a mess. :( Most languages are stored with only the iso country + // code (2 characters like "de") but chinese + // with the exact language variant (zh_TW) so I have to search for both + // variants + if (currentID == -1) { + currentID = ui->languageBox->findData(languageCode.mid(0, 2)); + } + if (currentID != -1) { + ui->languageBox->setCurrentIndex(currentID); + } +} + void GeneralSettingsTab::addStyles() { ui->styleBox->addItem("None", ""); @@ -147,107 +218,136 @@ void GeneralSettingsTab::addStyles() } } -void GeneralSettingsTab::resetDialogs() +void GeneralSettingsTab::selectStyle() { - settings().widgets().resetQuestionButtons(); + const int currentID = ui->styleBox->findData( + settings().interface().styleName().value_or("")); + + if (currentID != -1) { + ui->styleBox->setCurrentIndex(currentID); + } } -void GeneralSettingsTab::setButtonColor(QPushButton *button, const QColor &color) +void GeneralSettingsTab::setColorTable() { - button->setStyleSheet( - QString("QPushButton {" - "background-color: rgba(%1, %2, %3, %4);" - "color: %5;" - "border: 1px solid;" - "padding: 3px;" - "}") - .arg(color.red()) - .arg(color.green()) - .arg(color.blue()) - .arg(color.alpha()) - .arg(ColorSettings::idealTextColor(color).name()) - ); -}; + ui->colorTable->setColumnCount(2); + ui->colorTable->setHorizontalHeaderLabels({ + QObject::tr("Item"), QObject::tr("Color") + }); -void GeneralSettingsTab::on_containsBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_ContainsColor, &dialog(), "Color Picker: Mod contains selected plugin", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_ContainsColor = result; - setButtonColor(ui->containsBtn, result); - } + ui->colorTable->setItemDelegate(new ColorDelegate(ui->colorTable)); + + addColor( + QObject::tr("Is overwritten (loose files)"), + QColor(0, 255, 0, 64), + [this]{ return settings().colors().modlistOverwrittenLoose(); }, + [this](auto&& v){ settings().colors().setModlistOverwrittenLoose(v); }); + + addColor( + QObject::tr("Is overwriting (loose files)"), + QColor(255, 0, 0, 64), + [this]{ return settings().colors().modlistOverwritingLoose(); }, + [this](auto&& v){ settings().colors().setModlistOverwritingLoose(v); }); + + addColor( + QObject::tr("Is overwritten (archives)"), + QColor(0, 255, 255, 64), + [this]{ return settings().colors().modlistOverwrittenArchive(); }, + [this](auto&& v){ settings().colors().setModlistOverwrittenArchive(v); }); + + addColor( + QObject::tr("Is overwriting (archives)"), + QColor(255, 0, 255, 64), + [this]{ return settings().colors().modlistOverwritingArchive(); }, + [this](auto&& v){ settings().colors().setModlistOverwritingArchive(v); }); + + addColor( + QObject::tr("Mod contains selected plugin"), + QColor(0, 0, 255, 64), + [this]{ return settings().colors().modlistContainsPlugin(); }, + [this](auto&& v){ settings().colors().setModlistContainsPlugin(v); }); + + addColor( + QObject::tr("Plugin is contained in selected mod"), + QColor(0, 0, 255, 64), + [this]{ return settings().colors().pluginListContained(); }, + [this](auto&& v){ settings().colors().setPluginListContained(v); }); + + QObject::connect( + ui->colorTable, &QTableWidget::cellActivated, + [&]{ onColorActivated(); }); } -void GeneralSettingsTab::on_containedBtn_clicked() +void GeneralSettingsTab::addColor( + const QString& text, const QColor& defaultColor, + std::function get, + std::function commit) { - QColor result = QColorDialog::getColor(m_ContainedColor, &dialog(), "ColorPicker: Plugin is Contained in selected Mod", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_ContainedColor = result; - setButtonColor(ui->containedBtn, result); - } + const auto r = ui->colorTable->rowCount(); + ui->colorTable->setRowCount(r + 1); + + ui->colorTable->setItem(r, 0, new QTableWidgetItem(text)); + ui->colorTable->setItem(r, 1, new ColorItem(defaultColor, get, commit)); + + ui->colorTable->resizeColumnsToContents(); } -void GeneralSettingsTab::on_overwrittenBtn_clicked() +void GeneralSettingsTab::resetDialogs() { - QColor result = QColorDialog::getColor(m_OverwrittenColor, &dialog(), "ColorPicker: Is overwritten (loose files)", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_OverwrittenColor = result; - setButtonColor(ui->overwrittenBtn, result); - } + settings().widgets().resetQuestionButtons(); } -void GeneralSettingsTab::on_overwritingBtn_clicked() +void GeneralSettingsTab::onColorActivated() { - QColor result = QColorDialog::getColor(m_OverwritingColor, &dialog(), "ColorPicker: Is overwriting (loose files)", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_OverwritingColor = result; - setButtonColor(ui->overwritingBtn, result); + const auto rows = ui->colorTable->selectionModel()->selectedRows(); + if (rows.isEmpty()) { + return; } -} -void GeneralSettingsTab::on_overwrittenArchiveBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_OverwrittenArchiveColor, &dialog(), "ColorPicker: Is overwritten (archive files)", QColorDialog::ShowAlphaChannel); - if (result.isValid()) { - m_OverwrittenArchiveColor = result; - setButtonColor(ui->overwrittenArchiveBtn, result); + const auto row = rows[0].row(); + + const auto text = ui->colorTable->item(row, 0)->text(); + auto* item = dynamic_cast(ui->colorTable->item(row, 1)); + + if (!item) { + return; } -} -void GeneralSettingsTab::on_overwritingArchiveBtn_clicked() -{ - QColor result = QColorDialog::getColor(m_OverwritingArchiveColor, &dialog(), "ColorPicker: Is overwriting (archive files)", QColorDialog::ShowAlphaChannel); + const QColor result = QColorDialog::getColor( + item->get(), &dialog(), text, QColorDialog::ShowAlphaChannel); + if (result.isValid()) { - m_OverwritingArchiveColor = result; - setButtonColor(ui->overwritingArchiveBtn, result); + item->set(result); + ui->colorTable->update(ui->colorTable->model()->index(row, 1)); } } void GeneralSettingsTab::on_resetColorsBtn_clicked() { - m_OverwritingColor = QColor(255, 0, 0, 64); - m_OverwrittenColor = QColor(0, 255, 0, 64); - m_OverwritingArchiveColor = QColor(255, 0, 255, 64); - m_OverwrittenArchiveColor = QColor(0, 255, 255, 64); - m_ContainsColor = QColor(0, 0, 255, 64); - m_ContainedColor = QColor(0, 0, 255, 64); - - setButtonColor(ui->overwritingBtn, m_OverwritingColor); - setButtonColor(ui->overwrittenBtn, m_OverwrittenColor); - setButtonColor(ui->overwritingArchiveBtn, m_OverwritingArchiveColor); - setButtonColor(ui->overwrittenArchiveBtn, m_OverwrittenArchiveColor); - setButtonColor(ui->containsBtn, m_ContainsColor); - setButtonColor(ui->containedBtn, m_ContainedColor); + bool changed = false; + + forEachColorItem(ui->colorTable, [&](auto* item) { + if (item->reset()) { + changed = true; + } + }); + + if (changed) { + ui->colorTable->update(); + } } void GeneralSettingsTab::on_resetDialogsButton_clicked() { - if (QMessageBox::question( - parentWidget(), QObject::tr("Confirm?"), + const auto r = QMessageBox::question( + parentWidget(), + QObject::tr("Confirm?"), QObject::tr( "This will reset all the choices you made to dialogs and make them all " "visible again. Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QMessageBox::Yes | QMessageBox::No); + + if (r == QMessageBox::Yes) { resetDialogs(); } } @@ -255,6 +355,7 @@ void GeneralSettingsTab::on_resetDialogsButton_clicked() void GeneralSettingsTab::on_categoriesBtn_clicked() { CategoriesDialog dialog(&dialog()); + if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); } diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 2038ba31..1f7fafff 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -12,38 +12,22 @@ public: void update(); private: - QColor m_OverwritingColor; - QColor m_OverwrittenColor; - QColor m_OverwritingArchiveColor; - QColor m_OverwrittenArchiveColor; - QColor m_ContainsColor; - QColor m_ContainedColor; - void addLanguages(); + void selectLanguage(); + void addStyles(); + void selectStyle(); + + void setColorTable(); + void resetDialogs(); - void setButtonColor(QPushButton *button, const QColor &color); - - QColor getOverwritingColor() { return m_OverwritingColor; } - QColor getOverwrittenColor() { return m_OverwrittenColor; } - QColor getOverwritingArchiveColor() { return m_OverwritingArchiveColor; } - QColor getOverwrittenArchiveColor() { return m_OverwrittenArchiveColor; } - QColor getContainsColor() { return m_ContainsColor; } - QColor getContainedColor() { return m_ContainedColor; } - - void setOverwritingColor(QColor col) { m_OverwritingColor = col; } - void setOverwrittenColor(QColor col) { m_OverwrittenColor = col; } - void setOverwritingArchiveColor(QColor col) { m_OverwritingArchiveColor = col; } - void setOverwrittenArchiveColor(QColor col) { m_OverwrittenArchiveColor = col; } - void setContainsColor(QColor col) { m_ContainsColor = col; } - void setContainedColor(QColor col) { m_ContainedColor = col; } - - void on_overwritingArchiveBtn_clicked(); - void on_overwritingBtn_clicked(); - void on_overwrittenArchiveBtn_clicked(); - void on_overwrittenBtn_clicked(); - void on_containedBtn_clicked(); - void on_containsBtn_clicked(); + + void addColor( + const QString& text, const QColor& defaultColor, + std::function get, + std::function commit); + + void onColorActivated(); void on_categoriesBtn_clicked(); void on_resetColorsBtn_clicked(); -- cgit v1.3.1 From 8237a1c47fa50ac9dd0f4a3fef26c0c40175fb9e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 25 Sep 2019 18:39:37 -0400 Subject: use QStyleFactory to get default styles instead of hardcoding them don't display extensions in style combobox --- src/moapplication.cpp | 4 ++-- src/settingsdialoggeneral.cpp | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 79e931fb..a071d58b 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -132,8 +132,8 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) void MOApplication::updateStyle(const QString &fileName) { - if (fileName == "Fusion") { - setStyle(QStyleFactory::create("fusion")); + if (QStyleFactory::keys().contains(fileName)) { + setStyle(QStyleFactory::create(fileName)); setStyleSheet(""); } else { setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle))); diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index be57101e..754d10cb 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -208,13 +208,24 @@ void GeneralSettingsTab::selectLanguage() void GeneralSettingsTab::addStyles() { ui->styleBox->addItem("None", ""); - ui->styleBox->addItem("Fusion", "Fusion"); + for (auto&& key : QStyleFactory::keys()) { + ui->styleBox->addItem(key, key); + } - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + QString::fromStdWString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); - while (langIter.hasNext()) { - langIter.next(); - QString style = langIter.fileName(); - ui->styleBox->addItem(style, style); + ui->styleBox->insertSeparator(ui->styleBox->count()); + + QDirIterator iter( + QCoreApplication::applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::stylesheetsPath()), + QStringList("*.qss"), + QDir::Files); + + while (iter.hasNext()) { + iter.next(); + + ui->styleBox->addItem( + iter.fileInfo().completeBaseName(), + iter.fileName()); } } -- cgit v1.3.1 From 5d74bd3789515a3e04e54267f1cdfe8f5397f6df Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 25 Sep 2019 19:32:53 -0400 Subject: refactored addLanguages() a bit changed some of the tooltips --- src/settingsdialog.ui | 42 ++++++++++++++++++------------- src/settingsdialoggeneral.cpp | 57 ++++++++++++++++++++++++++++--------------- 2 files changed, 63 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 1ecf19f9..704e4134 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -68,14 +68,10 @@ - The display language + The language of the user interface. - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + The language of the user interface. @@ -89,10 +85,10 @@ p, li { white-space: pre-wrap; } - graphical style + Visual theme of the user interface. - graphical style of the MO user interface + Visual theme of the user interface. @@ -121,10 +117,10 @@ p, li { white-space: pre-wrap; } - Reset stored information from dialogs. + Reset all choices made in dialogs. - This will make all dialogs show up again where you checked the "Remember selection"-box. + Reset all choices made in dialogs. Reset Dialog Choices @@ -169,7 +165,10 @@ p, li { white-space: pre-wrap; } - If checked, the download list will display meta information instead of file names. + Show meta information instead of file names in the download list. + + + Show meta information instead of file names in the download list. Show Meta Information @@ -179,7 +178,10 @@ p, li { white-space: pre-wrap; } - If checked, the download interface will be more compact. + Make the download list more compact. + + + Make the download list more compact. Compact List @@ -257,10 +259,10 @@ p, li { white-space: pre-wrap; } - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + Colors set on separators will also be shown in the mod list scrollbar at the location of the separator. This can be useful for quickly navigating to a specific separator. - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + Colors set on separators will also be shown in the mod list scrollbar at the location of the separator. This can be useful for quickly navigating to a specific separator. Show mod list separator colors on the scrollbar @@ -285,6 +287,12 @@ p, li { white-space: pre-wrap; } + + Reset all colors to their default value. + + + Reset all colors to their default value. + Reset Colors @@ -305,10 +313,10 @@ p, li { white-space: pre-wrap; } - Mod Organizer checks for updates on Github on startup. + Check for Mod Organizer updates on Github on startup. - Mod Organizer checks for updates on Github on startup. + Check for Mod Organizer updates on Github on startup. Check for updates @@ -321,7 +329,7 @@ p, li { white-space: pre-wrap; } Update to non-stable releases. - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. + Update to non-stable releases. Install Pre-releases (Betas) diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 754d10cb..0dfd3a08 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -158,33 +158,52 @@ void GeneralSettingsTab::update() void GeneralSettingsTab::addLanguages() { + // matches the end of filenames for something like "_en.qm" or "_zh_CN.qm" + const QString pattern = + QString::fromStdWString(AppConfig::translationPrefix()) + + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + + const QRegExp exp(pattern); + + QDirIterator iter( + QCoreApplication::applicationDirPath() + "/translations", + QDir::Files); + std::vector> languages; - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); - QString pattern = QString::fromStdWString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; - QRegExp exp(pattern); - while (langIter.hasNext()) { - langIter.next(); - QString file = langIter.fileName(); - if (exp.exactMatch(file)) { - QString languageCode = exp.cap(1); - QLocale locale(languageCode); - QString languageString = QString("%1 (%2)").arg(locale.nativeLanguageName()).arg(locale.nativeCountryName()); //QLocale::languageToString(locale.language()); - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (traditional)"; - } else { - languageString = "Chinese (simplified)"; - } + while (iter.hasNext()) { + iter.next(); + + const QString file = iter.fileName(); + if (!exp.exactMatch(file)) { + continue; + } + + const QString languageCode = exp.cap(1); + const QLocale locale(languageCode); + + QString languageString = QString("%1 (%2)") + .arg(locale.nativeLanguageName()) + .arg(locale.nativeCountryName()); + + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (Traditional)"; + } else { + languageString = "Chinese (Simplified)"; } - languages.push_back(std::make_pair(QString("%1").arg(languageString), exp.cap(1))); } + + languages.push_back({languageString, exp.cap(1)}); } + if (!ui->languageBox->findText("English")) { - languages.push_back(std::make_pair(QString("English"), QString("en_US"))); + languages.push_back({QString("English"), QString("en_US")}); } + std::sort(languages.begin(), languages.end()); - for (const auto &lang : languages) { + + for (const auto& lang : languages) { ui->languageBox->addItem(lang.first, lang.second); } } -- cgit v1.3.1 From 51d664d76ce6b611e7a7585b209bad9d68fe65d7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 27 Sep 2019 15:20:02 -0400 Subject: moved color stuff to ColorTable, now shows sample text and icons --- src/CMakeLists.txt | 3 + src/colortable.cpp | 278 ++++++++++++++++++++++++++++++++++++++++++ src/colortable.h | 28 +++++ src/icondelegate.cpp | 15 ++- src/icondelegate.h | 18 +-- src/modflagicondelegate.cpp | 114 +++++++++-------- src/modflagicondelegate.h | 11 +- src/settingsdialog.ui | 9 +- src/settingsdialoggeneral.cpp | 235 +++-------------------------------- src/settingsdialoggeneral.h | 9 -- 10 files changed, 421 insertions(+), 299 deletions(-) create mode 100644 src/colortable.cpp create mode 100644 src/colortable.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c29ff48..b21d1a8b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,6 +141,7 @@ SET(organizer_SRCS envsecurity.cpp envshortcut.cpp envwindows.cpp + colortable.cpp shared/windows_error.cpp shared/error_report.cpp @@ -263,6 +264,7 @@ SET(organizer_HDRS envsecurity.h envshortcut.h envwindows.h + colortable.h shared/windows_error.h shared/error_report.h @@ -473,6 +475,7 @@ set(utilities ) set(widgets + colortable genericicondelegate filerenamer filterwidget diff --git a/src/colortable.cpp b/src/colortable.cpp new file mode 100644 index 00000000..7546abe5 --- /dev/null +++ b/src/colortable.cpp @@ -0,0 +1,278 @@ +#include "colortable.h" +#include "modflagicondelegate.h" +#include "settings.h" + +class ColorItem; +ColorItem* colorItemForRow(QTableWidget* table, int row); + +void paintBackground( + QTableWidget* table, QPainter* p, const QStyleOptionViewItem& option, + const QModelIndex& index); + + +class ColoredBackgroundDelegate : public QStyledItemDelegate +{ +public: + ColoredBackgroundDelegate(QTableWidget* table) + : m_table(table) + { + } + + void paint( + QPainter* p, const QStyleOptionViewItem& option, + const QModelIndex& index) const override + { + paintBackground(m_table, p, option, index); + + QStyleOptionViewItem itemOption(option); + initStyleOption(&itemOption, index); + itemOption.state = QStyle::State_Enabled; + + QStyledItemDelegate::paint(p, itemOption, index); + } + +private: + QTableWidget* m_table; +}; + + +class FakeModFlagIconDelegate : public ModFlagIconDelegate +{ +public: + explicit FakeModFlagIconDelegate(QTableWidget* table) + : m_table(table) + { + } + + void paint( + QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + paintBackground(m_table, painter, option, index); + ModFlagIconDelegate::paintIcons(painter, option, index, getIcons(index)); + } + +protected: + QList getIcons(const QModelIndex &index) const override + { + const auto flags = { + ModInfo::FLAG_CONFLICT_MIXED, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, + ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED, + ModInfo::FLAG_BACKUP, + ModInfo::FLAG_NOTENDORSED, + ModInfo::FLAG_NOTES, + ModInfo::FLAG_ALTERNATE_GAME + }; + + return getIconsForFlags(flags, false); + } + + size_t getNumIcons(const QModelIndex &index) const override + { + return getIcons(index).size(); + } + +private: + QTableWidget* m_table; +}; + + +class ColorItem : public QTableWidgetItem +{ +public: + ColorItem( + QString caption, QColor defaultColor, + std::function get, + std::function commit) : + m_caption(std::move(caption)), m_default(defaultColor), + m_get(get), m_commit(commit) + { + setText(m_caption); + set(get()); + } + + const QString& caption() const + { + return m_caption; + } + + QColor get() const + { + return m_temp; + } + + bool set(const QColor& c) + { + if (m_temp != c) { + m_temp = c; + return true; + } + + return false; + } + + void commit() + { + m_commit(m_temp); + } + + bool reset() + { + return set(m_default); + } + +private: + const QString m_caption; + const QColor m_default; + std::function m_get; + std::function m_commit; + QColor m_temp; +}; + + +ColorItem* colorItemForRow(QTableWidget* table, int row) +{ + return dynamic_cast(table->item(row, 0)); +} + +template +void forEachColorItem(QTableWidget* table, F&& f) +{ + const auto rowCount = table->rowCount(); + + for (int i=0; isave(); + p->fillRect(option.rect, ci->get()); + p->restore(); + } +} + + +ColorTable::ColorTable(QWidget* parent) + : QTableWidget(parent), m_settings(nullptr) +{ + setColumnCount(3); + setHorizontalHeaderLabels({"", "", ""}); + + setItemDelegateForColumn(1, new ColoredBackgroundDelegate(this)); + setItemDelegateForColumn(2, new FakeModFlagIconDelegate(this)); + + connect( + this, &QTableWidget::cellActivated, + [&]{ onColorActivated(); }); +} + +void ColorTable::load(Settings& s) +{ + m_settings = &s; + + addColor( + QObject::tr("Is overwritten (loose files)"), + QColor(0, 255, 0, 64), + [this]{ return m_settings->colors().modlistOverwrittenLoose(); }, + [this](auto&& v){ m_settings->colors().setModlistOverwrittenLoose(v); }); + + addColor( + QObject::tr("Is overwriting (loose files)"), + QColor(255, 0, 0, 64), + [this]{ return m_settings->colors().modlistOverwritingLoose(); }, + [this](auto&& v){ m_settings->colors().setModlistOverwritingLoose(v); }); + + addColor( + QObject::tr("Is overwritten (archives)"), + QColor(0, 255, 255, 64), + [this]{ return m_settings->colors().modlistOverwrittenArchive(); }, + [this](auto&& v){ m_settings->colors().setModlistOverwrittenArchive(v); }); + + addColor( + QObject::tr("Is overwriting (archives)"), + QColor(255, 0, 255, 64), + [this]{ return m_settings->colors().modlistOverwritingArchive(); }, + [this](auto&& v){ m_settings->colors().setModlistOverwritingArchive(v); }); + + addColor( + QObject::tr("Mod contains selected plugin"), + QColor(0, 0, 255, 64), + [this]{ return m_settings->colors().modlistContainsPlugin(); }, + [this](auto&& v){ m_settings->colors().setModlistContainsPlugin(v); }); + + addColor( + QObject::tr("Plugin is contained in selected mod"), + QColor(0, 0, 255, 64), + [this]{ return m_settings->colors().pluginListContained(); }, + [this](auto&& v){ m_settings->colors().setPluginListContained(v); }); +} + +void ColorTable::resetColors() +{ + bool changed = false; + + forEachColorItem(this, [&](auto* item) { + if (item->reset()) { + changed = true; + } + }); + + if (changed) { + update(); + } +} + +void ColorTable::commitColors() +{ + forEachColorItem(this, [](auto* item) { + item->commit(); + }); +} + +void ColorTable::addColor( + const QString& text, const QColor& defaultColor, + std::function get, + std::function commit) +{ + const auto r = rowCount(); + setRowCount(r + 1); + + auto* item = new ColorItem(text, defaultColor, get, commit); + + setItem(r, 0, item); + setItem(r, 1, new QTableWidgetItem("Text")); + setItem(r, 2, new QTableWidgetItem); + + resizeColumnsToContents(); +} + +void ColorTable::onColorActivated() +{ + const auto rows = selectionModel()->selectedRows(); + if (rows.isEmpty()) { + return; + } + + const auto row = rows[0].row(); + auto* ci = colorItemForRow(this, row); + if (!ci) { + return; + } + + const QColor result = QColorDialog::getColor( + ci->get(), topLevelWidget(), ci->caption(), QColorDialog::ShowAlphaChannel); + + if (result.isValid()) { + ci->set(result); + update(model()->index(row, 1)); + } +} diff --git a/src/colortable.h b/src/colortable.h new file mode 100644 index 00000000..c2b64a4d --- /dev/null +++ b/src/colortable.h @@ -0,0 +1,28 @@ +#ifndef COLORTABLE_H +#define COLORTABLE_H + +#include + +class Settings; + +class ColorTable : public QTableWidget +{ +public: + ColorTable(QWidget* parent=nullptr); + + void load(Settings& s); + void resetColors(); + void commitColors(); + +private: + Settings* m_settings; + + void addColor( + const QString& text, const QColor& defaultColor, + std::function get, + std::function commit); + + void onColorActivated(); +}; + +#endif // COLORTABLE_H diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 39038f3c..03964263 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -32,13 +32,10 @@ IconDelegate::IconDelegate(QObject *parent) { } - -void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +void IconDelegate::paintIcons( + QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index, const QList& icons) { - QStyledItemDelegate::paint(painter, option, index); - - QList icons = getIcons(index); - int x = 4; painter->save(); @@ -67,3 +64,9 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, painter->restore(); } +void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + paintIcons(painter, option, index, getIcons(index)); +} + diff --git a/src/icondelegate.h b/src/icondelegate.h index 39694481..bac71a62 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -27,25 +27,19 @@ along with Mod Organizer. If not, see . class IconDelegate : public QStyledItemDelegate { - Q_OBJECT -public: + Q_OBJECT; +public: explicit IconDelegate(QObject *parent = 0); - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; -signals: - -public slots: - -private: + static void paintIcons( + QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index, const QList& icons); +protected: virtual QList getIcons(const QModelIndex &index) const = 0; virtual size_t getNumIcons(const QModelIndex &index) const = 0; - - -private: - }; #endif // ICONDELEGATE_H diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index 7110a590..a5e9aa22 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -31,72 +31,82 @@ void ModFlagIconDelegate::columnResized(int logicalIndex, int, int newSize) } } -QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const { +QList ModFlagIconDelegate::getIconsForFlags( + std::vector flags, bool compact) +{ QList result; - QVariant modid = index.data(Qt::UserRole + 1); - if (modid.isValid()) { - ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); - std::vector flags = info->getFlags(); - // Don't do flags for overwrite - if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE) != flags.end()) - return result; - - // insert conflict icons to provide nicer alignment - { // insert loose file conflicts first - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ConflictFlags, m_ConflictFlags + 4); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!m_Compact) { - result.append(QString()); - } + // Don't do flags for overwrite + if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE) != flags.end()) + return result; + + // insert conflict icons to provide nicer alignment + { // insert loose file conflicts first + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ConflictFlags, m_ConflictFlags + 4); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); } + } - { // insert loose vs archive overwrite second - auto iter = std::find(flags.begin(), flags.end(), - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!m_Compact) { - result.append(QString()); - } + { // insert loose vs archive overwrite second + auto iter = std::find(flags.begin(), flags.end(), + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); } + } - { // insert loose vs archive overwritten third - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!m_Compact) { - result.append(QString()); - } + { // insert loose vs archive overwritten third + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); } + } - { // insert archive conflicts last - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!m_Compact) { - result.append(QString()); - } + { // insert archive conflicts last + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); } + } - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - auto iconPath = getFlagIcon(*iter); - if (!iconPath.isEmpty()) - result.append(iconPath); - } + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + auto iconPath = getFlagIcon(*iter); + if (!iconPath.isEmpty()) + result.append(iconPath); } + return result; } -QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const +QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const +{ + QVariant modid = index.data(Qt::UserRole + 1); + + if (modid.isValid()) { + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); + return getIconsForFlags(info->getFlags(), m_Compact); + } + + return {}; +} + +QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) { switch (flag) { case ModInfo::FLAG_BACKUP: return QStringLiteral(":/MO/gui/emblem_backup"); diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index eb6a76ab..4f22dd90 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -5,21 +5,24 @@ class ModFlagIconDelegate : public IconDelegate { -Q_OBJECT + Q_OBJECT; public: explicit ModFlagIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 120); virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + static QList getIconsForFlags( + std::vector flags, bool compact); + + static QString getFlagIcon(ModInfo::EFlag flag); + public slots: void columnResized(int logicalIndex, int oldSize, int newSize); -private: +protected: virtual QList getIcons(const QModelIndex &index) const; virtual size_t getNumIcons(const QModelIndex &index) const; - QString getFlagIcon(ModInfo::EFlag flag) const; - private: static ModInfo::EFlag m_ConflictFlags[4]; static ModInfo::EFlag m_ArchiveLooseConflictFlags[2]; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 704e4134..fba65545 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -211,7 +211,7 @@ - + QAbstractItemView::NoEditTriggers @@ -1527,6 +1527,13 @@ programs you are intentionally running. + + + ColorTable + QTableWidget +
colortable.h
+
+
logLevelBox baseDirEdit diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index 0dfd3a08..a9ec5cae 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -2,114 +2,9 @@ #include "ui_settingsdialog.h" #include "appconfig.h" #include "categoriesdialog.h" +#include "colortable.h" #include -using MOBase::QuestionBoxMemory; - - -class ColorItem : public QTableWidgetItem -{ -public: - ColorItem( - const QColor& defaultColor, - std::function get, - std::function commit) - : m_default(defaultColor), m_get(get), m_commit(commit) - { - set(get()); - } - - QColor get() const - { - return m_temp; - } - - bool set(const QColor& c) - { - if (m_temp != c) { - m_temp = c; - return true; - } - - return false; - } - - void commit() - { - m_commit(m_temp); - } - - bool reset() - { - return set(m_default); - } - -private: - const QColor m_default; - std::function m_get; - std::function m_commit; - QColor m_temp; -}; - - -class ColorDelegate : public QStyledItemDelegate -{ -public: - ColorDelegate(QTableWidget* table) - : m_table(table) - { - } - -protected: - void paint( - QPainter* p, const QStyleOptionViewItem& option, - const QModelIndex& index) const override - { - if (!paintColor(p, option, index)) { - QStyledItemDelegate::paint(p, option, index); - } - } - -private: - QTableWidget* m_table; - - bool paintColor( - QPainter* p, const QStyleOptionViewItem& option, - const QModelIndex& index) const - { - if (index.column() != 1) { - return false; - } - - const auto* item = dynamic_cast( - m_table->item(index.row(), index.column())); - - if (!item) { - return false; - } - - p->save(); - p->fillRect(option.rect, item->get()); - p->restore(); - - return true; - } -}; - - -template -void forEachColorItem(QTableWidget* table, F&& f) -{ - const auto rowCount = table->rowCount(); - - for (int i=0; i(table->item(i, 1))) { - f(item); - } - } -} - - GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { @@ -119,17 +14,26 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) addStyles(); selectStyle(); - setColorTable(); + ui->colorTable->load(s); - QObject::connect(ui->categoriesBtn, &QPushButton::clicked, [&]{ on_categoriesBtn_clicked(); }); - QObject::connect(ui->resetColorsBtn, &QPushButton::clicked, [&]{ on_resetColorsBtn_clicked(); }); - QObject::connect(ui->resetDialogsButton, &QPushButton::clicked, [&]{ on_resetDialogsButton_clicked(); }); + QObject::connect( + ui->categoriesBtn, &QPushButton::clicked, + [&]{ on_categoriesBtn_clicked(); }); + + QObject::connect( + ui->resetColorsBtn, &QPushButton::clicked, + [&]{ on_resetColorsBtn_clicked(); }); + + QObject::connect( + ui->resetDialogsButton, &QPushButton::clicked, + [&]{ on_resetDialogsButton_clicked(); }); } void GeneralSettingsTab::update() { const QString oldLanguage = settings().interface().language(); - const QString newLanguage = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString(); + const QString newLanguage = ui->languageBox->itemData( + ui->languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { settings().interface().setLanguage(newLanguage); @@ -137,7 +41,9 @@ void GeneralSettingsTab::update() } const QString oldStyle = settings().interface().styleName().value_or(""); - const QString newStyle = ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); + const QString newStyle = ui->styleBox->itemData( + ui->styleBox->currentIndex()).toString(); + if (oldStyle != newStyle) { settings().interface().setStyleName(newStyle); emit settings().styleChanged(newStyle); @@ -149,9 +55,7 @@ void GeneralSettingsTab::update() settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); - forEachColorItem(ui->colorTable, [](auto* item) { - item->commit(); - }); + ui->colorTable->commitColors(); settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } @@ -258,113 +162,14 @@ void GeneralSettingsTab::selectStyle() } } -void GeneralSettingsTab::setColorTable() -{ - ui->colorTable->setColumnCount(2); - ui->colorTable->setHorizontalHeaderLabels({ - QObject::tr("Item"), QObject::tr("Color") - }); - - ui->colorTable->setItemDelegate(new ColorDelegate(ui->colorTable)); - - addColor( - QObject::tr("Is overwritten (loose files)"), - QColor(0, 255, 0, 64), - [this]{ return settings().colors().modlistOverwrittenLoose(); }, - [this](auto&& v){ settings().colors().setModlistOverwrittenLoose(v); }); - - addColor( - QObject::tr("Is overwriting (loose files)"), - QColor(255, 0, 0, 64), - [this]{ return settings().colors().modlistOverwritingLoose(); }, - [this](auto&& v){ settings().colors().setModlistOverwritingLoose(v); }); - - addColor( - QObject::tr("Is overwritten (archives)"), - QColor(0, 255, 255, 64), - [this]{ return settings().colors().modlistOverwrittenArchive(); }, - [this](auto&& v){ settings().colors().setModlistOverwrittenArchive(v); }); - - addColor( - QObject::tr("Is overwriting (archives)"), - QColor(255, 0, 255, 64), - [this]{ return settings().colors().modlistOverwritingArchive(); }, - [this](auto&& v){ settings().colors().setModlistOverwritingArchive(v); }); - - addColor( - QObject::tr("Mod contains selected plugin"), - QColor(0, 0, 255, 64), - [this]{ return settings().colors().modlistContainsPlugin(); }, - [this](auto&& v){ settings().colors().setModlistContainsPlugin(v); }); - - addColor( - QObject::tr("Plugin is contained in selected mod"), - QColor(0, 0, 255, 64), - [this]{ return settings().colors().pluginListContained(); }, - [this](auto&& v){ settings().colors().setPluginListContained(v); }); - - QObject::connect( - ui->colorTable, &QTableWidget::cellActivated, - [&]{ onColorActivated(); }); -} - -void GeneralSettingsTab::addColor( - const QString& text, const QColor& defaultColor, - std::function get, - std::function commit) -{ - const auto r = ui->colorTable->rowCount(); - ui->colorTable->setRowCount(r + 1); - - ui->colorTable->setItem(r, 0, new QTableWidgetItem(text)); - ui->colorTable->setItem(r, 1, new ColorItem(defaultColor, get, commit)); - - ui->colorTable->resizeColumnsToContents(); -} - void GeneralSettingsTab::resetDialogs() { settings().widgets().resetQuestionButtons(); } -void GeneralSettingsTab::onColorActivated() -{ - const auto rows = ui->colorTable->selectionModel()->selectedRows(); - if (rows.isEmpty()) { - return; - } - - const auto row = rows[0].row(); - - const auto text = ui->colorTable->item(row, 0)->text(); - auto* item = dynamic_cast(ui->colorTable->item(row, 1)); - - if (!item) { - return; - } - - const QColor result = QColorDialog::getColor( - item->get(), &dialog(), text, QColorDialog::ShowAlphaChannel); - - if (result.isValid()) { - item->set(result); - ui->colorTable->update(ui->colorTable->model()->index(row, 1)); - } -} - void GeneralSettingsTab::on_resetColorsBtn_clicked() { - bool changed = false; - - forEachColorItem(ui->colorTable, [&](auto* item) { - if (item->reset()) { - changed = true; - } - }); - - if (changed) { - ui->colorTable->update(); - } + ui->colorTable->resetColors(); } void GeneralSettingsTab::on_resetDialogsButton_clicked() diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 1f7fafff..706ba9ef 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -18,17 +18,8 @@ private: void addStyles(); void selectStyle(); - void setColorTable(); - void resetDialogs(); - void addColor( - const QString& text, const QColor& defaultColor, - std::function get, - std::function commit); - - void onColorActivated(); - void on_categoriesBtn_clicked(); void on_resetColorsBtn_clicked(); void on_resetDialogsButton_clicked(); -- cgit v1.3.1 From ec48a6d79665915b07f11f93a834fbec7fc09c45 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 27 Sep 2019 15:26:00 -0400 Subject: a few comments for ColorTable --- src/colortable.cpp | 31 ++++++++++++++++++++++++------- src/colortable.h | 11 +++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/colortable.cpp b/src/colortable.cpp index 7546abe5..61c5ee5f 100644 --- a/src/colortable.cpp +++ b/src/colortable.cpp @@ -10,6 +10,8 @@ void paintBackground( const QModelIndex& index); +// delegate for the sample text column; paints the background color +// class ColoredBackgroundDelegate : public QStyledItemDelegate { public: @@ -26,6 +28,9 @@ public: QStyleOptionViewItem itemOption(option); initStyleOption(&itemOption, index); + + // paint the default stuff like text, but override the state to avoid + // destroying the background for selected items, etc. itemOption.state = QStyle::State_Enabled; QStyledItemDelegate::paint(p, itemOption, index); @@ -36,6 +41,8 @@ private: }; +// delegate for the icons column; paints the background and icons +// class FakeModFlagIconDelegate : public ModFlagIconDelegate { public: @@ -79,6 +86,8 @@ private: }; +// item used in the first column of the table +// class ColorItem : public QTableWidgetItem { public: @@ -93,16 +102,22 @@ public: set(get()); } + // color caption + // const QString& caption() const { return m_caption; } + // the current color + // QColor get() const { return m_temp; } + // sets the current color, commit() must be called to save it + // bool set(const QColor& c) { if (m_temp != c) { @@ -113,14 +128,18 @@ public: return false; } - void commit() + // resets the current color, commit() must be called to save it + // + bool reset() { - m_commit(m_temp); + return set(m_default); } - bool reset() + // saves the current color + // + void commit() { - return set(m_default); + m_commit(m_temp); } private: @@ -246,9 +265,7 @@ void ColorTable::addColor( const auto r = rowCount(); setRowCount(r + 1); - auto* item = new ColorItem(text, defaultColor, get, commit); - - setItem(r, 0, item); + setItem(r, 0, new ColorItem(text, defaultColor, get, commit)); setItem(r, 1, new QTableWidgetItem("Text")); setItem(r, 2, new QTableWidgetItem); diff --git a/src/colortable.h b/src/colortable.h index c2b64a4d..039b6024 100644 --- a/src/colortable.h +++ b/src/colortable.h @@ -5,13 +5,24 @@ class Settings; +// a QTableWidget to view and modify color settings +// class ColorTable : public QTableWidget { public: ColorTable(QWidget* parent=nullptr); + // adds colors to the table from the settings + // void load(Settings& s); + + // resets the colors to their default values; commitColors() must be called + // to save them + // void resetColors(); + + // commits any changes + // void commitColors(); private: -- cgit v1.3.1 From 4fe267b6b46bdd97cd0b221ff9d91d1618e537e9 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 28 Sep 2019 08:11:29 -0500 Subject: Fix md5 hashing for large download files Previously, the md5 hashing was broken for download files larger than about 2GB. This was due to the maximum size of a QByteArray. The hashing was broken up into 10MB chunks to correct this issue. Additionally, a progress bar was added as hashing large files would lock up the GUI. --- src/downloadmanager.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 56238ef3..35f60d7a 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -985,16 +985,39 @@ void DownloadManager::queryInfoMd5(int index) downloadFile.setFileName(m_OrganizerCore->downloadsPath() + "\\" + info->m_FileName); } if (!downloadFile.exists()) { - log::debug("Can't find download file {}", info->m_FileName); + log::error("Can't find download file '{}'", info->m_FileName); return; } if (!downloadFile.open(QIODevice::ReadOnly)) { - log::debug("Can't open download file {}", info->m_FileName); + log::error("Can't open download file '{}'", info->m_FileName); return; } - info->m_Hash = QCryptographicHash::hash(downloadFile.readAll(), QCryptographicHash::Md5); + + QCryptographicHash hash(QCryptographicHash::Md5); + const qint64 progressStep = 10 * 1024 * 1024; + QProgressDialog progress(tr("Hashing download file '%1'").arg(info->m_FileName), + tr("Cancel"), + 0, + downloadFile.size() / progressStep); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(1000); + + for (qint64 i = 0; i < downloadFile.size(); i += progressStep) { + progress.setValue(progress.value()+1); + if (progress.wasCanceled()) { + break; + } + hash.addData(downloadFile.read(progressStep)); + } + if (progress.wasCanceled()) { + downloadFile.close(); + return; + } + + progress.close(); downloadFile.close(); + info->m_Hash = hash.result(); info->m_ReQueried = true; setState(info, STATE_FETCHINGMODINFO_MD5); } -- cgit v1.3.1 From 8e3af100f9bec17be43a91266d0206b8f5036cf4 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 28 Sep 2019 17:01:42 -0500 Subject: Fix help text for optional ESP deactivate button --- src/modinfodialog.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 5b559992..ba2db799 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -384,7 +384,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e Move a file to the data directory.
- This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of MO. @@ -404,10 +404,10 @@ Most mods do not have optional esps, so chances are good you are looking at an e - Make the selected mod in the lower list unavailable. + Make the selected mod in the right list unavailable. - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + The selected esp (in the right list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. -- cgit v1.3.1 From b389161077675752b6f551e7a6563c4eb0e0ca80 Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 2 Oct 2019 23:06:35 +0200 Subject: Made categories section (filters) resizable via a splitter. Correctly handled hiding and unhiding as well as saving the state between sessions. --- src/mainwindow.cpp | 2 + src/mainwindow.ui | 2215 +++++++++++++++++++++++++-------------------------- src/organizer_en.ts | 857 ++++++++++---------- 3 files changed, 1535 insertions(+), 1539 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42cbe919..91da786c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2139,6 +2139,7 @@ void MainWindow::readSettings(const Settings& settings) settings.geometry().restoreDocks(this); settings.geometry().restoreToolbars(this); settings.geometry().restoreState(ui->splitter); + settings.geometry().restoreState(ui->categoriesSplitter); settings.geometry().restoreVisibility(ui->menuBar); settings.geometry().restoreVisibility(ui->statusBar); @@ -2218,6 +2219,7 @@ void MainWindow::storeSettings(Settings& s) s.geometry().saveVisibility(ui->statusBar); s.geometry().saveToolbars(this); s.geometry().saveState(ui->splitter); + s.geometry().saveState(ui->categoriesSplitter); s.geometry().saveMainWindowMonitor(this); s.geometry().saveVisibility(ui->categoriesGroup); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 02c6dec0..6d36e035 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -33,88 +33,427 @@ Qt::CustomContextMenu - - - 6 - - - 6 - - - 6 - - - 0 - + - - - - + + + Qt::Horizontal + + + + + 500 + 16777215 + + + + Categories + + + + 0 + + + 3 + + + 7 + + + 3 + + + 1 + + + + + + 120 + 0 + + + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + + 0 + + + true + + + false + + + + 1 + + + + + + + + false + + + + 0 + 0 + + + + + 0 + 25 + + + + Clear + + + true + + + - - - Categories + + + + 0 + 0 + - - - 0 + + + + + If checked, only mods that match all selected categories are displayed. + + + And + + + true + + + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + Or + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 2 + + + + + + + + 0 + 0 + + + + Profile + + + profileBox + + + + + + + Pick a module collection + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 16777215 + 16777215 + + + + Open list options... + + + Refresh list. This is usually not necessary unless you modified data outside the program. + + + + + + + :/MO/gui/settings:/MO/gui/settings + + + + 16 + 16 + + + + + + + + Show Open Folders menu... + + + + + + + :/MO/gui/open_folder:/MO/gui/open_folder + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + + + Active: + + + + + + + + 0 + 26 + + + + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + QFrame::Sunken + + + 5 + + + QLCDNumber::Flat + + + + + + + + + + 330 + 400 + + + + Qt::CustomContextMenu + + + List of available mods. + + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + + + + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows - - 3 + + 20 - - 7 + + true - - 3 + + true - - 1 + + true + + false + + + 35 + + + true + + + false + + + + + - - + + - 120 - 0 + 20 + 16777215 - + + x + + - 214 - 16777215 + 20 + 20 - - Qt::CustomContextMenu + + true - - QAbstractItemView::ExtendedSelection + + + + + + + 0 + 0 + - - 0 + + Filter - - true + + + + + + + 8 + true + - - false - - - - 1 - - - - - false + + + Qt::Horizontal + + + + 40 + 20 + + + + + - + 0 0 @@ -122,587 +461,795 @@ 0 - 25 + 22 + + + + + 95 + 0 + + false + + + Qt::RightToLeft + + + border:1px solid #ff0000; + - Clear + Clear all Filters - - true + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + + 12 + 12 + - - - - 0 - 0 - + + + + 220 + 0 + + + + Qt::ClickFocus + + + + No groups + + + + + Categories + + + + + Nexus IDs + + + + + + + + + 220 + 0 + + + + Filter - - - - - If checked, only mods that match all selected categories are displayed. - - - And - - - true - - - - - - - If checked, all mods that match at least one of the selected categories are displayed. - - - Or - - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - 2 - - - + + + + + + + + - + - + 0 0 - - Profile + + + 0 + 40 + - - profileBox + + + 9 + 75 + true + - - - - - Pick a module collection + Pick a program to run. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 16777215 - 16777215 - - - - Open list options... - - - Refresh list. This is usually not necessary unless you modified data outside the program. - - - - - - - :/MO/gui/settings:/MO/gui/settings - - - - 16 - 16 - - - - - - - - Show Open Folders menu... - - - - - - - :/MO/gui/open_folder:/MO/gui/open_folder - - - - - - - Restore Backup... - - - - - - - :/MO/gui/restore:/MO/gui/restore - - - - - - - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup - - - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 5 - - - QLCDNumber::Flat - - - - - - - - - - 330 - 400 - - - - Qt::CustomContextMenu - - - List of available mods. - - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked - - - true - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - 20 - - - true - - - true - - - true - - - false - - - 35 - - - true - - - false - - - - - - - - - - 20 - 16777215 - - - - x +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - 20 - 20 + 32 + 32 - - true - - - - - - - - 0 - 0 - - - - Filter - - - - - - - - 8 - true - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - - 95 - 0 - - - + false - - Qt::RightToLeft - - - border:1px solid #ff0000; - - - Clear all Filters - - - - :/MO/gui/edit_clear:/MO/gui/edit_clear - - - - 12 - 12 - - - - - - 220 - 0 - - - - Qt::ClickFocus - + - - No groups - - - - - Categories - + + + + 0 + 0 + + + + + 120 + 0 + + + + + 16777215 + 16777215 + + + + + 10 + 75 + true + + + + Run program + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + + + + + + Run + + + + :/MO/gui/run:/MO/gui/run + + + + 36 + 36 + + + - - Nexus IDs - + + + + 0 + 0 + + + + + 140 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + Create a shortcut in your start menu or on the desktop to the specified program + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + + + Shortcut + + + + :/MO/gui/link:/MO/gui/link + + - - - - - - - 220 - 0 - - - - Filter - - +
-
- - - - - - - + + + + + + + 340 + 250 + + + + + 16777215 + 16777215 + + + + Qt::NoContextMenu + + + QTabWidget::Rounded + + + 4 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Plugins + + + + 6 + + + 6 + + + 6 + + + 0 + - - - - 0 - 0 - - + + + + + false + + + Sort + + + + :/MO/gui/sort:/MO/gui/sort + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Restore Backup... + + + + + + + :/MO/gui/restore:/MO/gui/restore + + + + 16 + 16 + + + + + + + + Create Backup + + + + + + + :/MO/gui/backup:/MO/gui/backup + + + + + + + Active: + + + + + + + + 0 + 26 + + + + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. + + + QFrame::Sunken + + + 4 + + + QLCDNumber::Flat + + + + + + + - 0 - 40 + 250 + 250 - - - 9 - 75 - true - + + Qt::CustomContextMenu - Pick a program to run. + List of available esp/esm files <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - - - 32 - 32 - + + QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + true + + + false + + + QAbstractItemView::InternalMove + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + 0 + + + true + + + false + + + true - + false + + false + - + - - - - 0 - 0 - + + + - - - 120 - 0 - + + Filter - - - 16777215 - 16777215 - + + + + + + + + + false + + + Archives + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - - 10 - 75 - true - + + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - - Run program + + true + + + + + + + + + Qt::CustomContextMenu + + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. + + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! + + BSAs checked here are loaded in such a way that your installation order is obeyed properly. + + + false + + + false + + + false + + + 20 + + + true + + + 1 + + + + + + + + Data + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + refresh data-directory overview + + + Refresh the overview. This may take a moment. + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + + + + + Qt::CustomContextMenu - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + This is an overview of your data directory as visible to the game (and tools). - - + + true + + + true + + + 400 + + + + File + + + + + Mod + + + + + + + + + + + + Filters the above list so that only conflicts are displayed. + + + Filters the above list so that only conflicts are displayed. - Run + Show only conflicts - - - :/MO/gui/run:/MO/gui/run + + + + + + Filters the above list so that files from archives are not shown - - - 36 - 36 - + + + + + Filters the above list so that files from archives are not shown + + + Show files from Archives + + + + + + + Saves + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Qt::CustomContextMenu + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + Downloads + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Refresh downloads view + + + Refresh + + + + :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png + + + + + - - - - 0 - 0 - - + - 140 + 320 0 - - - 16777215 - 16777215 - + + Qt::CustomContextMenu - - - 0 - 0 - + + true - Create a shortcut in your start menu or on the desktop to the specified program + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. + + + Qt::ScrollBarAlwaysOn + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + true + + + QAbstractItemView::ScrollPerPixel + + + 0 + + + false + + + true + + + + + + + + + - Shortcut + Show Hidden - - - :/MO/gui/link:/MO/gui/link + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Filter @@ -710,578 +1257,12 @@ p, li { white-space: pre-wrap; } - - - - - - 340 - 250 - - - - - 16777215 - 16777215 - - - - Qt::NoContextMenu - - - QTabWidget::Rounded - - - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Plugins - - - - 6 - - - 6 - - - 6 - - - 0 - - - - - - - true - - - Sort - - - - :/MO/gui/sort:/MO/gui/sort - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Restore Backup... - - - - - - - :/MO/gui/restore:/MO/gui/restore - - - - 16 - 16 - - - - - - - - Create Backup - - - - - - - :/MO/gui/backup:/MO/gui/backup - - - - - - - Active: - - - - - - - - 0 - 26 - - - - This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - - QFrame::Sunken - - - 4 - - - QLCDNumber::Flat - - - - - - - - - - 250 - 250 - - - - Qt::CustomContextMenu - - - List of available esp/esm files - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps, esms, and esls contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - - - QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked - - - true - - - false - - - QAbstractItemView::InternalMove - - - Qt::MoveAction - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - 0 - - - true - - - false - - - true - - - false - - - false - - - - - - - - - - - - Filter - - - - - - - - - - false - - - Archives - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - - <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - - - true - - - - - - - - - Qt::CustomContextMenu - - - List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. - By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! - - BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - - false - - - false - - - false - - - 20 - - - true - - - 1 - - - - - - - - Data - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - refresh data-directory overview - - - Refresh the overview. This may take a moment. - - - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png - - - - - - - - - Qt::CustomContextMenu - - - This is an overview of your data directory as visible to the game (and tools). - - - true - - - true - - - 400 - - - - File - - - - - Mod - - - - - - - - - - - - Filters the above list so that only conflicts are displayed. - - - Filters the above list so that only conflicts are displayed. - - - Show only conflicts - - - - - - - Filters the above list so that files from archives are not shown - - - - - - Filters the above list so that files from archives are not shown - - - Show files from Archives - - - - - - - - - - Saves - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - Qt::CustomContextMenu - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all save games for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - - - - - - Downloads - - - - 2 - - - 2 - - - 2 - - - 2 - - - - - Refresh downloads view - - - Refresh - - - - :/MO/gui/resources/view-refresh.png:/MO/gui/resources/view-refresh.png - - - - - - - - - - 320 - 0 - - - - Qt::CustomContextMenu - - - true - - - - - - This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - - - Qt::ScrollBarAlwaysOn - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::ScrollPerPixel - - - 0 - - - false - - - true - - - - - - - - - - - Show Hidden - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - - - - - - - - + + + - - + diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 465b0c87..e3531cb2 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -768,194 +768,204 @@ File %3: %4
- + + Hashing download file '%1' + + + + + Cancel + + + + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1622,48 +1632,48 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - - + + Categories - + Clear - + If checked, only mods that match all selected categories are displayed. - + And - + If checked, all mods that match at least one of the selected categories are displayed. - + Or - + Profile - + Pick a module collection - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1673,84 +1683,84 @@ p, li { white-space: pre-wrap; } - + Open list options... - + Refresh list. This is usually not necessary unless you modified data outside the program. - + Show Open Folders menu... - - + + Restore Backup... - - - + + + Create Backup - - + + Active: - + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available mods. - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - + Clear all Filters - + No groups - + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1760,12 +1770,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1774,17 +1784,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1793,32 +1803,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1827,27 +1837,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1855,72 +1865,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1931,307 +1941,307 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Main ToolBar - + &File - - + + &Tools - - - + + + &Help - + &View - + &Toolbars - + &Run - - + + Log - + Install &Mod... - + Install &Mod - - + + Install a new mod from an archive - + Ctrl+M - + &Profiles... - + &Profiles - - + + Configure profiles - + Ctrl+P - + &Executables... - + &Executables - - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + &Tool Plugins - + Tools - + Ctrl+I - + &Settings... - + &Settings - - + + Configure settings and workarounds - + Ctrl+S - - + + Visit &Nexus - - + + Visit the Nexus website in your browser for more mods - + Ctrl+N - - + + &Update Mod Organizer - - + + Mod Organizer is up-to-date - + &Notifications... - - + + Open the notifications dialog - + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Show help options - + Ctrl+H - - + + &Endorse ModOrganizer - - - + + + Endorse Mod Organizer - + &Change Game... - + &Change Game - - + + Open the Instance selection dialog to manage a different Game - - + + E&xit - - + + Exits Mod Organizer - + M&ain Toolbar - + &Small Icons - + Lar&ge Icons - + &Icons Only - + &Text Only - + I&cons and Text - + M&edium Icons - + &Menu - + Status &bar St&atus bar @@ -2280,8 +2290,8 @@ Error: %1 - - + + Endorse @@ -2401,700 +2411,700 @@ Error: %1 - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3102,12 +3112,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3115,12 +3125,12 @@ You can also use online editors and converters instead. - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3128,336 +3138,336 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occurred - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3662,17 +3672,20 @@ Most mods do not have optional esps, so chances are good you are looking at an e - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of MO. + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - Make the selected mod in the lower list unavailable. + Make the selected mod in the right list unavailable. + Make the selected mod in the lower list unavailable. - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + The selected esp (in the right list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. @@ -5767,7 +5780,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> -- cgit v1.3.1 From 17ebbc30c521382009f01e6f2a7dc3a0a06b2fd0 Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 2 Oct 2019 23:35:51 +0200 Subject: Set Plugins tab as default tab again after last commit. --- src/mainwindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6d36e035..cbfed73e 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -715,7 +715,7 @@ p, li { white-space: pre-wrap; } QTabWidget::Rounded - 4 + 0 -- cgit v1.3.1 From 75941afd34bc61e8d52863a9c765ccf8480ac389 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 2 Oct 2019 18:28:31 -0400 Subject: fixed checkboxes not being set when opening the settings dialog --- src/settingsdialoggeneral.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index a9ec5cae..e21fc5d0 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -16,6 +16,13 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->colorTable->load(s); + ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); + ui->compactBox->setChecked(settings().interface().compactDownloads()); + ui->showMetaBox->setChecked(settings().interface().metaDownloads()); + ui->checkForUpdates->setChecked(settings().checkForUpdates()); + ui->usePrereleaseBox->setChecked(settings().usePrereleases()); + ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); + QObject::connect( ui->categoriesBtn, &QPushButton::clicked, [&]{ on_categoriesBtn_clicked(); }); @@ -49,14 +56,13 @@ void GeneralSettingsTab::update() emit settings().styleChanged(newStyle); } + ui->colorTable->commitColors(); + settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); settings().setUsePrereleases(ui->usePrereleaseBox->isChecked()); - - ui->colorTable->commitColors(); - settings().colors().setColorSeparatorScrollbar(ui->colorSeparatorsBox->isChecked()); } -- cgit v1.3.1 From e6a47bf8b70be2c480f77c68c08480a33e71dce3 Mon Sep 17 00:00:00 2001 From: Al Date: Thu, 3 Oct 2019 01:20:38 +0200 Subject: Preemptively bump version to alpha build 3 so I don't forget next time. --- src/version.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 9bf6cc83..433bcb40 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2alpha1\0" +#define VER_FILEVERSION_STR "2.2.2alpha3\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 239cfbe6854f727b5dd3e6922cacc17587361cf5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 4 Oct 2019 06:29:23 -0400 Subject: explicit tab order, seems to fix hang when switching tabs --- src/settingsdialog.ui | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index fba65545..0ecbd101 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1535,7 +1535,19 @@ programs you are intentionally running. - logLevelBox + tabWidget + languageBox + styleBox + centerDialogs + resetDialogsButton + categoriesBtn + showMetaBox + compactBox + checkForUpdates + usePrereleaseBox + colorTable + colorSeparatorsBox + resetColorsBtn baseDirEdit browseBaseDirBtn downloadDirEdit @@ -1548,7 +1560,18 @@ programs you are intentionally running. browseProfilesDirBtn overwriteDirEdit browseOverwriteDirBtn + managedGameDirEdit + browseGameDirBtn + nexusConnect + nexusManualKey + nexusDisconnect + nexusLog + offlineBox + endorsementBox + proxyBox + hideAPICounterBox associateButton + clearCacheButton knownServersList preferredServersList steamUserEdit @@ -1558,8 +1581,17 @@ programs you are intentionally running. pluginBlacklist appIDEdit mechanismBox + hideUncheckedBox + forceEnableBox + lockGUIBox + displayForeignBox + enableArchiveParsingBox bsaDateBtn - tabWidget + execBlacklistBtn + resetGeometryBtn + logLevelBox + dumpsTypeBox + dumpsMaxEdit -- cgit v1.3.1 From 27aec50c25e9cf8beda506ea515d0b0e0930f4be Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 4 Oct 2019 07:11:50 -0400 Subject: added tooltip for flags, reworded those copy/pasted from the mod list --- src/pluginlist.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index c6c61da3..31e88f7e 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -113,10 +113,11 @@ QString PluginList::getColumnName(int column) QString PluginList::getColumnToolTip(int column) { switch (column) { - case COL_NAME: return tr("Name of your mods"); - case COL_PRIORITY: return tr("Load priority of your mod. The higher, the more \"important\" it is and thus " + case COL_NAME: return tr("Name of the plugin"); + case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_PRIORITY: return tr("Load priority of plugins. The higher, the more \"important\" it is and thus " "overwrites data from plugins with lower priority."); - case COL_MODINDEX: return tr("The modindex determines the formids of objects originating from this mods."); + case COL_MODINDEX: return tr("Determines the formids of objects originating from this mods."); default: return tr("unknown"); } } -- cgit v1.3.1 From c17b0829dc23b488523a12e0748b18aab1f427c9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 4 Oct 2019 07:14:30 -0400 Subject: emblemes -> emblems --- src/modlist.cpp | 2 +- src/pluginlist.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index 6018d3d4..c5bc37e9 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1272,7 +1272,7 @@ QString ModList::getColumnToolTip(int column) case COL_CATEGORY: return tr("Category of the mod."); case COL_GAME: return tr("The source game which was the origin of this mod."); case COL_MODID: return tr("Id of the mod as used on Nexus."); - case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_FLAGS: return tr("Emblems to highlight things that might require attention."); case COL_CONTENT: return tr("Depicts the content of the mod:
" "" "" diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 31e88f7e..5f1ae347 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -114,7 +114,7 @@ QString PluginList::getColumnToolTip(int column) { switch (column) { case COL_NAME: return tr("Name of the plugin"); - case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_FLAGS: return tr("Emblems to highlight things that might require attention."); case COL_PRIORITY: return tr("Load priority of plugins. The higher, the more \"important\" it is and thus " "overwrites data from plugins with lower priority."); case COL_MODINDEX: return tr("Determines the formids of objects originating from this mods."); -- cgit v1.3.1 From 88ff7bb3721a2d8b94ccb141a86769c3bfc460bb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 4 Oct 2019 07:45:59 -0400 Subject: made extraction errors translatable --- src/installationmanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 522489e4..af82c5e2 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -307,7 +307,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool QCoreApplication::processEvents(); } while (!future.isFinished() || m_InstallationProgress->isVisible()); if (!future.result()) { - throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); + throw MyException(QString("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); } return result; @@ -491,7 +491,7 @@ bool InstallationManager::testOverwrite(GuessedValue &modName, bool *me if (overwriteDialog.backup()) { QString backupDirectory = generateBackupName(targetDirectory); if (!copyDir(targetDirectory, backupDirectory, false)) { - reportError(tr("failed to create backup")); + reportError(tr("Failed to create backup")); return false; } } @@ -616,12 +616,12 @@ bool InstallationManager::doInstall(GuessedValue &modName, QString game if (!future.result()) { if (m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { if (!m_ErrorMessage.isEmpty()) { - throw MyException(QString("extracting failed (%1)").arg(m_ErrorMessage)); + throw MyException(tr("Extraction failed: %1").arg(m_ErrorMessage)); } else { return false; } } else { - throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); + throw MyException(tr("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); } } -- cgit v1.3.1 From c4dd23abb7a37531040d6348c491dc868919013c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 4 Oct 2019 09:40:37 -0400 Subject: added error messages to FileRenamer and a few more fixes for shell functions changing names --- src/downloadmanager.cpp | 10 +++++----- src/filerenamer.cpp | 42 +++++++++++++++++++++++++++++------------- src/filerenamer.h | 8 ++++++-- src/mainwindow.cpp | 36 ++++++++++++++++++------------------ src/modinfodialogconflicts.cpp | 2 +- src/modinfodialogfiletree.cpp | 8 ++++---- src/modinfodialogimages.cpp | 2 +- src/modinfodialognexus.cpp | 6 +++--- src/motddialog.cpp | 2 +- src/nxmaccessmanager.cpp | 2 +- src/overwriteinfodialog.cpp | 4 ++-- src/problemsdialog.cpp | 2 +- src/selfupdater.cpp | 7 +++++-- src/settings.cpp | 6 ++++-- src/settingsdialognexus.cpp | 2 +- src/texteditor.cpp | 2 +- 16 files changed, 83 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 35f60d7a..b4a7b57d 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1059,11 +1059,11 @@ void DownloadManager::openFile(int index) QDir path = QDir(m_OutputDirectory); if (path.exists(getFileName(index))) { - shell::OpenFile(getFilePath(index)); + shell::Open(getFilePath(index)); return; } - shell::ExploreFile(m_OutputDirectory); + shell::Explore(m_OutputDirectory); return; } @@ -1077,18 +1077,18 @@ void DownloadManager::openInDownloadsFolder(int index) const auto path = getFilePath(index); if (QFile::exists(path)) { - shell::ExploreFile(path); + shell::Explore(path); return; } else { const auto unfinished = path + ".unfinished"; if (QFile::exists(unfinished)) { - shell::ExploreFile(unfinished); + shell::Explore(unfinished); return; } } - shell::ExploreFile(m_OutputDirectory); + shell::Explore(m_OutputDirectory); } diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp index a97d7742..7fc90eb2 100644 --- a/src/filerenamer.cpp +++ b/src/filerenamer.cpp @@ -1,4 +1,5 @@ #include "filerenamer.h" +#include #include #include #include @@ -37,10 +38,13 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt log::debug("removing {}", newName); // user wants to replace the file, so remove it - if (!QFile(newName).remove()) { - log::warn("failed to remove '{}'", newName); + const auto r = shell::Delete(newName); + + if (!r.success()) { + log::error("failed to remove '{}': {}", newName, r.toString()); + // removal failed, warn the user and allow canceling - if (!removeFailed(newName)) { + if (!removeFailed(newName, r)) { log::debug("canceling {}", oldName); // user wants to cancel return RESULT_CANCEL; @@ -64,12 +68,15 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt } // target either didn't exist or was removed correctly + const auto r = shell::Rename(oldName, newName); - if (!QFile::rename(oldName, newName)) { - log::warn("failed to rename '{}' to '{}'", oldName, newName); + if (!r.success()) { + log::error( + "failed to rename '{}' to '{}': {}", + oldName, newName, r.toString()); // renaming failed, warn the user and allow canceling - if (!renameFailed(oldName, newName)) { + if (!renameFailed(oldName, newName, r)) { // user wants to cancel log::debug("canceling"); return RESULT_CANCEL; @@ -144,7 +151,7 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) } } -bool FileRenamer::removeFailed(const QString& name) +bool FileRenamer::removeFailed(const QString& name, const shell::Result& r) { QMessageBox::StandardButtons buttons = QMessageBox::Ok; if (m_flags & MULTIPLE) { @@ -153,8 +160,9 @@ bool FileRenamer::removeFailed(const QString& name) } const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(name), + m_parent, + QObject::tr("File operation failed"), + QObject::tr("Failed to remove \"%1\": %2").arg(name).arg(r.toString()), buttons); if (answer == QMessageBox::Cancel) { @@ -168,7 +176,8 @@ bool FileRenamer::removeFailed(const QString& name) return true; } -bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) +bool FileRenamer::renameFailed( + const QString& oldName, const QString& newName, const shell::Result& r) { QMessageBox::StandardButtons buttons = QMessageBox::Ok; if (m_flags & MULTIPLE) { @@ -177,9 +186,16 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) } const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName)), - buttons); + m_parent, + QObject::tr("File operation failed"), + QObject::tr( + "Failed to rename file: %1.\r\n\r\n" + "Source:\r\n\"%2\"\r\n\r\n" + "Destination:\r\n\"%3\"") + .arg(r.toString()) + .arg(QDir::toNativeSeparators(oldName)) + .arg(QDir::toNativeSeparators(newName)), + buttons); if (answer == QMessageBox::Cancel) { // user wants to stop diff --git a/src/filerenamer.h b/src/filerenamer.h index cd57244c..5583ecbd 100644 --- a/src/filerenamer.h +++ b/src/filerenamer.h @@ -3,6 +3,8 @@ #include +namespace MOBase::shell { class Result; } + /** * Renames individual files and handles dialog boxes to confirm replacements and * failures with the user @@ -126,7 +128,7 @@ private: * @param name The name of the file that failed to be removed * @return true to continue, false to stop **/ - bool removeFailed(const QString& name); + bool removeFailed(const QString& name, const MOBase::shell::Result& r); /** * renaming a file failed, ask the user to continue or cancel @@ -134,7 +136,9 @@ private: * @param newName new filename * @return true to continue, false to stop **/ - bool renameFailed(const QString& oldName, const QString& newName); + bool renameFailed( + const QString& oldName, const QString& newName, + const MOBase::shell::Result& r); }; #endif // FILERENAMER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ab28d22..b29ae11c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3313,12 +3313,12 @@ void MainWindow::openExplorer_clicked() if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - shell::ExploreFile(info->absolutePath()); + shell::Explore(info->absolutePath()); } } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3333,14 +3333,14 @@ void MainWindow::openPluginOriginExplorer_clicked() continue; } ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } else { QModelIndex idx = selection->currentIndex(); QString fileName = idx.data().toString(); ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3355,7 +3355,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } @@ -3376,7 +3376,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - shell::ExploreFile(modInfo->absolutePath()); + shell::Explore(modInfo->absolutePath()); } } } @@ -4344,61 +4344,61 @@ void MainWindow::disableVisibleMods() void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); - shell::ExploreFile(dataPath); + shell::Explore(dataPath); } void MainWindow::openLogsFolder() { QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); - shell::ExploreFile(logsPath); + shell::Explore(logsPath); } void MainWindow::openInstallFolder() { - shell::ExploreFile(qApp->applicationDirPath()); + shell::Explore(qApp->applicationDirPath()); } void MainWindow::openPluginsFolder() { QString pluginsPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - shell::ExploreFile(pluginsPath); + shell::Explore(pluginsPath); } void MainWindow::openProfileFolder() { - shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::Explore(m_OrganizerCore.currentProfile()->absolutePath()); } void MainWindow::openIniFolder() { if (m_OrganizerCore.currentProfile()->localSettingsEnabled()) { - shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::Explore(m_OrganizerCore.currentProfile()->absolutePath()); } else { - shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } } void MainWindow::openDownloadsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().paths().downloads()); + shell::Explore(m_OrganizerCore.settings().paths().downloads()); } void MainWindow::openModsFolder() { - shell::ExploreFile(m_OrganizerCore.settings().paths().mods()); + shell::Explore(m_OrganizerCore.settings().paths().mods()); } void MainWindow::openGameFolder() { - shell::ExploreFile(m_OrganizerCore.managedGame()->gameDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->gameDirectory()); } void MainWindow::openMyGamesFolder() { - shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::Explore(m_OrganizerCore.managedGame()->documentsDirectory()); } @@ -5391,7 +5391,7 @@ void MainWindow::openDataOriginExplorer_clicked() const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); log::debug("opening in explorer: {}", fullPath); - shell::ExploreFile(fullPath); + shell::Explore(fullPath); } void MainWindow::updateAvailable() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 3a71b405..36559a75 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -547,7 +547,7 @@ void ConflictsTab::exploreItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - shell::ExploreFile(item->fileName()); + shell::Explore(item->fileName()); return true; }); } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 207c792d..71ea9210 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -128,7 +128,7 @@ void FileTreeTab::onOpen() return; } - shell::OpenFile(m_fs->filePath(selection)); + shell::Open(m_fs->filePath(selection)); } void FileTreeTab::onPreview() @@ -146,9 +146,9 @@ void FileTreeTab::onExplore() auto selection = singleSelection(); if (selection.isValid()) { - shell::ExploreFile(m_fs->filePath(selection)); + shell::Explore(m_fs->filePath(selection)); } else { - shell::ExploreFile(mod().absolutePath()); + shell::Explore(mod().absolutePath()); } } @@ -204,7 +204,7 @@ void FileTreeTab::onUnhide() void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(mod().absolutePath()); + shell::Explore(mod().absolutePath()); } bool FileTreeTab::deleteFile(const QModelIndex& index) diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 9d347f57..c5b04538 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -547,7 +547,7 @@ void ImagesTab::showTooltip(QHelpEvent* e) void ImagesTab::onExplore() { if (auto* f=m_files.selectedFile()) { - MOBase::shell::ExploreFile(f->path()); + shell::Explore(f->path()); } } diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 95e62328..59bfe930 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -95,7 +95,7 @@ void NexusTab::update() connect( page, &NexusTabWebpage::linkClicked, - [&](const QUrl& url){ shell::OpenLink(url); }); + [&](const QUrl& url){ shell::Open(url); }); ui->endorse->setEnabled( (mod().endorsedState() == ModInfo::ENDORSED_FALSE) || @@ -363,7 +363,7 @@ void NexusTab::onVisitNexus() const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod().getGameName()); - shell::OpenLink(QUrl(nexusLink)); + shell::Open(QUrl(nexusLink)); } } @@ -412,6 +412,6 @@ void NexusTab::onVisitCustomURL() { const auto url = mod().parseCustomURL(); if (url.isValid()) { - shell::OpenLink(url); + shell::Open(url); } } diff --git a/src/motddialog.cpp b/src/motddialog.cpp index ca1e60ad..eee80205 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -47,5 +47,5 @@ void MotDDialog::on_okButton_clicked() void MotDDialog::linkClicked(const QUrl &url) { - shell::OpenLink(url); + shell::Open(url); } diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index c6ef7bc7..3cc1b7d9 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -286,7 +286,7 @@ void NexusSSOLogin::onMessage(const QString& s) // open browser const auto url = NexusSSOPage.arg(m_guid); - shell::OpenLink(url); + shell::Open(url); m_timeout.stop(); setState(WaitingForBrowser); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index fe1d8825..078bcfc9 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -229,7 +229,7 @@ void OverwriteInfoDialog::renameTriggered() void OverwriteInfoDialog::openFile(const QModelIndex &index) { - shell::OpenFile(m_FileSystemModel->filePath(index)); + shell::Open(m_FileSystemModel->filePath(index)); } @@ -270,7 +270,7 @@ void OverwriteInfoDialog::createDirectoryTriggered() void OverwriteInfoDialog::on_explorerButton_clicked() { - shell::ExploreFile(m_ModInfo->absolutePath()); + shell::Explore(m_ModInfo->absolutePath()); } void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 63d58295..ea23beec 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -112,5 +112,5 @@ void ProblemsDialog::startFix() void ProblemsDialog::urlClicked(const QUrl &url) { - shell::OpenLink(url); + shell::Open(url); } diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 8887927a..5a70568e 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -341,11 +341,14 @@ void SelfUpdater::downloadCancel() void SelfUpdater::installUpdate() { const QString parameters = "/DIR=\"" + qApp->applicationDirPath() + "\" "; + const auto r = shell::Execute(m_UpdateFile.fileName(), parameters); - if (shell::Execute(m_UpdateFile.fileName(), parameters)) { + if (r.success()) { QCoreApplication::quit(); } else { - reportError(tr("Failed to start %1").arg(m_UpdateFile.fileName())); + reportError(tr("Failed to start %1: %2") + .arg(m_UpdateFile.fileName()) + .arg(r.toString())); } m_UpdateFile.remove(); diff --git a/src/settings.cpp b/src/settings.cpp index 462cd92a..15bc801a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1808,10 +1808,12 @@ void NexusSettings::registerAsNXMHandler(bool force) } parameters += " \"" + executable + "\""; - if (!shell::Execute(nxmPath, parameters)) { + const auto r = shell::Execute(nxmPath, parameters); + + if (!r.success()) { QMessageBox::critical( nullptr, QObject::tr("Failed"), - QObject::tr("Failed to start the helper application")); + QObject::tr("Failed to start the helper application: %1").arg(r.toString())); } } diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 826075c0..2021bdc1 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -49,7 +49,7 @@ public: void openBrowser() { - shell::OpenLink(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); + shell::Open(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); } void paste() diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 0c0eb1cc..4a8080f4 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -199,7 +199,7 @@ void TextEditor::explore() return; } - MOBase::shell::ExploreFile(m_filename); + shell::Explore(m_filename); } void TextEditor::onModified(bool b) -- cgit v1.3.1 From daa0b4b3390f80ebc4d8b65f937f2715c76711d4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 4 Oct 2019 10:47:42 -0400 Subject: forgot one tr() --- src/installationmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index af82c5e2..dd5cfb55 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -307,7 +307,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool QCoreApplication::processEvents(); } while (!future.isFinished() || m_InstallationProgress->isVisible()); if (!future.result()) { - throw MyException(QString("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); + throw MyException(tr("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); } return result; -- cgit v1.3.1 From 49e19c8185eb890b6dc6788bf95dd65455e72538 Mon Sep 17 00:00:00 2001 From: Al Date: Fri, 4 Oct 2019 17:18:06 +0200 Subject: Added "No valid game data" and "No Nexus ID" filters as per #295 --- src/categories.h | 2 ++ src/mainwindow.cpp | 2 ++ src/modlistsortproxy.cpp | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) (limited to 'src') diff --git a/src/categories.h b/src/categories.h index 48e0b44b..67fee3e7 100644 --- a/src/categories.h +++ b/src/categories.h @@ -50,6 +50,8 @@ public: static const int CATEGORY_SPECIAL_BACKUP = 10006; static const int CATEGORY_SPECIAL_MANAGED = 10007; static const int CATEGORY_SPECIAL_UNMANAGED = 10008; + static const int CATEGORY_SPECIAL_NOGAMEDATA = 10009; + static const int CATEGORY_SPECIAL_NONEXUSID = 10010; public: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ab28d22..a53c081b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2691,6 +2691,8 @@ void MainWindow::refreshFilters() addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); addContentFilters(); std::set categoriesUsed; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 77ffad96..a9ff6463 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -308,6 +308,15 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; } break; + case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: { + if (!info->hasFlag(ModInfo::FLAG_INVALID)) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { + if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && + !info->hasFlag(ModInfo::FLAG_BACKUP) && + !info->hasFlag(ModInfo::FLAG_SEPARATOR) && + !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return false; + } break; default: { if (!info->categorySet(*iter)) return false; } break; @@ -353,6 +362,15 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; } break; + case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: { + if (info->hasFlag(ModInfo::FLAG_INVALID)) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { + if ((info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && + !info->hasFlag(ModInfo::FLAG_BACKUP) && + !info->hasFlag(ModInfo::FLAG_SEPARATOR) && + !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return true; + } break; default: { if (info->categorySet(*iter)) return true; } break; -- cgit v1.3.1 From 633ef81972139f3c082429ada10ffb27d9f07898 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 27 Sep 2019 17:07:04 -0400 Subject: moved checks to sanitychecks.cpp added check for blocked files, only logs --- src/CMakeLists.txt | 2 + src/main.cpp | 46 +------------- src/sanitychecks.cpp | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 43 deletions(-) create mode 100644 src/sanitychecks.cpp (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b21d1a8b..180422ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ SET(organizer_SRCS envshortcut.cpp envwindows.cpp colortable.cpp + sanitychecks.cpp shared/windows_error.cpp shared/error_report.cpp @@ -324,6 +325,7 @@ set(application mainwindow moapplication moshortcut + sanitychecks selfupdater singleinstance statusbar diff --git a/src/main.cpp b/src/main.cpp index ba988ae3..fe5fd87a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -91,6 +91,9 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; + +void sanityChecks(const env::Environment& env); + bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); QString fullPath = dataPath + "/" + QString::fromStdWString(subPath); @@ -496,49 +499,6 @@ static QString getVersionDisplayString() return createVersionInfo().displayString(3); } -void checkMissingFiles() -{ - // files that are likely to be eaten - static const QStringList files({ - "helper.exe", "nxmhandler.exe", - "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", - "usvfs_x64.dll", "usvfs_x86.dll" - }); - - const auto dir = QCoreApplication::applicationDirPath(); - - for (const auto& name : files) { - const QFileInfo file(dir + QDir::separator() + name); - if (!file.exists()) { - log::warn( - "'{}' seems to be missing, an antivirus may have deleted it", - file.absoluteFilePath()); - } - } -} - -void checkNahimic(const env::Environment& e) -{ - for (auto&& m : e.loadedModules()) { - const QFileInfo file(m.path()); - - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { - log::warn( - "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " - "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it."); - - break; - } - } -} - -void sanityChecks(const env::Environment& e) -{ - checkMissingFiles(); - checkNahimic(e); -} - int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp new file mode 100644 index 00000000..5f9705b8 --- /dev/null +++ b/src/sanitychecks.cpp @@ -0,0 +1,174 @@ +#include "env.h" +#include "envmodule.h" +#include + +using namespace MOBase; + +enum class SecurityZone +{ + NoZone = -1, + MyComputer = 0, + Intranet = 1, + Trusted = 2, + Internet = 3, + Untrusted = 4, +}; + +QString toString(SecurityZone z) +{ + switch (z) + { + case SecurityZone::NoZone: return "NoZone"; + case SecurityZone::MyComputer: return "MyComputer"; + case SecurityZone::Intranet: return "Intranet"; + case SecurityZone::Trusted: return "Trusted"; + case SecurityZone::Internet: return "Internet"; + case SecurityZone::Untrusted: return "Untrusted"; + default: return QString("unknown (%1)").arg(static_cast(z)); + } +} + +bool isZoneBlocked(SecurityZone z) +{ + return (z == SecurityZone::Internet || z == SecurityZone::Untrusted); +} + +bool isFileBlocked(const QFileInfo& fi) +{ + const QString ads = "Zone.Identifier"; + const auto key = "ZoneTransfer/ZoneId"; + + const auto path = fi.absoluteFilePath(); + const auto adsPath = path + ":" + ads; + + QFile f(adsPath); + if (!f.exists()) { + return false; + } + + log::debug("file '{}' has an ADS for {}", path, adsPath); + + QSettings qs(adsPath, QSettings::IniFormat); + + if (!qs.contains(key)) { + log::debug("but key '{}' is not found", key); + return false; + } + + const auto v = qs.value(key); + if (v.isNull()) { + log::debug("but key '{}' is null", key); + return false; + } + + bool ok = false; + const auto z = static_cast(v.toInt(&ok)); + + if (!ok) { + log::debug( + "but key '{}' is not an int (value is '{}')", + key, v); + + return false; + } + + if (!isZoneBlocked(z)) { + log::debug( + "but zone id is {}, {}, which is fine", + static_cast(z), toString(z)); + + return false; + } + + log::warn( + "file '{}' is blocked (zone id is {}, {})", + path, static_cast(z), toString(z)); + + return true; +} + +void checkBlockedFiles(const QDir& dir) +{ + if (!dir.exists()) { + log::error( + "while checking for blocked files, directory '{}' not found", + dir.absolutePath()); + + return; + } + + const auto files = dir.entryInfoList({"*.dll", "*.exe"}, QDir::Files); + if (files.empty()) { + log::error( + "while checking for blocked files, directory '{}' is empty", + dir.absolutePath()); + + return; + } + + for (auto&& fi : files) { + isFileBlocked(fi); + } +} + +void checkBlocked() +{ + const QString appDir = QCoreApplication::applicationDirPath(); + + const QDir dirs[] = { + appDir, + appDir + "/dlls", + appDir + "/loot", + appDir + "/NCC", + appDir + "/platforms", + appDir + "/plugins" + }; + + for (const auto& d : dirs) { + checkBlockedFiles(d); + } +} + +void checkMissingFiles() +{ + // files that are likely to be eaten + static const QStringList files({ + "helper.exe", "nxmhandler.exe", + "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", + "usvfs_x64.dll", "usvfs_x86.dll" + }); + + const auto dir = QCoreApplication::applicationDirPath(); + + for (const auto& name : files) { + const QFileInfo file(dir + QDir::separator() + name); + if (!file.exists()) { + log::warn( + "'{}' seems to be missing, an antivirus may have deleted it", + file.absoluteFilePath()); + } + } +} + +void checkNahimic(const env::Environment& e) +{ + for (auto&& m : e.loadedModules()) { + const QFileInfo file(m.path()); + + if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { + log::warn( + "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "Mod Organizer, such as freezing or blank windows. Consider " + "uninstalling it."); + + break; + } + } +} + +void sanityChecks(const env::Environment& e) +{ + checkBlocked(); + checkMissingFiles(); + checkNahimic(e); +} -- cgit v1.3.1 From 1dc39621998457ab09765520f6cce4e266eaa8db Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 07:03:41 -0400 Subject: changed some of blocked files logging --- src/sanitychecks.cpp | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 5f9705b8..185b1f9c 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -14,7 +14,7 @@ enum class SecurityZone Untrusted = 4, }; -QString toString(SecurityZone z) +QString toCodeName(SecurityZone z) { switch (z) { @@ -24,10 +24,17 @@ QString toString(SecurityZone z) case SecurityZone::Trusted: return "Trusted"; case SecurityZone::Internet: return "Internet"; case SecurityZone::Untrusted: return "Untrusted"; - default: return QString("unknown (%1)").arg(static_cast(z)); + default: return "Unknown"; } } +QString toString(SecurityZone z) +{ + return QString("%1 (%2)") + .arg(toCodeName(z)) + .arg(static_cast(z)); +} + bool isZoneBlocked(SecurityZone z) { return (z == SecurityZone::Internet || z == SecurityZone::Untrusted); @@ -46,18 +53,18 @@ bool isFileBlocked(const QFileInfo& fi) return false; } - log::debug("file '{}' has an ADS for {}", path, adsPath); + log::debug("'{}' has an ADS for {}", path, adsPath); - QSettings qs(adsPath, QSettings::IniFormat); + const QSettings qs(adsPath, QSettings::IniFormat); if (!qs.contains(key)) { - log::debug("but key '{}' is not found", key); + log::debug("'{}': key '{}' not found", adsPath, key); return false; } const auto v = qs.value(key); if (v.isNull()) { - log::debug("but key '{}' is null", key); + log::debug("'{}': key '{}' is null", adsPath, key); return false; } @@ -65,25 +72,16 @@ bool isFileBlocked(const QFileInfo& fi) const auto z = static_cast(v.toInt(&ok)); if (!ok) { - log::debug( - "but key '{}' is not an int (value is '{}')", - key, v); - + log::debug("'{}': key '{}' is not an int (value is '{}')", adsPath, key, v); return false; } if (!isZoneBlocked(z)) { - log::debug( - "but zone id is {}, {}, which is fine", - static_cast(z), toString(z)); - + log::debug("'{}': zone id is {}, which is fine", adsPath, toString(z)); return false; } - log::warn( - "file '{}' is blocked (zone id is {}, {})", - path, static_cast(z), toString(z)); - + log::warn("'{}': file is blocked (zone id is {})", path, toString(z)); return true; } @@ -141,7 +139,8 @@ void checkMissingFiles() const auto dir = QCoreApplication::applicationDirPath(); for (const auto& name : files) { - const QFileInfo file(dir + QDir::separator() + name); + const QFileInfo file(dir + "/" + name); + if (!file.exists()) { log::warn( "'{}' seems to be missing, an antivirus may have deleted it", @@ -168,6 +167,8 @@ void checkNahimic(const env::Environment& e) void sanityChecks(const env::Environment& e) { + log::debug("running sanity checks"); + checkBlocked(); checkMissingFiles(); checkNahimic(e); -- cgit v1.3.1 From 897d315359718712a8e6b160c279f563f7436367 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 07:38:14 -0400 Subject: fixes for VS preview: missing namespace, missing setting() with two parameters --- src/profile.cpp | 5 +++++ src/profile.h | 7 +++++-- src/settingsutilities.h | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/profile.cpp b/src/profile.cpp index e76060b9..f2360674 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -917,6 +917,11 @@ QVariant Profile::setting(const QString §ion, const QString &name, return m_Settings->value(section + "/" + name, fallback); } +QVariant Profile::setting(const QString &name, const QVariant &fallback) const +{ + return m_Settings->value(name, fallback); +} + void Profile::storeSetting(const QString §ion, const QString &name, const QVariant &value) { diff --git a/src/profile.h b/src/profile.h index bc7964f8..85d929ac 100644 --- a/src/profile.h +++ b/src/profile.h @@ -313,8 +313,11 @@ public: void dumpModStatus() const; - QVariant setting(const QString §ion, const QString &name = QString(), - const QVariant &fallback = QVariant()) const; + QVariant setting( + const QString §ion, const QString &name, + const QVariant &fallback) const; + + QVariant setting(const QString &name, const QVariant &fallback={}) const; void storeSetting(const QString §ion, const QString &name, const QVariant &value); diff --git a/src/settingsutilities.h b/src/settingsutilities.h index a6737144..ac6aeb29 100644 --- a/src/settingsutilities.h +++ b/src/settingsutilities.h @@ -39,11 +39,11 @@ void logChange( using VC = ValueConverter; if (oldValue) { - log::debug( + MOBase::log::debug( "setting '{}' changed from '{}' to '{}'", displayName, VC::convert(*oldValue), VC::convert(newValue)); } else { - log::debug( + MOBase::log::debug( "setting '{}' set to '{}'", displayName, VC::convert(newValue)); } -- cgit v1.3.1 From 1467cb9955776c0e7738a93eb338321aabfda456 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 08:11:12 -0400 Subject: sanity checks: comments, more debug logging --- src/sanitychecks.cpp | 131 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 185b1f9c..3b4185a7 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -24,7 +24,7 @@ QString toCodeName(SecurityZone z) case SecurityZone::Trusted: return "Trusted"; case SecurityZone::Internet: return "Internet"; case SecurityZone::Untrusted: return "Untrusted"; - default: return "Unknown"; + default: return "Unknown zone"; } } @@ -35,21 +35,42 @@ QString toString(SecurityZone z) .arg(static_cast(z)); } +// whether the given zone is considered blocked +// bool isZoneBlocked(SecurityZone z) { - return (z == SecurityZone::Internet || z == SecurityZone::Untrusted); + switch (z) + { + case SecurityZone::Internet: + case SecurityZone::Untrusted: + return true; + + case SecurityZone::NoZone: + case SecurityZone::MyComputer: + case SecurityZone::Intranet: + case SecurityZone::Trusted: + default: + return false; + } } +// whether the given file is blocked +// bool isFileBlocked(const QFileInfo& fi) { + // name of the alternate data stream containing the zone identifier ini const QString ads = "Zone.Identifier"; + + // key in the ini const auto key = "ZoneTransfer/ZoneId"; + // the path to the ADS is always `filename:Zone.Identifier` const auto path = fi.absoluteFilePath(); const auto adsPath = path + ":" + ads; QFile f(adsPath); if (!f.exists()) { + // no ADS for this file return false; } @@ -57,17 +78,20 @@ bool isFileBlocked(const QFileInfo& fi) const QSettings qs(adsPath, QSettings::IniFormat); + // looking for key if (!qs.contains(key)) { log::debug("'{}': key '{}' not found", adsPath, key); return false; } + // getting value const auto v = qs.value(key); if (v.isNull()) { log::debug("'{}': key '{}' is null", adsPath, key); return false; } + // should be an int bool ok = false; const auto z = static_cast(v.toInt(&ok)); @@ -77,57 +101,79 @@ bool isFileBlocked(const QFileInfo& fi) } if (!isZoneBlocked(z)) { + // that zone is not a blocked zone log::debug("'{}': zone id is {}, which is fine", adsPath, toString(z)); return false; } - log::warn("'{}': file is blocked (zone id is {})", path, toString(z)); + // file is blocked + log::warn("'{}': file is blocked, zone id is {}", path, toString(z)); return true; } -void checkBlockedFiles(const QDir& dir) +int checkBlockedFiles(const QDir& dir) { + // executables file types + const QStringList FileTypes = {"*.dll", "*.exe"}; + if (!dir.exists()) { + // shouldn't happen log::error( "while checking for blocked files, directory '{}' not found", dir.absolutePath()); - return; + return 1; } - const auto files = dir.entryInfoList({"*.dll", "*.exe"}, QDir::Files); + const auto files = dir.entryInfoList(FileTypes, QDir::Files); if (files.empty()) { + // shouldn't happen log::error( "while checking for blocked files, directory '{}' is empty", dir.absolutePath()); - return; + return 1; } + int n = 0; + + // checking each file in this directory for (auto&& fi : files) { - isFileBlocked(fi); + if (isFileBlocked(fi)) { + ++n; + } } + + return n; } -void checkBlocked() +int checkBlocked() { + // directories that contain executables; these need to be explicit because + // portable instances might add billions of files in MO's directory + const QString dirs[] = { + ".", + "/dlls", + "/loot", + "/NCC", + "/platforms", + "/plugins" + }; + + log::debug(" . blocked files"); const QString appDir = QCoreApplication::applicationDirPath(); - const QDir dirs[] = { - appDir, - appDir + "/dlls", - appDir + "/loot", - appDir + "/NCC", - appDir + "/platforms", - appDir + "/plugins" - }; + int n = 0; for (const auto& d : dirs) { - checkBlockedFiles(d); + const auto path = QDir(appDir + "/" + d).canonicalPath(); + n += checkBlockedFiles(path); } + + return n; } -void checkMissingFiles() +int checkMissingFiles() { // files that are likely to be eaten static const QStringList files({ @@ -136,8 +182,11 @@ void checkMissingFiles() "usvfs_x64.dll", "usvfs_x86.dll" }); + log::debug(" . missing files"); const auto dir = QCoreApplication::applicationDirPath(); + int n = 0; + for (const auto& name : files) { const QFileInfo file(dir + "/" + name); @@ -145,12 +194,23 @@ void checkMissingFiles() log::warn( "'{}' seems to be missing, an antivirus may have deleted it", file.absoluteFilePath()); + + ++n; } } + + return n; } -void checkNahimic(const env::Environment& e) +bool checkNahimic(const env::Environment& e) { + // Nahimic seems to interfere mostly with dialogs, like the mod info dialog: + // it renders dialogs fully white and makes it impossible to interact with + // them + // + // NahimicOSD.dll is usually loaded on startup, but there has been some + // reports where it got loaded later, so this check is not entirely accurate + for (auto&& m : e.loadedModules()) { const QFileInfo file(m.path()); @@ -160,16 +220,37 @@ void checkNahimic(const env::Environment& e) "Mod Organizer, such as freezing or blank windows. Consider " "uninstalling it."); - break; + return true; } } + + return false; +} + +int checkIncompatibilities(const env::Environment& e) +{ + log::debug(" . incompatibilities"); + + int n = 0; + + if (checkNahimic(e)) { + ++n; + } + + return n; } void sanityChecks(const env::Environment& e) { - log::debug("running sanity checks"); + log::debug("running sanity checks..."); + + int n = 0; + + n += checkBlocked(); + n += checkMissingFiles(); + n += checkIncompatibilities(e); - checkBlocked(); - checkMissingFiles(); - checkNahimic(e); + log::debug( + "sanity checks done, {}", + (n > 0 ? "problems were found" : "everything looks okay")); } -- cgit v1.3.1 From 93423f63512f7d7e6d21bc8e75fc1a17bdba28aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 08:21:59 -0400 Subject: don't log "(no guid"), happens all the time for the windows firewall --- src/envsecurity.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index ffb17c42..786291c6 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -206,9 +206,10 @@ QString SecurityProduct::toString() const s += ", definitions outdated"; } - if (m_guid.isNull()) { - s += ", (no guid)"; - } else { + // all products have a guid, but the windows firewall is not actually a real + // one from wmi, it's queried independently in getWindowsFirewall() and has a + // null guid, so just don't log it + if (!m_guid.isNull()) { s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); } -- cgit v1.3.1 From c4d96ed8cfee5c66089f9bdf7c0ce8567aaf46fe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 08:30:58 -0400 Subject: check the api setting on startup to hide it if needed --- src/mainwindow.cpp | 2 +- src/statusbar.cpp | 4 +++- src/statusbar.h | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8ab28d22..0a82fcaf 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -226,7 +226,7 @@ MainWindow::MainWindow(Settings &settings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); ui->setupUi(this); - ui->statusBar->setup(ui); + ui->statusBar->setup(ui, settings); { auto* ni = NexusInterface::instance(&m_PluginContainer); diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 3734aa87..c2c54862 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -9,7 +9,7 @@ StatusBar::StatusBar(QWidget* parent) : { } -void StatusBar::setup(Ui::MainWindow* mainWindowUI) +void StatusBar::setup(Ui::MainWindow* mainWindowUI, const Settings& settings) { ui = mainWindowUI; m_notifications = new StatusBarAction(ui->actionNotifications); @@ -51,6 +51,8 @@ void StatusBar::setup(Ui::MainWindow* mainWindowUI) clearMessage(); setProgress(-1); setAPI({}, {}); + + checkSettings(settings); } void StatusBar::setProgress(int percent) diff --git a/src/statusbar.h b/src/statusbar.h index 442b9acf..3da45d41 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -34,7 +34,7 @@ class StatusBar : public QStatusBar public: StatusBar(QWidget* parent=nullptr); - void setup(Ui::MainWindow* ui); + void setup(Ui::MainWindow* ui, const Settings& settings); void setProgress(int percent); void setNotifications(bool hasNotifications); -- cgit v1.3.1 From 76df854483c40cb7610d79c5540a5e67f554d840 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 08:53:31 -0400 Subject: only log a warning for symlinks instead of exiting MO added logs: when dmp files are present, for portable instances, and the ini path clear the log widget when switching instances --- src/instancemanager.cpp | 7 ++++++- src/instancemanager.h | 1 + src/main.cpp | 8 ++++++++ src/organizercore.cpp | 37 ++++++++++++++++++++++++++----------- 4 files changed, 41 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index c0d343de..ec3c1add 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -263,6 +263,11 @@ QStringList InstanceManager::instances() const } +bool InstanceManager::isPortablePath(const QString& dataPath) +{ + return (dataPath == qApp->applicationDirPath()); +} + bool InstanceManager::portableInstall() const { return QFile::exists(qApp->applicationDirPath() + "/" + @@ -303,7 +308,7 @@ void InstanceManager::createDataPath(const QString &dataPath) const QString InstanceManager::determineDataPath() { QString instanceId = currentInstance(); - if (portableInstallIsLocked()) + if (portableInstallIsLocked()) { instanceId.clear(); } diff --git a/src/instancemanager.h b/src/instancemanager.h index 0e31fb08..649c2195 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -39,6 +39,7 @@ public: QString currentInstance() const; bool allowedToChangeInstance() const; + static bool isPortablePath(const QString& dataPath); private: diff --git a/src/main.cpp b/src/main.cpp index fe5fd87a..a6918d99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -515,6 +515,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, const QString dataPath = application.property("dataPath").toString(); log::info("data path: {}", dataPath); + if (InstanceManager::isPortablePath(dataPath)) { + log::debug("this is a portable instance"); + } + if (!bootstrap()) { reportError("failed to set up data paths"); return 1; @@ -531,6 +535,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, Settings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName())); log::getDefault().setLevel(settings.diagnostics().logLevel()); + log::debug("using ini at '{}'", settings.filename()); + // global crashDumpType sits in OrganizerCore to make a bit less ugly to // update it when the settings are changed during runtime OrganizerCore::setGlobalCrashDumpsType(settings.diagnostics().crashDumpsType()); @@ -860,6 +866,8 @@ int main(int argc, char *argv[]) } // we continue for the primary instance OR if MO was called with parameters do { + LogModel::instance().clear(); + // make sure the log file isn't locked in case MO was restarted and // the previous instance gets deleted log::getDefault().setFile({}); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a4a89c99..335910da 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -443,16 +443,20 @@ bool OrganizerCore::createDirectory(const QString &path) { } bool OrganizerCore::checkPathSymlinks() { - bool hasSymlink = (QFileInfo(m_Settings.paths().profiles()).isSymLink() || + const bool hasSymlink = ( + QFileInfo(m_Settings.paths().profiles()).isSymLink() || QFileInfo(m_Settings.paths().mods()).isSymLink() || QFileInfo(m_Settings.paths().overwrite()).isSymLink()); + if (hasSymlink) { - QMessageBox::critical(nullptr, QObject::tr("Error"), - QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) " - "is on a path containing a symbolic (or other) link. This is incompatible " - "with MO2's VFS system.")); + log::warn("{}", QObject::tr( + "One of the configured MO2 directories (profiles, mods, or overwrite) " + "is on a path containing a symbolic (or other) link. This is likely to " + "be incompatible with MO2's virtual filesystem.")); + return false; } + return true; } @@ -498,18 +502,29 @@ void OrganizerCore::setLogLevel(log::Levels level) log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); } -bool OrganizerCore::cycleDiagnostics() { - if (int maxDumps = settings().diagnostics().crashDumpsMax()) - removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); +bool OrganizerCore::cycleDiagnostics() +{ + const auto maxDumps = settings().diagnostics().crashDumpsMax(); + const auto path = QString::fromStdWString(crashDumpsPath()); + + if (maxDumps > 0) { + removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed); + } + + // log if there are any files left + const auto files = QDir(path).entryList({"*.dmp"}, QDir::Files); + if (!files.isEmpty()) { + log::debug("there are crash dumps in '{}'", path); + } + return true; } -//static -void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) { +void OrganizerCore::setGlobalCrashDumpsType(CrashDumpsType type) +{ m_globalCrashDumpsType = type; } -//static std::wstring OrganizerCore::crashDumpsPath() { return ( qApp->property("dataPath").toString() + "/" -- cgit v1.3.1 From 3fe54c8431b9c1e792a235b9b370267447c3210c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 10:11:55 -0400 Subject: now using ui file for validation progress dialog moved elapsed timer to NexusKeyValidator, progress dialog now just shows what the validator is doing, which allows for hiding and showing it at any time recreate the dialog when the parent changes, avoids theme errors --- src/nxmaccessmanager.cpp | 92 ++++++++++++++++++------------- src/nxmaccessmanager.h | 65 +++++++++++----------- src/validationprogressdialog.ui | 117 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 70 deletions(-) create mode 100644 src/validationprogressdialog.ui (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 3cc1b7d9..731feafb 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -18,6 +18,7 @@ along with Mod Organizer. If not, see . */ #include "nxmaccessmanager.h" +#include "ui_validationprogressdialog.h" #include "iplugingame.h" #include "nexusinterface.h" #include "nxmurl.h" @@ -48,24 +49,14 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t) : - m_timeout(t), m_bar(nullptr), m_buttons(nullptr), m_timer(nullptr), - m_first(true) +ValidationProgressDialog::ValidationProgressDialog(const NexusKeyValidator& v) + : m_validator(v), m_updateTimer(nullptr), m_first(true) { - m_bar = new QProgressBar; - m_bar->setTextVisible(false); + ui.reset(new Ui::ValidationProgressDialog); + ui->setupUi(this); - auto* label = new QLabel(tr("Validating Nexus Connection")); - label->setAlignment(Qt::AlignHCenter); - - auto* vbox = new QVBoxLayout(this); - vbox->addWidget(label); - vbox->addWidget(m_bar); - - m_buttons = new QDialogButtonBox; - m_buttons->addButton(tr("Hide"), QDialogButtonBox::RejectRole); - connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); - vbox->addWidget(m_buttons); + connect(ui->hide, &QPushButton::clicked, [&]{ onHide(); }); + connect(ui->cancel, &QPushButton::clicked, [&]{ onCancel(); }); } void ValidationProgressDialog::setParentWidget(QWidget* w) @@ -75,30 +66,32 @@ void ValidationProgressDialog::setParentWidget(QWidget* w) hide(); setParent(w, windowFlags() | Qt::Dialog); setModal(false); - setVisible(wasVisible); + + if (w && wasVisible) { + setVisible(true); + } } void ValidationProgressDialog::start() { - if (!m_timer) { - m_timer = new QTimer(this); - connect(m_timer, &QTimer::timeout, [&]{ onTimer(); }); - m_timer->setInterval(100ms); + if (!m_updateTimer) { + m_updateTimer = new QTimer(this); + connect(m_updateTimer, &QTimer::timeout, [&]{ onTimer(); }); + m_updateTimer->setInterval(100ms); } - m_bar->setRange(0, m_timeout.count()); - m_bar->setValue(0); + ui->progress->setRange(0, m_validator.timeout().count()); + ui->progress->setValue(0); - m_elapsed.start(); - m_timer->start(); + m_updateTimer->start(); show(); } void ValidationProgressDialog::stop() { - if (m_timer) { - m_timer->stop(); + if (m_updateTimer) { + m_updateTimer->stop(); } hide(); @@ -118,18 +111,18 @@ void ValidationProgressDialog::closeEvent(QCloseEvent* e) e->ignore(); } -void ValidationProgressDialog::onButton(QAbstractButton* b) +void ValidationProgressDialog::onHide() +{ + hide(); +} + +void ValidationProgressDialog::onCancel() { - if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - hide(); - } else { - qCritical() << "validation dialog: unknown button pressed"; - } } void ValidationProgressDialog::onTimer() { - m_bar->setValue(m_elapsed.elapsed() / 1000); + ui->progress->setValue(m_validator.elapsed().elapsed() / 1000); } @@ -395,6 +388,7 @@ void NexusKeyValidator::start(const QString& key) m_active = true; setState(Connecting); + m_elapsed.start(); const QString requestUrl(NexusBaseUrl + "/users/validate"); QNetworkRequest request(requestUrl); @@ -437,6 +431,16 @@ bool NexusKeyValidator::isActive() const return m_active; } +QElapsedTimer NexusKeyValidator::elapsed() const +{ + return m_elapsed; +} + +std::chrono::seconds NexusKeyValidator::timeout() const +{ + return NXMAccessManager::ValidationTimeout; +} + void NexusKeyValidator::close() { m_active = false; @@ -563,11 +567,12 @@ void NexusKeyValidator::handleError( NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) - , m_ProgressDialog(new ValidationProgressDialog(ValidationTimeout)) , m_MOVersion(moVersion) , m_validator(*this) , m_validationState(NotChecked) { + m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_validator.stateChanged = [&](auto&& s, auto&& e){ onValidatorState(s, e); }; m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; @@ -582,7 +587,13 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) void NXMAccessManager::setTopLevelWidget(QWidget* w) { - m_ProgressDialog->setParentWidget(w); + if (w) { + m_ProgressDialog->setParentWidget(w); + } else { + const auto v = m_ProgressDialog->isVisible(); + m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_validator.cancel(); + } } QNetworkReply *NXMAccessManager::createRequest( @@ -641,8 +652,13 @@ void NXMAccessManager::onValidatorState( } m_ProgressDialog->stop(); - m_validationState = Invalid; - emit validateFailed(NexusKeyValidator::stateToString(s, e)); + + if (s == NexusKeyValidator::Cancelled) { + m_validationState = NotChecked; + } else { + m_validationState = Invalid; + emit validateFailed(NexusKeyValidator::stateToString(s, e)); + } } void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 0c85153b..2a4c066c 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -31,39 +31,9 @@ along with Mod Organizer. If not, see . #include namespace MOBase { class IPluginGame; } +namespace Ui { class ValidationProgressDialog; } class NXMAccessManager; -class ValidationProgressDialog : private QDialog -{ - Q_OBJECT; - -public: - ValidationProgressDialog(std::chrono::seconds timeout); - - void setParentWidget(QWidget* w); - - void start(); - void stop(); - - using QDialog::show; - -protected: - void showEvent(QShowEvent* e) override; - void closeEvent(QCloseEvent* e) override; - -private: - std::chrono::seconds m_timeout; - QProgressBar* m_bar; - QDialogButtonBox* m_buttons; - QTimer* m_timer; - QElapsedTimer m_elapsed; - bool m_first; - - void onButton(QAbstractButton* b); - void onTimer(); -}; - - class NexusSSOLogin { public: @@ -138,12 +108,15 @@ public: void cancel(); bool isActive() const; + QElapsedTimer elapsed() const; + std::chrono::seconds timeout() const; private: NXMAccessManager& m_manager; QNetworkReply* m_reply; QTimer m_timeout; bool m_active; + QElapsedTimer m_elapsed; void setState(States s, const QString& error={}); @@ -159,6 +132,34 @@ private: }; +class ValidationProgressDialog : public QDialog +{ + Q_OBJECT; + +public: + ValidationProgressDialog(const NexusKeyValidator& v); + + void setParentWidget(QWidget* w); + + void start(); + void stop(); + +protected: + void showEvent(QShowEvent* e) override; + void closeEvent(QCloseEvent* e) override; + +private: + std::unique_ptr ui; + const NexusKeyValidator& m_validator; + QTimer* m_updateTimer; + bool m_first; + + void onHide(); + void onCancel(); + void onTimer(); +}; + + /** * @brief access manager extended to handle nxm links **/ @@ -224,7 +225,7 @@ private: }; QWidget* m_TopLevel; - mutable ValidationProgressDialog* m_ProgressDialog; + mutable std::unique_ptr m_ProgressDialog; QString m_MOVersion; NexusKeyValidator m_validator; States m_validationState; diff --git a/src/validationprogressdialog.ui b/src/validationprogressdialog.ui new file mode 100644 index 00000000..1c0cbcb8 --- /dev/null +++ b/src/validationprogressdialog.ui @@ -0,0 +1,117 @@ + + + ValidationProgressDialog + + + + 0 + 0 + 305 + 93 + + + + Validating Nexus Connection + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Connecting to Nexus... + + + + + + + 24 + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Cancel + + + + + + + Qt::Horizontal + + + + 122 + 20 + + + + + + + + Hide + + + true + + + + + + + + + + + -- cgit v1.3.1 From fc909d4b2eaff02cb8b7f38ca5079dc9b12cef68 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 10:41:23 -0400 Subject: implemented cancel button on progress dialog fixed crash when exiting MO because the dialog is destroyed in atexit(), way after Qt is gone --- src/nxmaccessmanager.cpp | 32 +++++++++++++++++++++++--------- src/nxmaccessmanager.h | 10 ++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 731feafb..036b22ff 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -49,7 +49,7 @@ const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); -ValidationProgressDialog::ValidationProgressDialog(const NexusKeyValidator& v) +ValidationProgressDialog::ValidationProgressDialog(NexusKeyValidator& v) : m_validator(v), m_updateTimer(nullptr), m_first(true) { ui.reset(new Ui::ValidationProgressDialog); @@ -118,6 +118,7 @@ void ValidationProgressDialog::onHide() void ValidationProgressDialog::onCancel() { + m_validator.cancel(); } void ValidationProgressDialog::onTimer() @@ -571,8 +572,6 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) , m_validator(*this) , m_validationState(NotChecked) { - m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); - m_validator.stateChanged = [&](auto&& s, auto&& e){ onValidatorState(s, e); }; m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; @@ -590,8 +589,7 @@ void NXMAccessManager::setTopLevelWidget(QWidget* w) if (w) { m_ProgressDialog->setParentWidget(w); } else { - const auto v = m_ProgressDialog->isVisible(); - m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + m_ProgressDialog.reset(); m_validator.cancel(); } } @@ -640,7 +638,7 @@ void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; m_validator.start(key); - m_ProgressDialog->start(); + startProgress(); } void NXMAccessManager::onValidatorState( @@ -651,7 +649,7 @@ void NXMAccessManager::onValidatorState( return; } - m_ProgressDialog->stop(); + stopProgress(); if (s == NexusKeyValidator::Cancelled) { m_validationState = NotChecked; @@ -663,7 +661,7 @@ void NXMAccessManager::onValidatorState( void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) { - m_ProgressDialog->stop(); + stopProgress(); m_validationState = Valid; emit credentialsReceived(user); @@ -673,7 +671,7 @@ void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) bool NXMAccessManager::validated() const { if (m_validator.isActive()) { - m_ProgressDialog->show(); + const_cast(this)->startProgress(); } return (m_validationState == Valid); @@ -739,3 +737,19 @@ void NXMAccessManager::clearApiKey() m_validator.cancel(); emit credentialsReceived(APIUserAccount()); } + +void NXMAccessManager::startProgress() +{ + if (!m_ProgressDialog) { + m_ProgressDialog.reset(new ValidationProgressDialog(m_validator)); + } + + m_ProgressDialog->start(); +} + +void NXMAccessManager::stopProgress() +{ + if (m_ProgressDialog) { + m_ProgressDialog->stop(); + } +} diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 2a4c066c..f0bdd8a6 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -137,7 +137,7 @@ class ValidationProgressDialog : public QDialog Q_OBJECT; public: - ValidationProgressDialog(const NexusKeyValidator& v); + ValidationProgressDialog(NexusKeyValidator& v); void setParentWidget(QWidget* w); @@ -150,7 +150,7 @@ protected: private: std::unique_ptr ui; - const NexusKeyValidator& m_validator; + NexusKeyValidator& m_validator; QTimer* m_updateTimer; bool m_first; @@ -169,8 +169,7 @@ class NXMAccessManager : public QNetworkAccessManager public: static const std::chrono::seconds ValidationTimeout; - explicit NXMAccessManager(QObject *parent, const QString &moVersion); - + NXMAccessManager(QObject *parent, const QString &moVersion); void setTopLevelWidget(QWidget* w); @@ -233,6 +232,9 @@ private: void startValidationCheck(const QString& key); void onValidatorState(NexusKeyValidator::States s, const QString& e); void onValidatorFinished(const APIUserAccount& user); + + void startProgress(); + void stopProgress(); }; #endif // NXMACCESSMANAGER_H -- cgit v1.3.1 From 8269ac1b489d30ca6065ec2b97542ddbb22298b3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 13:36:27 -0400 Subject: rework of the nexus key validator to allow multiple attempts before failing --- src/nxmaccessmanager.cpp | 495 ++++++++++++++++++++++++++++++-------------- src/nxmaccessmanager.h | 100 ++++++--- src/settingsdialognexus.cpp | 45 ++-- src/settingsdialognexus.h | 5 +- 4 files changed, 428 insertions(+), 217 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 036b22ff..99b93048 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -44,7 +44,6 @@ using namespace MOBase; using namespace std::chrono_literals; const QString NexusBaseUrl("https://api.nexusmods.com/v1"); -const std::chrono::seconds NXMAccessManager::ValidationTimeout = 10s; const QString NexusSSO("wss://sso.nexusmods.com"); const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2"); @@ -69,6 +68,7 @@ void ValidationProgressDialog::setParentWidget(QWidget* w) if (w && wasVisible) { setVisible(true); + raise(); } } @@ -80,9 +80,7 @@ void ValidationProgressDialog::start() m_updateTimer->setInterval(100ms); } - ui->progress->setRange(0, m_validator.timeout().count()); - ui->progress->setValue(0); - + updateProgress(); m_updateTimer->start(); show(); @@ -123,14 +121,35 @@ void ValidationProgressDialog::onCancel() void ValidationProgressDialog::onTimer() { - ui->progress->setValue(m_validator.elapsed().elapsed() / 1000); + updateProgress(); +} + +void ValidationProgressDialog::updateProgress() +{ + const auto* current = m_validator.currentAttempt(); + + if (current) { + ui->progress->setRange(0, current->timeout().count()); + ui->progress->setValue(current->elapsed().elapsed() / 1000); + } else { + // indeterminate + ui->progress->setRange(0, 0); + } + + if (const auto* a=m_validator.lastAttempt()) { + ui->label->setText(a->message() + ". " + tr("Trying again...")); + } else if (current) { + ui->label->setText(tr("Connecting to Nexus...")); + } else { + ui->label->setText("?"); + } } NexusSSOLogin::NexusSSOLogin() : m_keyReceived(false), m_active(false) { - m_timeout.setInterval(NXMAccessManager::ValidationTimeout); + m_timeout.setInterval(10s); m_timeout.setSingleShot(true); QObject::connect( @@ -167,20 +186,22 @@ QString NexusSSOLogin::stateToString(States s, const QString& e) return QObject::tr("Waiting for Nexus..."); case WaitingForBrowser: - return QObject::tr( - "Opened Nexus in browser.\n" - "Switch to your browser and accept the request."); + return + QObject::tr("Opened Nexus in browser.") + "\n" + + QObject::tr("Switch to your browser and accept the request."); case Finished: return QObject::tr("Finished."); case Timeout: - return QObject::tr( - "No answer from Nexus.\n" - "A firewall might be blocking Mod Organizer."); + return + QObject::tr("No answer from Nexus.") + "\n" + + QObject::tr("A firewall might be blocking Mod Organizer."); case ClosedByRemote: - return QObject::tr("Nexus closed the connection."); + return + QObject::tr("Nexus closed the connection.") + "\n" + + QObject::tr("A firewall might be blocking Mod Organizer."); case Cancelled: return QObject::tr("Cancelled."); @@ -300,10 +321,11 @@ void NexusSSOLogin::onMessage(const QString& s) void NexusSSOLogin::onDisconnected() { if (m_active) { - m_active = false; - if (!m_keyReceived) { + close(); setState(ClosedByRemote); + } else { + m_active = false; } } } @@ -332,84 +354,48 @@ void NexusSSOLogin::onTimeout() } -NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) - : m_manager(am), m_reply(nullptr), m_active(false) +ValidationAttempt::ValidationAttempt(std::chrono::seconds timeout) + : m_reply(nullptr), m_result(None) { - m_timeout.setInterval(NXMAccessManager::ValidationTimeout); m_timeout.setSingleShot(true); + m_timeout.setInterval(timeout); QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); }); } -NexusKeyValidator::~NexusKeyValidator() -{ - abort(); -} - -QString NexusKeyValidator::stateToString(States s, const QString& e) -{ - switch (s) - { - case NexusKeyValidator::Connecting: - return QObject::tr("Connecting to Nexus..."); - - case NexusKeyValidator::Finished: - return QObject::tr("Finished."); - - case NexusKeyValidator::InvalidJson: - return QObject::tr("Invalid JSON"); - - case NexusKeyValidator::BadResponse: - return QObject::tr("Bad response"); - - case NexusKeyValidator::Timeout: - return QObject::tr("There was a timeout during the request"); - - case NexusKeyValidator::Cancelled: - return QObject::tr("Cancelled"); - - case NexusKeyValidator::Error: // fall-through - default: - { - if (e.isEmpty()) { - return QString("%1").arg(s); - } else { - return e; - } - } - } -} - -void NexusKeyValidator::start(const QString& key) +void ValidationAttempt::start(NXMAccessManager& m, const QString& key) { - if (m_reply) { - abort(); + if (!sendRequest(m, key)) { return; } - m_active = true; - setState(Connecting); m_elapsed.start(); + m_timeout.start(); + + log::debug( + "validator: attempt started with timeout of {} seconds", timeout().count()); +} +bool ValidationAttempt::sendRequest( + NXMAccessManager& m, const QString& key) +{ const QString requestUrl(NexusBaseUrl + "/users/validate"); QNetworkRequest request(requestUrl); request.setRawHeader("APIKEY", key.toUtf8()); - request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m_manager.userAgent().toUtf8()); + request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, m.userAgent().toUtf8()); request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); request.setRawHeader("Protocol-Version", "1.0.0"); request.setRawHeader("Application-Name", "MO2"); - request.setRawHeader("Application-Version", m_manager.MOVersion().toUtf8()); + request.setRawHeader("Application-Version", m.MOVersion().toUtf8()); + + m_reply = m.get(request); - m_reply = m_manager.get(request); if (!m_reply) { - close(); - setState(Error, QObject::tr("Failed to request %1").arg(requestUrl)); - return; + setFailure(SoftError, QObject::tr("Failed to request %1").arg(requestUrl)); + return false; } - m_timeout.start(NXMAccessManager::ValidationTimeout); - QObject::connect( m_reply, &QNetworkReply::finished, [&]{ onFinished(); }); @@ -417,93 +403,114 @@ void NexusKeyValidator::start(const QString& key) QObject::connect( m_reply, &QNetworkReply::sslErrors, [&](auto&& errors){ onSslErrors(errors); }); + + return true; } -void NexusKeyValidator::cancel() +void ValidationAttempt::cancel() { - if (m_active) { - abort(); - setState(Cancelled); + if (!m_reply || m_result != None) { + // not running + return; } + + setFailure(Cancelled, QObject::tr("Cancelled")); + + if (m_reply) { + m_reply->abort(); + } + + cleanup(); } -bool NexusKeyValidator::isActive() const +bool ValidationAttempt::done() const { - return m_active; + return (m_result != None); } -QElapsedTimer NexusKeyValidator::elapsed() const +ValidationAttempt::Result ValidationAttempt::result() const { - return m_elapsed; + return m_result; } -std::chrono::seconds NexusKeyValidator::timeout() const +const QString& ValidationAttempt::message() const { - return NXMAccessManager::ValidationTimeout; + return m_message; } -void NexusKeyValidator::close() +std::chrono::seconds ValidationAttempt::timeout() const { - m_active = false; - m_timeout.stop(); - - if (m_reply) { - m_reply->disconnect(); - m_reply->deleteLater(); - m_reply = nullptr; - } + return std::chrono::duration_cast( + m_timeout.intervalAsDuration()); } -void NexusKeyValidator::abort() +QElapsedTimer ValidationAttempt::elapsed() const { - m_active = false; - m_timeout.stop(); - - if (m_reply) { - m_reply->disconnect(); - m_reply->abort(); - m_reply->deleteLater(); - m_reply = nullptr; - } + return m_elapsed; } -void NexusKeyValidator::setState(States s, const QString& error) +void ValidationAttempt::onFinished() { - if (stateChanged) { - stateChanged(s, error); + if (m_result == Cancelled) { + return; } -} -void NexusKeyValidator::onFinished() -{ + log::debug("validator attempt: request has finished"); + if (!m_reply) { // shouldn't happen + log::error("validator attempt: reply is null"); + setFailure(HardError, QObject::tr("Internal error")); return; } - m_timeout.stop(); + const auto code = m_reply->attribute( + QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (code == 0) { + // request wasn't even sent + log::error("validator attempt: code is 0"); + setFailure(SoftError, m_reply->errorString()); + return; + } - const auto code = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); const auto doc = QJsonDocument::fromJson(m_reply->readAll()); const auto headers = m_reply->rawHeaderPairs(); - const auto error = m_reply->errorString(); - - close(); + const auto httpError = m_reply->errorString(); const QJsonObject data = doc.object(); if (code != 200) { - handleError(code, data.value("message").toString(), error); + // http request failed + + QString s = m_reply->errorString(); + + const auto nexusMessage = data.value("message").toString(); + if (!nexusMessage.isEmpty()) { + if (!s.isEmpty()) { + s += ", "; + } + + s += nexusMessage; + } + + if (s.isEmpty()) { + s = QObject::tr("HTTP code %1").arg(code); + } else { + s += QString(" (%1)").arg(code); + } + + setFailure(SoftError, s); return; } if (doc.isNull()) { - setState(InvalidJson); + setFailure(HardError, QObject::tr("Invalid JSON")); return; } if (!data.contains("user_id")) { - setState(BadResponse); + setFailure(HardError, QObject::tr("Bad response")); return; } @@ -512,6 +519,11 @@ void NexusKeyValidator::onFinished() const QString name = data.value("name").toString(); const bool premium = data.value("is_premium").toBool(); + if (key.isEmpty()) { + setFailure(HardError, QObject::tr("API key is empty")); + return; + } + const auto user = APIUserAccount() .apiKey(key) .id(QString("%1").arg(id)) @@ -519,51 +531,218 @@ void NexusKeyValidator::onFinished() .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) .limits(NexusInterface::parseLimits(headers)); - if (finished) { - setState(Finished); - finished(user); + setSuccess(user); +} + +void ValidationAttempt::onSslErrors(const QList& errors) +{ + log::error("validator attempt: ssl errors"); + + for (auto& e : errors) { + log::error(" . {}", e.errorString()); } + + setFailure(HardError, QObject::tr("SSL error")); } -void NexusKeyValidator::onSslErrors(const QList& errors) +void ValidationAttempt::onTimeout() { - if (m_active) { - for (const auto& e : errors) { - setState(Error, e.errorString()); - } + setFailure(SoftError, QObject::tr("Timed out")); +} + +void ValidationAttempt::setFailure(Result r, const QString& error) +{ + if (r != Cancelled) { + // don't spam the log + log::error("validator attempt: {}", error); + } + + cleanup(); + + m_result = r; + m_message = error; + + if (failure) { + failure(); } } -void NexusKeyValidator::onTimeout() +void ValidationAttempt::setSuccess(const APIUserAccount& user) +{ + log::debug("validator attempt successful"); + cleanup(); + + m_result = Success; + m_message = ""; + + if (success) { + success(user); + } +} + +void ValidationAttempt::cleanup() +{ + m_timeout.stop(); + + if (m_reply) { + m_reply->disconnect(); + m_reply->deleteLater(); + m_reply = nullptr; + } +} + + +NexusKeyValidator::NexusKeyValidator(NXMAccessManager& am) + : m_manager(am) { - abort(); - setState(Timeout); } -void NexusKeyValidator::handleError( - int code, const QString& nexusMessage, const QString& httpError) +NexusKeyValidator::~NexusKeyValidator() { - QString s = httpError; + cancel(); +} - if (!nexusMessage.isEmpty()) { - if (!s.isEmpty()) { - s += ", "; +void NexusKeyValidator::start(const QString& key, Behaviour b) +{ + if (isActive()) { + log::debug("validator: trying to start while ongoing; ignoring"); + return; + } + + m_key = key; + + switch (b) + { + case OneShot: + { + createAttempts({10s}); + break; } - s += nexusMessage; + case Retry: + { + createAttempts({5s, 5s, 10s}); + break; + } } - if (code != 0) { - if (s.isEmpty()) { - s = QString("HTTP code %1").arg(code); + nextTry(); +} + +void NexusKeyValidator::createAttempts( + const std::vector& timeouts) +{ + m_attempts.clear(); + + for (auto&& t : timeouts) { + m_attempts.push_back(std::make_unique(t)); + } +} + +void NexusKeyValidator::cancel() +{ + log::debug("validator: cancelled"); + + for (auto&& a : m_attempts) { + a->cancel(); + } +} + +bool NexusKeyValidator::isActive() const +{ + for (auto&& a : m_attempts) { + if (!a->done()) { + return true; + } + } + + return false; +} + +const ValidationAttempt* NexusKeyValidator::lastAttempt() const +{ + const ValidationAttempt* last = nullptr; + + for (auto&& a : m_attempts) { + if (a->done()) { + last = a.get(); } else { - s += QString(" (%1)").arg(code); + break; } } - setState(Error, s); + return last; } +const ValidationAttempt* NexusKeyValidator::currentAttempt() const +{ + for (auto&& a : m_attempts) { + if (!a->done()) { + return a.get(); + } + } + + return nullptr; +} + +bool NexusKeyValidator::nextTry() +{ + for (auto&& a : m_attempts) { + if (!a->done()) { + a->success = [&](auto&& user){ onAttemptSuccess(*a, user); }; + a->failure = [&]{ onAttemptFailure(*a); }; + + a->start(m_manager, m_key); + return true; + } + } + + // no more + return false; +} + +void NexusKeyValidator::onAttemptSuccess( + const ValidationAttempt&, const APIUserAccount& u) +{ + setFinished(ValidationAttempt::Success, "", u); +} + +void NexusKeyValidator::onAttemptFailure(const ValidationAttempt& a) +{ + switch (a.result()) + { + case ValidationAttempt::SoftError: + { + if (!nextTry()) { + setFinished(a.result(), a.message(), {}); + } + + break; + } + + case ValidationAttempt::HardError: + { + cancel(); + setFinished(a.result(), a.message(), {}); + break; + } + + case ValidationAttempt::Cancelled: + { + setFinished(ValidationAttempt::Cancelled, QObject::tr("Cancelled"), {}); + break; + } + } +} + +void NexusKeyValidator::setFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user) +{ + if (finished) { + finished(r, message, user); + } +} NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) @@ -572,8 +751,9 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) , m_validator(*this) , m_validationState(NotChecked) { - m_validator.stateChanged = [&](auto&& s, auto&& e){ onValidatorState(s, e); }; - m_validator.finished = [&](auto&& user){ onValidatorFinished(user); }; + m_validator.finished = [&](auto&& r, auto&& m, auto&& u) { + onValidatorFinished(r, m, u); + }; setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( Settings::instance().paths().cache() + "/nexus_cookies.dat"))); @@ -587,7 +767,9 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) void NXMAccessManager::setTopLevelWidget(QWidget* w) { if (w) { - m_ProgressDialog->setParentWidget(w); + if (m_ProgressDialog) { + m_ProgressDialog->setParentWidget(w); + } } else { m_ProgressDialog.reset(); m_validator.cancel(); @@ -637,37 +819,30 @@ void NXMAccessManager::clearCookies() void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; - m_validator.start(key); + m_validator.start(key, NexusKeyValidator::Retry); startProgress(); } -void NXMAccessManager::onValidatorState( - NexusKeyValidator::States s, const QString& e) +void NXMAccessManager::onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user) { - if (s == NexusKeyValidator::Connecting || s == NexusKeyValidator::Finished) { - // no-op, success is handled in onValidatorFinished() - return; - } - stopProgress(); - if (s == NexusKeyValidator::Cancelled) { - m_validationState = NotChecked; + if (user) { + m_validationState = Valid; + emit credentialsReceived(*user); + emit validateSuccessful(true); } else { - m_validationState = Invalid; - emit validateFailed(NexusKeyValidator::stateToString(s, e)); + if (r == ValidationAttempt::Cancelled) { + m_validationState = NotChecked; + } else { + m_validationState = Invalid; + emit validateFailed(message); + } } } -void NXMAccessManager::onValidatorFinished(const APIUserAccount& user) -{ - stopProgress(); - - m_validationState = Valid; - emit credentialsReceived(user); - emit validateSuccessful(true); -} - bool NXMAccessManager::validated() const { if (m_validator.isActive()) { diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index f0bdd8a6..b0ea45e7 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -82,53 +82,93 @@ private: }; -class NexusKeyValidator +class ValidationAttempt { public: - enum States + enum Result { - Connecting, - Finished, - InvalidJson, - BadResponse, - Timeout, - Cancelled, - Error + None, + Success, + SoftError, + HardError, + Cancelled }; - std::function finished; - std::function stateChanged; - - static QString stateToString(States s, const QString& e); + std::function success; + std::function failure; - NexusKeyValidator(NXMAccessManager& am); - ~NexusKeyValidator(); + ValidationAttempt(std::chrono::seconds timeout); + ValidationAttempt(const ValidationAttempt&) = delete; + ValidationAttempt& operator=(const ValidationAttempt&) = delete; - void start(const QString& key); + void start(NXMAccessManager& m, const QString& key); void cancel(); - bool isActive() const; - QElapsedTimer elapsed() const; + bool done() const; + Result result() const; + const QString& message() const; std::chrono::seconds timeout() const; + QElapsedTimer elapsed() const; private: - NXMAccessManager& m_manager; QNetworkReply* m_reply; + Result m_result; + QString m_message; QTimer m_timeout; - bool m_active; QElapsedTimer m_elapsed; - void setState(States s, const QString& error={}); - - void close(); - void abort(); + bool sendRequest(NXMAccessManager& m, const QString& key); void onFinished(); void onSslErrors(const QList& errors); void onTimeout(); - void handleError( - int code, const QString& nexusMessage, const QString& httpError); + void setFailure(Result r, const QString& error); + void setSuccess(const APIUserAccount& user); + + void cleanup(); +}; + + +class NexusKeyValidator +{ +public: + enum Behaviour + { + OneShot = 0, + Retry + }; + + using FinishedCallback = void ( + ValidationAttempt::Result, const QString&, + std::optional); + + std::function finished; + + NexusKeyValidator(NXMAccessManager& am); + ~NexusKeyValidator(); + + void start(const QString& key, Behaviour b); + void cancel(); + + bool isActive() const; + const ValidationAttempt* lastAttempt() const; + const ValidationAttempt* currentAttempt() const; + +private: + NXMAccessManager& m_manager; + QString m_key; + std::vector> m_attempts; + + void createAttempts(const std::vector& timeouts); + + bool nextTry(); + void onAttemptSuccess(const ValidationAttempt& a, const APIUserAccount& u); + void onAttemptFailure(const ValidationAttempt& a); + + void setFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user); }; @@ -157,6 +197,7 @@ private: void onHide(); void onCancel(); void onTimer(); + void updateProgress(); }; @@ -167,8 +208,6 @@ class NXMAccessManager : public QNetworkAccessManager { Q_OBJECT public: - static const std::chrono::seconds ValidationTimeout; - NXMAccessManager(QObject *parent, const QString &moVersion); void setTopLevelWidget(QWidget* w); @@ -230,8 +269,9 @@ private: States m_validationState; void startValidationCheck(const QString& key); - void onValidatorState(NexusKeyValidator::States s, const QString& e); - void onValidatorFinished(const APIUserAccount& user); + void onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional); void startProgress(); void stopProgress(); diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2021bdc1..209ed661 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -240,17 +240,13 @@ void NexusSettingsTab::validateKey(const QString& key) m_nexusValidator.reset(new NexusKeyValidator( *NexusInterface::instance(dialog().pluginContainer())->getAccessManager())); - m_nexusValidator->stateChanged = [&](auto&& s, auto&& e){ - onValidatorStateChanged(s, e); - }; - - m_nexusValidator->finished = [&](auto&& user) { - onValidatorFinished(user); + m_nexusValidator->finished = [&](auto&& r, auto&& m, auto&& u) { + onValidatorFinished(r, m, u); }; } addNexusLog(QObject::tr("Checking API key...")); - m_nexusValidator->start(key); + m_nexusValidator->start(key, NexusKeyValidator::OneShot); } void NexusSettingsTab::onSSOKeyChanged(const QString& key) @@ -277,32 +273,31 @@ void NexusSettingsTab::onSSOStateChanged(NexusSSOLogin::States s, const QString& updateNexusState(); } -void NexusSettingsTab::onValidatorStateChanged( - NexusKeyValidator::States s, const QString& e) +void NexusSettingsTab::onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional user) { - if (s != NexusKeyValidator::Finished) { - // finished state is handled in onValidatorFinished() - const auto log = NexusKeyValidator::stateToString(s, e); + if (user) { + NexusInterface::instance(dialog().pluginContainer())->setUserAccount(*user); + addNexusLog(QObject::tr("Received user acount information")); - for (auto&& line : log.split("\n")) { - addNexusLog(line); + if (setKey(user->apiKey())) { + addNexusLog(QObject::tr("Linked with Nexus successfully.")); + } else { + addNexusLog(QObject::tr("Failed to set API key")); + } + } else { + if (message.isEmpty()) { + // shouldn't happen + addNexusLog("Unknown error"); + } else { + addNexusLog(message); } } updateNexusState(); } -void NexusSettingsTab::onValidatorFinished(const APIUserAccount& user) -{ - NexusInterface::instance(dialog().pluginContainer())->setUserAccount(user); - - if (!user.apiKey().isEmpty()) { - if (setKey(user.apiKey())) { - addNexusLog(QObject::tr("Linked with Nexus successfully.")); - } - } -} - void NexusSettingsTab::addNexusLog(const QString& s) { ui->nexusLog->addItem(s); diff --git a/src/settingsdialognexus.h b/src/settingsdialognexus.h index 89a6618f..2cb1cc1e 100644 --- a/src/settingsdialognexus.h +++ b/src/settingsdialognexus.h @@ -32,8 +32,9 @@ private: void onSSOKeyChanged(const QString& key); void onSSOStateChanged(NexusSSOLogin::States s, const QString& e); - void onValidatorStateChanged(NexusKeyValidator::States s, const QString& e); - void onValidatorFinished(const APIUserAccount& user); + void onValidatorFinished( + ValidationAttempt::Result r, const QString& message, + std::optional useR); void addNexusLog(const QString& s); }; -- cgit v1.3.1 From e7cc4774e1a7d03fb0710875f6a91ec703b0005e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 13:47:11 -0400 Subject: don't show the progress dialog on startup until after the first failure --- src/nxmaccessmanager.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++-- src/nxmaccessmanager.h | 4 ++++ 2 files changed, 47 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 99b93048..5d8b8b9e 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -702,13 +702,21 @@ bool NexusKeyValidator::nextTry() } void NexusKeyValidator::onAttemptSuccess( - const ValidationAttempt&, const APIUserAccount& u) + const ValidationAttempt& a, const APIUserAccount& u) { + if (attemptFinished) { + attemptFinished(a); + } + setFinished(ValidationAttempt::Success, "", u); } void NexusKeyValidator::onAttemptFailure(const ValidationAttempt& a) { + if (attemptFinished) { + attemptFinished(a); + } + switch (a.result()) { case ValidationAttempt::SoftError: @@ -755,6 +763,10 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) onValidatorFinished(r, m, u); }; + m_validator.attemptFinished = [&](auto&& a) { + onValidatorAttemptFinished(a); + }; + setCookieJar(new PersistentCookieJar(QDir::fromNativeSeparators( Settings::instance().paths().cache() + "/nexus_cookies.dat"))); @@ -820,7 +832,12 @@ void NXMAccessManager::startValidationCheck(const QString& key) { m_validationState = NotChecked; m_validator.start(key, NexusKeyValidator::Retry); - startProgress(); + + if (m_ProgressDialog) { + // don't show the progress dialog on startup for the first attempt; the + // dialog will be shown in onValidatorAttemptFinished() if it failed + startProgress(); + } } void NXMAccessManager::onValidatorFinished( @@ -843,6 +860,30 @@ void NXMAccessManager::onValidatorFinished( } } +void NXMAccessManager::onValidatorAttemptFinished(const ValidationAttempt& a) +{ + if (!m_ProgressDialog) { + switch (a.result()) + { + case ValidationAttempt::SoftError: + case ValidationAttempt::HardError: + { + startProgress(); + break; + } + + case ValidationAttempt::None: + case ValidationAttempt::Success: + case ValidationAttempt::Cancelled: + default: + { + // don't show the dialog + break; + } + } + } +} + bool NXMAccessManager::validated() const { if (m_validator.isActive()) { diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index b0ea45e7..6a45d880 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -144,6 +144,7 @@ public: std::optional); std::function finished; + std::function attemptFinished; NexusKeyValidator(NXMAccessManager& am); ~NexusKeyValidator(); @@ -269,10 +270,13 @@ private: States m_validationState; void startValidationCheck(const QString& key); + void onValidatorFinished( ValidationAttempt::Result r, const QString& message, std::optional); + void onValidatorAttemptFinished(const ValidationAttempt& a); + void startProgress(); void stopProgress(); }; -- cgit v1.3.1 From 925dbcba875477e1111453878f9b69e069bc8fd2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 13:52:04 -0400 Subject: at least put one message in the list when opening the dialog to avoid having an empty widget --- src/settingsdialognexus.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 209ed661..2dd8b998 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -112,6 +112,12 @@ NexusSettingsTab::NexusSettingsTab(Settings& s, SettingsDialog& d) QObject::connect(ui->clearCacheButton, &QPushButton::clicked, [&]{ on_clearCacheButton_clicked(); }); QObject::connect(ui->associateButton, &QPushButton::clicked, [&]{ on_associateButton_clicked(); }); + if (settings().nexus().hasApiKey()) { + addNexusLog(QObject::tr("Connected.")); + } else { + addNexusLog(QObject::tr("Not connected.")); + } + updateNexusState(); } -- cgit v1.3.1 From c28d435ed41f9e693a0d7f09482a86b963fae6ff Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 14:17:04 -0400 Subject: showEvent() is called for stuff like minimizing and restoring the window, don't re-read settings for that --- src/mainwindow.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3b977dd6..76109e00 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1208,13 +1208,12 @@ void MainWindow::hookUpWindowTutorials() void MainWindow::showEvent(QShowEvent *event) { - readSettings(m_OrganizerCore.settings()); - - refreshFilters(); - QMainWindow::showEvent(event); if (!m_WasVisible) { + readSettings(m_OrganizerCore.settings()); + refreshFilters(); + // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which // connects its signal in runApplication() (in main.cpp), and that happens -- cgit v1.3.1 From ef993a693c9ca5acf60daac3506411ef6b81bb89 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 5 Oct 2019 14:36:07 -0400 Subject: stop reusing the view menu, just create a new context menu with the right stuff in it --- src/mainwindow.cpp | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3b977dd6..c6f2ee5d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -787,15 +787,6 @@ void MainWindow::updatePinnedExecutables() void MainWindow::updateToolbarMenu() { - // well, this is a bit of a hack to allow the same toolbar menu to be shown - // in both the main menu and the context menu - // - // the toolbar menu is returned by createPopupMenu(), but Qt takes ownership - // of it and deletes it by setting the WA_DeleteOnClose attribute on it - // - // to avoid deleting the menu, the attribute is removed here - ui->menuToolbars->setAttribute(Qt::WA_DeleteOnClose, false); - ui->actionMainMenuToggle->setChecked(ui->menuBar->isVisible()); ui->actionToolBarMainToggle->setChecked(ui->toolBar->isVisible()); ui->actionStatusBarToggle->setChecked(ui->statusBar->isVisible()); @@ -816,7 +807,23 @@ void MainWindow::updateViewMenu() QMenu* MainWindow::createPopupMenu() { - return ui->menuToolbars; + auto* m = new QMenu; + + // add all the actions from the toolbars menu + for (auto* a : ui->menuToolbars->actions()) { + m->addAction(a); + } + + m->addSeparator(); + + // other actions + m->addAction(ui->actionViewLog); + + // make sure the actions are updated + updateToolbarMenu(); + updateViewMenu(); + + return m; } void MainWindow::on_actionMainMenuToggle_triggered() @@ -900,8 +907,7 @@ void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) return; } - auto* m = createPopupMenu(); - m->exec(ui->centralWidget->mapToGlobal(pos)); + createPopupMenu()->exec(ui->centralWidget->mapToGlobal(pos)); } void MainWindow::scheduleUpdateButton() -- cgit v1.3.1 From c9e8e71b9344f492974363bff769089616bfaa64 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 02:23:28 -0400 Subject: removed some ambiguous overloads for settings --- src/profile.cpp | 66 ++++++++++++++++++++++++++++----------------------------- src/profile.h | 11 ++++------ 2 files changed, 37 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/profile.cpp b/src/profile.cpp index f2360674..f041a241 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -157,42 +157,42 @@ Profile::~Profile() void Profile::findProfileSettings() { - if (setting("LocalSaves") == QVariant()) { + if (setting("", "LocalSaves") == QVariant()) { if (m_Directory.exists("saves")) { - storeSetting("LocalSaves", true); + storeSetting("", "LocalSaves", true); } else { if (m_Directory.exists("_saves")) { m_Directory.rename("_saves", "saves"); } - storeSetting("LocalSaves", false); + storeSetting("", "LocalSaves", false); } } - if (setting("LocalSettings") == QVariant()) { + if (setting("", "LocalSettings") == QVariant()) { QString backupFile = getIniFileName() + "_"; if (m_Directory.exists(backupFile)) { - storeSetting("LocalSettings", false); + storeSetting("", "LocalSettings", false); m_Directory.rename(backupFile, getIniFileName()); } else { - storeSetting("LocalSettings", true); + storeSetting("", "LocalSettings", true); } } - if (setting("AutomaticArchiveInvalidation") == QVariant()) { + if (setting("", "AutomaticArchiveInvalidation") == QVariant()) { BSAInvalidation *invalidation = m_GamePlugin->feature(); DataArchives *dataArchives = m_GamePlugin->feature(); bool found = false; if ((invalidation != nullptr) && (dataArchives != nullptr)) { for (const QString &archive : dataArchives->archives(this)) { if (invalidation->isInvalidationBSA(archive)) { - storeSetting("AutomaticArchiveInvalidation", true); + storeSetting("", "AutomaticArchiveInvalidation", true); found = true; break; } } } if (!found) { - storeSetting("AutomaticArchiveInvalidation", false); + storeSetting("", "AutomaticArchiveInvalidation", false); } } } @@ -742,7 +742,7 @@ bool Profile::invalidationActive(bool *supported) const *supported = ((invalidation != nullptr) && (dataArchives != nullptr)); } - return setting("AutomaticArchiveInvalidation", false).toBool(); + return setting("", "AutomaticArchiveInvalidation", false).toBool(); } @@ -754,7 +754,7 @@ void Profile::deactivateInvalidation() invalidation->deactivate(this); } - storeSetting("AutomaticArchiveInvalidation", false); + storeSetting("", "AutomaticArchiveInvalidation", false); } @@ -766,13 +766,13 @@ void Profile::activateInvalidation() invalidation->activate(this); } - storeSetting("AutomaticArchiveInvalidation", true); + storeSetting("", "AutomaticArchiveInvalidation", true); } bool Profile::localSavesEnabled() const { - return setting("LocalSaves", false).toBool(); + return setting("", "LocalSaves", false).toBool(); } @@ -797,14 +797,14 @@ bool Profile::enableLocalSaves(bool enable) return false; } } - storeSetting("LocalSaves", enable); + storeSetting("", "LocalSaves", enable); return true; } bool Profile::localSettingsEnabled() const { - bool enabled = setting("LocalSettings", false).toBool(); + bool enabled = setting("", "LocalSettings", false).toBool(); if (enabled) { QStringList missingFiles; for (QString file : m_GamePlugin->iniFiles()) { @@ -849,7 +849,7 @@ bool Profile::enableLocalSettings(bool enable) return false; } } - storeSetting("LocalSettings", enable); + storeSetting("", "LocalSettings", enable); return true; } @@ -911,36 +911,36 @@ void Profile::rename(const QString &newName) m_Directory.setPath(profileDir.absoluteFilePath(newName)); } -QVariant Profile::setting(const QString §ion, const QString &name, - const QVariant &fallback) const +QString keyName(const QString §ion, const QString &name) { - return m_Settings->value(section + "/" + name, fallback); + QString key = section; + + if (!name.isEmpty()) { + if (!key.isEmpty()) { + key += "/"; + } + + key += name; + } + + return key; } -QVariant Profile::setting(const QString &name, const QVariant &fallback) const +QVariant Profile::setting(const QString §ion, const QString &name, + const QVariant &fallback) const { - return m_Settings->value(name, fallback); + return m_Settings->value(keyName(section, name), fallback); } void Profile::storeSetting(const QString §ion, const QString &name, const QVariant &value) { - m_Settings->setValue(section + "/" + name, value); -} - -void Profile::storeSetting(const QString &name, const QVariant &value) -{ - storeSetting("", name, value); + m_Settings->setValue(keyName(section, name), value); } void Profile::removeSetting(const QString §ion, const QString &name) { - m_Settings->remove(section + "/" + name); -} - -void Profile::removeSetting(const QString &name) -{ - removeSetting("", name); + m_Settings->remove(keyName(section, name)); } QVariantMap Profile::settingsByGroup(const QString §ion) const diff --git a/src/profile.h b/src/profile.h index 85d929ac..d0bd4a84 100644 --- a/src/profile.h +++ b/src/profile.h @@ -315,15 +315,12 @@ public: QVariant setting( const QString §ion, const QString &name, - const QVariant &fallback) const; + const QVariant &fallback={}) const; - QVariant setting(const QString &name, const QVariant &fallback={}) const; + void storeSetting( + const QString §ion, const QString &name, const QVariant &value={}); - void storeSetting(const QString §ion, const QString &name, - const QVariant &value); - void storeSetting(const QString &name, const QVariant &value); - void removeSetting(const QString §ion, const QString &name = QString()); - void removeSetting(const QString &name); + void removeSetting(const QString §ion, const QString &name); QVariantMap settingsByGroup(const QString §ion) const; void storeSettingsByGroup(const QString §ion, const QVariantMap &values); -- cgit v1.3.1 From f10e91528b96a2588a321ef33fa65c527b816ccf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 03:26:20 -0400 Subject: remaining requests can be 0 if not logged in, would show incorrect throttling message validation dialog could pop even if logged in typos in comments --- src/apiuseraccount.cpp | 2 +- src/apiuseraccount.h | 6 +++--- src/nxmaccessmanager.cpp | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/apiuseraccount.cpp b/src/apiuseraccount.cpp index 35a868d5..976dd488 100644 --- a/src/apiuseraccount.cpp +++ b/src/apiuseraccount.cpp @@ -96,5 +96,5 @@ bool APIUserAccount::shouldThrottle() const bool APIUserAccount::exhausted() const { - return (remainingRequests() <= 0); + return isValid() && (remainingRequests() <= 0); } diff --git a/src/apiuseraccount.h b/src/apiuseraccount.h index ea4e8685..ac8931cf 100644 --- a/src/apiuseraccount.h +++ b/src/apiuseraccount.h @@ -51,12 +51,12 @@ struct APIStats /** -* represents a user account on the mod provier website +* represents a user account on the mod provider website */ class APIUserAccount { public: - // when the number of remanining requests is under this number, further + // when the number of remaining requests is under this number, further // requests will be throttled by avoiding non-critical ones static const int ThrottleThreshold = 200; @@ -110,7 +110,7 @@ public: APIUserAccount& name(const QString& name); /** - * sets the acount type + * sets the account type */ APIUserAccount& type(APIUserAccountTypes type); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 5d8b8b9e..e5f1fffe 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -886,11 +886,15 @@ void NXMAccessManager::onValidatorAttemptFinished(const ValidationAttempt& a) bool NXMAccessManager::validated() const { + if (m_validationState == Valid) { + return true; + } + if (m_validator.isActive()) { const_cast(this)->startProgress(); } - return (m_validationState == Valid); + return false; } void NXMAccessManager::refuseValidation() -- cgit v1.3.1 From 3d97b0efd04326c0252411fc6639c4c6df2f2160 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 04:27:53 -0400 Subject: added ExitModOrganizer(), used instead of qApp->exit() reset geometry uses TaskDialog moved restart code for the settings dialog to MainWindow fixed settings sometimes not being saved when restarting don't log "crash dumps present" every time the settings dialog is closed --- src/main.cpp | 12 +++- src/mainwindow.cpp | 113 +++++++++++++++++++++++++------------- src/mainwindow.h | 11 ++-- src/settingsdialog.cpp | 21 +++---- src/settingsdialog.h | 9 ++- src/settingsdialognexus.cpp | 4 +- src/settingsdialogworkarounds.cpp | 23 ++++---- src/shared/util.cpp | 47 ++++++++++++++++ src/shared/util.h | 18 ++++++ src/spawn.cpp | 12 ++-- 10 files changed, 189 insertions(+), 81 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index a6918d99..f08ba066 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -563,10 +563,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (game == nullptr) { InstanceManager &instance = InstanceManager::instance(); QString instanceName = instance.currentInstance(); + if (instanceName.compare("Portable", Qt::CaseInsensitive) != 0) { instance.clearCurrentInstance(); - return INT_MAX; + return RestartExitCode; } + return 1; } @@ -710,6 +712,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); res = application.exec(); + mainWindow.onBeforeClose(); + mainWindow.close(); NexusInterface::instance(&pluginContainer) ->getAccessManager()->setTopLevelWidget(nullptr); @@ -867,6 +871,7 @@ int main(int argc, char *argv[]) do { LogModel::instance().clear(); + ResetExitFlag(); // make sure the log file isn't locked in case MO was restarted and // the previous instance gets deleted @@ -904,10 +909,11 @@ int main(int argc, char *argv[]) splash = ":/MO/gui/splash"; } - int result = runApplication(application, instance, splash); - if (result != INT_MAX) { + const int result = runApplication(application, instance, splash); + if (result != RestartExitCode) { return result; } + argc = 1; moshortcut = MOShortcut(""); } while (true); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 295c60a6..23733cac 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1217,7 +1217,7 @@ void MainWindow::showEvent(QShowEvent *event) QMainWindow::showEvent(event); if (!m_WasVisible) { - readSettings(m_OrganizerCore.settings()); + readSettings(); refreshFilters(); // this needs to be connected here instead of in the constructor because the @@ -1267,26 +1267,44 @@ void MainWindow::showEvent(QShowEvent *event) } } +void MainWindow::onBeforeClose() +{ + storeSettings(); +} void MainWindow::closeEvent(QCloseEvent* event) { - if (!confirmExit()) { + // this happens for two reasons: + // 1) the user requested to close the window, such as clicking the X + // 2) close() is called in runApplication() after application.exec() + // returns, which happens when qApp->exit() is called + // + // the window must never actually close for 1), because settings haven't been + // saved yet: the state of many widgets is saved to the ini, which relies on + // the window still being onscreen (or else everything is considered hidden) + // + // for 2), the settings have been saved and the window can just close + + if (ModOrganizerExiting()) { + // the user has confirmed if necessary and all settings have been saved, + // just close it + QMainWindow::closeEvent(event); + } else { + // never close the window because settings might need to be changed event->ignore(); - return; - } - storeSettings(m_OrganizerCore.settings()); + // start the process of exiting, which may require confirmation by calling + // canExit(), among other things + ExitModOrganizer(); + } } -bool MainWindow::confirmExit() +bool MainWindow::canExit() { - m_closing = true; - if (m_OrganizerCore.downloadManager()->downloadsInProgressNoPause()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { - m_closing = false; return false; } else { m_OrganizerCore.downloadManager()->pauseAll(); @@ -1298,8 +1316,9 @@ bool MainWindow::confirmExit() HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); if (injected_process_still_running != INVALID_HANDLE_VALUE) { + m_exitAfterWait = true; m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_closing) { // if operation cancelled + if (!m_exitAfterWait) { // if operation cancelled return false; } } @@ -2138,20 +2157,22 @@ void MainWindow::activateProxy(bool activate) busyDialog.hide(); } -void MainWindow::readSettings(const Settings& settings) +void MainWindow::readSettings() { - settings.geometry().restoreGeometry(this); - settings.geometry().restoreState(this); - settings.geometry().restoreDocks(this); - settings.geometry().restoreToolbars(this); - settings.geometry().restoreState(ui->splitter); - settings.geometry().restoreState(ui->categoriesSplitter); - settings.geometry().restoreVisibility(ui->menuBar); - settings.geometry().restoreVisibility(ui->statusBar); + const auto& s = m_OrganizerCore.settings(); + + s.geometry().restoreGeometry(this); + s.geometry().restoreState(this); + s.geometry().restoreDocks(this); + s.geometry().restoreToolbars(this); + s.geometry().restoreState(ui->splitter); + s.geometry().restoreState(ui->categoriesSplitter); + s.geometry().restoreVisibility(ui->menuBar); + s.geometry().restoreVisibility(ui->statusBar); { // special case in case someone puts 0 in the INI - auto v = settings.widgets().index(ui->executablesListBox); + auto v = s.widgets().index(ui->executablesListBox); if (!v || v == 0) { v = 1; } @@ -2159,16 +2180,16 @@ void MainWindow::readSettings(const Settings& settings) ui->executablesListBox->setCurrentIndex(*v); } - settings.widgets().restoreIndex(ui->groupCombo); + s.widgets().restoreIndex(ui->groupCombo); { - settings.geometry().restoreVisibility(ui->categoriesGroup, false); + s.geometry().restoreVisibility(ui->categoriesGroup, false); const auto v = ui->categoriesGroup->isVisible(); setCategoryListVisible(v); ui->displayCategoriesBtn->setChecked(v); } - if (settings.network().useProxy()) { + if (s.network().useProxy()) { activateProxy(true); } } @@ -2215,8 +2236,10 @@ void MainWindow::processUpdates(Settings& settings) { } } -void MainWindow::storeSettings(Settings& s) +void MainWindow::storeSettings() { + auto& s = m_OrganizerCore.settings(); + s.geometry().saveState(this); s.geometry().saveGeometry(this); s.geometry().saveDocks(this); @@ -2244,7 +2267,7 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - if (m_closing) + if (m_exitAfterWait) m_LockDialog = new WaitingOnCloseDialog(this); else m_LockDialog = new LockedDialog(this, true); @@ -2265,8 +2288,8 @@ void MainWindow::unlock() } --m_LockCount; if (m_LockCount == 0) { - if (m_closing && m_LockDialog->canceled()) - m_closing = false; + if (m_exitAfterWait && m_LockDialog->canceled()) + m_exitAfterWait = false; m_LockDialog->hide(); m_LockDialog->deleteLater(); m_LockDialog = nullptr; @@ -5018,18 +5041,31 @@ void MainWindow::on_actionSettings_triggered() bool proxy = settings.network().useProxy(); DownloadManager *dlManager = m_OrganizerCore.downloadManager(); const bool oldCheckForUpdates = settings.checkForUpdates(); + const int oldMaxDumps = settings.diagnostics().crashDumpsMax(); SettingsDialog dialog(&m_PluginContainer, settings, this); dialog.exec(); + auto e = dialog.exitNeeded(); + if (oldManagedGameDirectory != settings.game().directory()) { - QMessageBox::about(this, tr("Restarting MO"), - tr("Changing the managed game directory requires restarting MO.\n" - "Any pending downloads will be paused.\n\n" - "Click OK to restart MO now.")); - dlManager->pauseAll(); - qApp->exit(INT_MAX); + e |= Exit::Restart; + } + + if (e.testFlag(Exit::Restart)) { + const auto r = MOBase::TaskDialog(this) + .title(tr("Restart Mod Organizer")) + .main("Restart Mod Organizer") + .content(tr("Mod Organizer must restart to finish configuration changes")) + .icon(QMessageBox::Question) + .button({tr("Restart"), QMessageBox::Yes}) + .button({tr("Continue"), tr("Some things might be weird."), QMessageBox::No}) + .exec(); + + if (r == QMessageBox::Yes) { + ExitModOrganizer(e); + } } InstallationManager *instManager = m_OrganizerCore.installationManager(); @@ -5092,7 +5128,10 @@ void MainWindow::on_actionSettings_triggered() updateDownloadView(); m_OrganizerCore.setLogLevel(settings.diagnostics().logLevel()); - m_OrganizerCore.cycleDiagnostics(); + + if (settings.diagnostics().crashDumpsMax() != oldMaxDumps) { + m_OrganizerCore.cycleDiagnostics(); + } toggleMO2EndorseState(); @@ -5475,9 +5514,7 @@ void MainWindow::on_actionUpdate_triggered() void MainWindow::on_actionExit_triggered() { - if (confirmExit()) { - qApp->exit(); - } + ExitModOrganizer(); } void MainWindow::actionEndorseMO() @@ -6121,7 +6158,7 @@ void MainWindow::on_actionChange_Game_triggered() if (r == QMessageBox::Yes) { InstanceManager::instance().clearCurrentInstance(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 524e2b6e..b04096be 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -111,7 +111,6 @@ class MainWindow : public QMainWindow, public IUserInterface friend class OrganizerProxy; public: - explicit MainWindow(Settings &settings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent = 0); @@ -154,7 +153,8 @@ public: void displayModInformation( ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; - bool confirmExit(); + bool canExit(); + void onBeforeClose(); virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); @@ -383,9 +383,8 @@ private: LockedDialogBase *m_LockDialog { nullptr }; uint64_t m_LockCount { 0 }; - bool m_closing{ false }; - bool m_showArchiveData{ true }; + bool m_exitAfterWait{ false }; MOBase::DelayedFileWriter m_ArchiveListWriter; @@ -672,8 +671,8 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); - void storeSettings(Settings& settings); - void readSettings(const Settings& settings); + void storeSettings(); + void readSettings(); void setupModList(); }; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 8fb25b1c..daccfba9 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -33,8 +33,8 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings& setti : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) , m_settings(settings) + , m_exit(Exit::None) , m_pluginContainer(pluginContainer) - , m_restartNeeded(false) { ui->setupUi(this); @@ -61,9 +61,14 @@ QWidget* SettingsDialog::parentWidgetForDialogs() } } -void SettingsDialog::setRestartNeeded() +void SettingsDialog::setExitNeeded(ExitFlags e) { - m_restartNeeded = true; + m_exit = e; +} + +ExitFlags SettingsDialog::exitNeeded() const +{ + return m_exit; } int SettingsDialog::exec() @@ -87,16 +92,6 @@ int SettingsDialog::exec() } } - if (m_restartNeeded) { - if (QMessageBox::question(parentWidgetForDialogs(), - tr("Restart Mod Organizer?"), - tr("In order to finish configuration changes, MO must be restarted.\n" - "Restart it now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - qApp->exit(INT_MAX); - } - } - return ret; } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index e89da665..bae4a469 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGSDIALOG_H #include "tutorabledialog.h" +#include "util.h" class PluginContainer; class Settings; @@ -74,7 +75,9 @@ public: PluginContainer* pluginContainer(); QWidget* parentWidgetForDialogs(); - void setRestartNeeded(); + + void setExitNeeded(ExitFlags e); + ExitFlags exitNeeded() const; int exec() override; @@ -82,10 +85,10 @@ public slots: virtual void accept(); private: + Ui::SettingsDialog* ui; Settings& m_settings; std::vector> m_tabs; - Ui::SettingsDialog* ui; - bool m_restartNeeded; + ExitFlags m_exit; PluginContainer* m_pluginContainer; }; diff --git a/src/settingsdialognexus.cpp b/src/settingsdialognexus.cpp index 2dd8b998..d49e0a33 100644 --- a/src/settingsdialognexus.cpp +++ b/src/settingsdialognexus.cpp @@ -312,7 +312,7 @@ void NexusSettingsTab::addNexusLog(const QString& s) bool NexusSettingsTab::setKey(const QString& key) { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const bool ret = settings().nexus().setApiKey(key); updateNexusState(); return ret; @@ -320,7 +320,7 @@ bool NexusSettingsTab::setKey(const QString& key) bool NexusSettingsTab::clearKey() { - dialog().setRestartNeeded(); + dialog().setExitNeeded(Exit::Restart); const auto ret = settings().nexus().clearApiKey(); NexusInterface::instance(dialog().pluginContainer())->getAccessManager()->clearApiKey(); diff --git a/src/settingsdialogworkarounds.cpp b/src/settingsdialogworkarounds.cpp index 0e31fc4b..1c5fbe26 100644 --- a/src/settingsdialogworkarounds.cpp +++ b/src/settingsdialogworkarounds.cpp @@ -2,6 +2,7 @@ #include "ui_settingsdialog.h" #include "spawn.h" #include "settings.h" +#include #include WorkaroundsSettingsTab::WorkaroundsSettingsTab(Settings& s, SettingsDialog& d) @@ -118,16 +119,18 @@ void WorkaroundsSettingsTab::on_bsaDateBtn_clicked() void WorkaroundsSettingsTab::on_resetGeometryBtn_clicked() { - const auto caption = QObject::tr("Restart Mod Organizer?"); - const auto text = QObject::tr( - "In order to reset the geometry, Mod Organizer must be restarted.\n" - "Restart now?"); - - const auto res = QMessageBox::question( - parentWidget(), caption, text, QMessageBox::Yes | QMessageBox::Cancel); - - if (res == QMessageBox::Yes) { + const auto r = MOBase::TaskDialog(parentWidget()) + .title(QObject::tr("Restart Mod Organizer")) + .main(QObject::tr("Restart Mod Organizer")) + .content(QObject::tr("Geometries will be reset to their default values.")) + .icon(QMessageBox::Question) + .button({QObject::tr("Restart Mod Organizer"), QMessageBox::Ok}) + .button({QObject::tr("Cancel"), QMessageBox::Cancel}) + .exec(); + + if (r == QMessageBox::Ok) { settings().geometry().requestReset(); - qApp->exit(INT_MAX); + ExitModOrganizer(Exit::Restart); + dialog().close(); } } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 07983e12..58f4eb2b 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" +#include "mainwindow.h" namespace MOShared { @@ -244,3 +245,49 @@ MOBase::VersionInfo createVersionInfo() } } // namespace MOShared + + +static bool g_exiting = false; + +MainWindow* findMainWindow() +{ + for (auto* tl : qApp->topLevelWidgets()) { + if (auto* mw=dynamic_cast(tl)) { + return mw; + } + } + + return nullptr; +} + +bool ExitModOrganizer(ExitFlags e) +{ + if (g_exiting) { + return true; + } + + if (!e.testFlag(Exit::Force)) { + if (auto* mw=findMainWindow()) { + if (!mw->canExit()) { + return false; + } + } + } + + g_exiting = true; + + const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); + qApp->exit(code); + + return true; +} + +bool ModOrganizerExiting() +{ + return g_exiting; +} + +void ResetExitFlag() +{ + g_exiting = false; +} diff --git a/src/shared/util.h b/src/shared/util.h index 7bae96f2..aea6f200 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -48,4 +48,22 @@ MOBase::VersionInfo createVersionInfo(); } // namespace MOShared + +enum class Exit +{ + None = 0x00, + Normal = 0x01, + Restart = 0x02, + Force = 0x04 +}; + +const int RestartExitCode = INT_MAX; + +using ExitFlags = QFlags; +Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); + +bool ExitModOrganizer(ExitFlags e=Exit::Normal); +bool ModOrganizerExiting(); +void ResetExitFlag(); + #endif // UTIL_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 079677f4..a34230b2 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -186,12 +186,12 @@ QMessageBox::StandardButton badSteamReg( .details(details) .icon(QMessageBox::Critical) .button({ - QObject::tr("Continue without starting Steam"), - QObject::tr("The program may fail to launch."), - QMessageBox::Yes}) + QObject::tr("Continue without starting Steam"), + QObject::tr("The program may fail to launch."), + QMessageBox::Yes}) .button({ - QObject::tr("Cancel"), - QMessageBox::Cancel}) + QObject::tr("Cancel"), + QMessageBox::Cancel}) .exec(); } @@ -517,7 +517,7 @@ bool restartAsAdmin(QWidget* parent) } log::debug("exiting MO"); - qApp->exit(0); + ExitModOrganizer(Exit::Force); return true; } -- cgit v1.3.1 From a082c029b25dcbf0877f318092eae925d177f223 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 07:04:56 -0400 Subject: added spawn delay setting, not exposed --- src/settings.cpp | 11 +++++++++++ src/settings.h | 3 +++ src/usvfsconnector.cpp | 11 +++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 15bc801a..5aeb82fe 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1997,3 +1997,14 @@ void DiagnosticsSettings::setCrashDumpsMax(int n) { set(m_Settings, "Settings", "crash_dumps_max", n); } + +std::chrono::seconds DiagnosticsSettings::spawnDelay() const +{ + return std::chrono::seconds( + get(m_Settings, "Settings", "spawn_delay", 0)); +} + +void DiagnosticsSettings::setSpawnDelay(std::chrono::seconds t) +{ + set(m_Settings, "Settings", "spawn_delay", t.count()); +} diff --git a/src/settings.h b/src/settings.h index 1556ba1e..d604823a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -629,6 +629,9 @@ public: int crashDumpsMax() const; void setCrashDumpsMax(int n); + std::chrono::seconds spawnDelay() const; + void setSpawnDelay(std::chrono::seconds t); + private: QSettings& m_Settings; }; diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 4315ed92..3ea811be 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -162,12 +162,15 @@ QString toString(CrashDumpsType t) UsvfsConnector::UsvfsConnector() { + const auto& s = Settings::instance(); + USVFSParameters params; - LogLevel level = toUsvfsLogLevel(Settings::instance().diagnostics().logLevel()); - CrashDumpsType dumpType = Settings::instance().diagnostics().crashDumpsType(); + const LogLevel level = toUsvfsLogLevel(s.diagnostics().logLevel()); + const CrashDumpsType dumpType = s.diagnostics().crashDumpsType(); + const auto delay = s.diagnostics().spawnDelay(); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); - USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); + USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str(), delay); InitLogging(false); log::debug( @@ -183,7 +186,7 @@ UsvfsConnector::UsvfsConnector() CreateVFS(¶ms); ClearExecutableBlacklist(); - for (auto exec : Settings::instance().executablesBlacklist().split(";")) { + for (auto exec : s.executablesBlacklist().split(";")) { std::wstring buf = exec.toStdWString(); BlacklistExecutable(buf.data()); } -- cgit v1.3.1 From b4f5c17898317720662fc15a7828b68b2e81d950 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 7 Oct 2019 07:41:13 -0400 Subject: disallow empty titles --- src/editexecutablesdialog.cpp | 27 +++++++++++++++++++++------ src/editexecutablesdialog.h | 5 +++++ 2 files changed, 26 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 0d6367b8..5c4ee149 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -222,6 +222,7 @@ void EditExecutablesDialog::updateUI( { // the ui is currently being set, ignore changes m_settingUI = true; + Guard g([&]{ m_settingUI = false; }); if (e) { setEdits(*e); @@ -230,9 +231,6 @@ void EditExecutablesDialog::updateUI( } setButtons(item, e); - - // any changes from now on are from the user - m_settingUI = false; } void EditExecutablesDialog::setButtons( @@ -274,6 +272,8 @@ void EditExecutablesDialog::clearEdits() ui->configureLibraries->setEnabled(false); ui->useApplicationIcon->setEnabled(false); ui->useApplicationIcon->setChecked(false); + + m_lastGoodTitle = ""; } void EditExecutablesDialog::setEdits(const Executable& e) @@ -287,6 +287,8 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->steamAppID->setText(e.steamAppID()); ui->useApplicationIcon->setChecked(e.usesOwnIcon()); + m_lastGoodTitle = e.title(); + { int modIndex = -1; @@ -559,6 +561,13 @@ void EditExecutablesDialog::on_title_textChanged(const QString& s) return; } + // don't allow empty names + if (s.trimmed().isEmpty()) { + return; + } + + m_lastGoodTitle = s; + // 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(); @@ -570,6 +579,13 @@ void EditExecutablesDialog::on_title_textChanged(const QString& s) } } +void EditExecutablesDialog::on_title_editingFinished() +{ + if (ui->title->text().trimmed().isEmpty()) { + ui->title->setText(m_lastGoodTitle); + } +} + void EditExecutablesDialog::on_overwriteSteamAppID_toggled(bool checked) { if (m_settingUI) { @@ -619,9 +635,8 @@ void EditExecutablesDialog::on_browseBinary_clicked() ui->binary->setText(QDir::toNativeSeparators(binaryName)); } - // setting title if currently empty or some variation of "New Executable" - if (ui->title->text().isEmpty() || - ui->title->text().startsWith(tr("New Executable"), Qt::CaseInsensitive)) { + // setting title if some variation of "New Executable" + if (ui->title->text().startsWith(tr("New Executable"), Qt::CaseInsensitive)) { const auto prefix = QFileInfo(binaryName).baseName(); const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 494f0651..9e39b115 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -169,6 +169,7 @@ private slots: void on_down_clicked(); void on_title_textChanged(const QString& s); + void on_title_editingFinished(); void on_overwriteSteamAppID_toggled(bool checked); void on_createFilesInMod_toggled(bool checked); void on_forceLoadLibraries_toggled(bool checked); @@ -196,6 +197,10 @@ private: // forced libraries set in the dialog ForcedLibraries m_forcedLibraries; + // remembers the last executable title that made sense, reverts to this when + // the widget loses focus if it's empty + QString m_lastGoodTitle; + // 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; -- cgit v1.3.1 From 8bc84a1513325f73252f4a07504ee6d2aad4b2f3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 19:08:55 -0400 Subject: remove whitespace from executable names add from binary, clone --- src/editexecutablesdialog.cpp | 164 ++++++++++++++++++++++++++++++------------ src/editexecutablesdialog.h | 13 +++- src/editexecutablesdialog.ui | 7 +- 3 files changed, 136 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 5c4ee149..41392712 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -33,6 +33,28 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +class IgnoreChanges +{ +public: + IgnoreChanges(EditExecutablesDialog* d) + : m_dialog(d) + { + m_dialog->m_settingUI = true; + } + + ~IgnoreChanges() + { + m_dialog->m_settingUI = false; + } + + IgnoreChanges(const IgnoreChanges&) = delete; + IgnoreChanges& operator=(const IgnoreChanges&) = delete; + +private: + EditExecutablesDialog* m_dialog; +}; + + EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) : TutorableDialog("EditExecutables", parent) , ui(new Ui::EditExecutablesDialog) @@ -53,6 +75,12 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) fillList(); setDirty(false); + auto* m = new QMenu; + m->addAction(tr("Add from file..."), [&]{ addFromFile(); }); + m->addAction(tr("Add empty"), [&]{ addEmpty(); }); + m->addAction(tr("Clone selected"), [&]{ clone(); }); + ui->add->setMenu(m); + // 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(); }); @@ -221,8 +249,7 @@ void EditExecutablesDialog::updateUI( const QListWidgetItem* item, const Executable* e) { // the ui is currently being set, ignore changes - m_settingUI = true; - Guard g([&]{ m_settingUI = false; }); + IgnoreChanges c(this); if (e) { setEdits(*e); @@ -461,20 +488,7 @@ void EditExecutablesDialog::on_reset_clicked() void EditExecutablesDialog::on_add_clicked() { - auto title = m_executablesList.makeNonConflictingTitle(tr("New Executable")); - if (!title) { - return; - } - - const Executable e(*title); - - m_executablesList.setExecutable(e); - - auto* item = createListItem(e); - ui->list->addItem(item); - item->setSelected(true); - - setDirty(true); + addFromFile(); } void EditExecutablesDialog::on_remove_clicked() @@ -550,19 +564,21 @@ bool EditExecutablesDialog::isTitleConflicting(const QString& s) return false; } -void EditExecutablesDialog::on_title_textChanged(const QString& s) +void EditExecutablesDialog::on_title_textChanged(const QString& original) { if (m_settingUI) { return; } - // don't allow changing the title to something that already exists - if (isTitleConflicting(s)) { + auto s = original.trimmed(); + + // disallow empty names + if (s.isEmpty()) { return; } - // don't allow empty names - if (s.trimmed().isEmpty()) { + // disallow changing the title to something that already exists + if (isTitleConflicting(s)) { return; } @@ -581,9 +597,7 @@ void EditExecutablesDialog::on_title_textChanged(const QString& s) void EditExecutablesDialog::on_title_editingFinished() { - if (ui->title->text().trimmed().isEmpty()) { - ui->title->setText(m_lastGoodTitle); - } + ui->title->setText(m_lastGoodTitle); } void EditExecutablesDialog::on_overwriteSteamAppID_toggled(bool checked) @@ -618,34 +632,78 @@ void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) 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")); + const auto binaryName = browseBinary(ui->binary->text()); + if (binaryName.fileName().isEmpty()) { + return; + } - if (binaryName.isNull()) { - // canceled + setBinary(binaryName); + save(); +} + +void EditExecutablesDialog::addFromFile() +{ + const auto binary = browseBinary(ui->binary->text()); + if (binary.fileName().isEmpty()) { return; } + addNew(Executable(binary.fileName())); + setBinary(binary); +} + +void EditExecutablesDialog::addEmpty() +{ + addNew(Executable(tr("New Executable"))); +} + +void EditExecutablesDialog::clone() +{ + auto* e = selectedExe(); + if (!e) { + return; + } + + addNew(*e); +} + +void EditExecutablesDialog::addNew(Executable e) +{ + const auto fixedTitle = m_executablesList.makeNonConflictingTitle(e.title()); + if (!fixedTitle) { + return; + } + + e.title(*fixedTitle); + + m_executablesList.setExecutable(e); + + auto* item = createListItem(e); + ui->list->addItem(item); + item->setSelected(true); + + setDirty(true); +} + +void EditExecutablesDialog::setBinary(const QFileInfo& binary) +{ // setting binary - if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { + if (binary.suffix().compare("jar", Qt::CaseInsensitive) == 0) { // special case for jar files, uses the system java installation - setJarBinary(binaryName); + setJarBinary(binary); } else { - ui->binary->setText(QDir::toNativeSeparators(binaryName)); + ui->binary->setText(QDir::toNativeSeparators(binary.absoluteFilePath())); } // setting title if some variation of "New Executable" if (ui->title->text().startsWith(tr("New Executable"), Qt::CaseInsensitive)) { - const auto prefix = QFileInfo(binaryName).baseName(); + const auto prefix = binary.baseName(); const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); if (newTitle) { ui->title->setText(*newTitle); } } - - save(); } void EditExecutablesDialog::on_browseWorkingDirectory_clicked() @@ -655,7 +713,7 @@ void EditExecutablesDialog::on_browseWorkingDirectory_clicked() ui->workingDirectory->text()); if (dirName.isNull()) { - // canceled + // cancelled return; } @@ -694,9 +752,26 @@ void EditExecutablesDialog::on_buttons_clicked(QAbstractButton* b) } } -void EditExecutablesDialog::setJarBinary(const QString& binaryName) +QFileInfo EditExecutablesDialog::browseBinary(const QString& initial) { - auto java = OrganizerCore::findJavaInstallation(binaryName); + const QString Filters = + tr("Executables (*.exe *.bat *.jar)") + ";;" + + tr("All Files (*.*)"); + + const auto f = FileDialogMemory::getOpenFileName( + "editExecutableBinary", this, tr("Select an executable"), + initial, Filters); + + if (f.isNull()) { + return {}; + } + + return QFileInfo(f); +} + +void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) +{ + auto java = OrganizerCore::findJavaInstallation(binary.absoluteFilePath()); if (java.isEmpty()) { QMessageBox::information( @@ -706,13 +781,14 @@ void EditExecutablesDialog::setJarBinary(const QString& binaryName) "the binary.")); } - // only save once + { + // only save once + IgnoreChanges c(this); - 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; + ui->binary->setText(java); + ui->workingDirectory->setText(QDir::toNativeSeparators(binary.absolutePath())); + ui->arguments->setText("-jar \"" + QDir::toNativeSeparators(binary.absoluteFilePath()) + "\""); + } save(); } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 9e39b115..2e52c722 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -141,7 +141,8 @@ private: **/ class EditExecutablesDialog : public MOBase::TutorableDialog { - Q_OBJECT + Q_OBJECT; + friend class IgnoreChanges; public: using CustomOverwrites = ToggableMap; @@ -222,10 +223,18 @@ private: 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); + + void addFromFile(); + void addEmpty(); + void clone(); + void addNew(Executable e); + + QFileInfo browseBinary(const QString& initial); + void setBinary(const QFileInfo& binary); + void setJarBinary(const QFileInfo& binary); }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index c2ff7d31..64d5140d 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -106,6 +106,9 @@ :/MO/gui/add:/MO/gui/add + + QToolButton::InstantPopup + @@ -140,7 +143,7 @@ Move the executable up in the list - Move the executable up in the list + Up @@ -160,7 +163,7 @@ Move the executable down in the list - Move the executable down in the list + Down -- cgit v1.3.1 From 52db7e8c7ddd39609a8eecbf44fa15ec6369f843 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 19:20:08 -0400 Subject: initial selection in the edit executables dialog from main window --- src/editexecutablesdialog.cpp | 6 +++++- src/editexecutablesdialog.h | 3 ++- src/mainwindow.cpp | 9 +++++---- src/mainwindow.h | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 41392712..19b57e9a 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -55,7 +55,7 @@ private: }; -EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) +EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget* parent) : TutorableDialog("EditExecutables", parent) , ui(new Ui::EditExecutablesDialog) , m_organizerCore(oc) @@ -75,6 +75,10 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) fillList(); setDirty(false); + if (sel >= 0 && sel < ui->list->count()) { + ui->list->item(sel)->setSelected(true); + } + auto* m = new QMenu; m->addAction(tr("Add from file..."), [&]{ addFromFile(); }); m->addAction(tr("Add empty"), [&]{ addEmpty(); }); diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 2e52c722..949aff66 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -148,7 +148,8 @@ public: using CustomOverwrites = ToggableMap; using ForcedLibraries = ToggableMap>; - explicit EditExecutablesDialog(OrganizerCore& oc, QWidget* parent=nullptr); + explicit EditExecutablesDialog( + OrganizerCore& oc, int selection=-1, QWidget* parent=nullptr); ~EditExecutablesDialog(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 295c60a6..ba02267a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2345,12 +2345,12 @@ void MainWindow::on_startButton_clicked() { ui->startButton->setEnabled(true); } -bool MainWindow::modifyExecutablesDialog() +bool MainWindow::modifyExecutablesDialog(int selection) { bool result = false; try { - EditExecutablesDialog dialog(m_OrganizerCore, this); + EditExecutablesDialog dialog(m_OrganizerCore, selection, this); result = (dialog.exec() == QDialog::Accepted); @@ -2375,7 +2375,7 @@ void MainWindow::on_executablesListBox_currentIndexChanged(int index) m_OldExecutableIndex = index; if (index == 0) { - modifyExecutablesDialog(); + modifyExecutablesDialog(previousIndex - 1); ui->executablesListBox->setCurrentIndex(previousIndex); } } @@ -2451,7 +2451,8 @@ void MainWindow::on_actionAdd_Profile_triggered() void MainWindow::on_actionModify_Executables_triggered() { - if (modifyExecutablesDialog()) { + const auto sel = (m_OldExecutableIndex > 0 ? m_OldExecutableIndex - 1 : 0); + if (modifyExecutablesDialog(sel)) { ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 524e2b6e..fa6a81cf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -235,7 +235,7 @@ private: QList findFileInfos(const QString &path, const std::function &filter) const; - bool modifyExecutablesDialog(); + bool modifyExecutablesDialog(int selection); void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None); void testExtractBSA(int modIndex); -- cgit v1.3.1 From 0ea975248b8cdb8319d3a366616f8d852726f83c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 19:38:56 -0400 Subject: remove separators and backups from mod list in the edit executables dialog --- src/editexecutablesdialog.cpp | 7 ++++++- src/modinfo.cpp | 25 ++++++++++++++++++++----- src/modinfo.h | 12 ++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 19b57e9a..b56fdc41 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -71,7 +71,12 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget loadCustomOverwrites(); loadForcedLibraries(); - ui->mods->addItems(m_organizerCore.modList()->allMods()); + for (auto&& m : m_organizerCore.modList()->allMods()) { + if (ModInfo::isRegularName(m)) { + ui->mods->addItem(m); + } + } + fillList(); setDirty(false); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index e3daa4fd..6e048bfb 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -61,17 +61,32 @@ bool ModInfo::ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; } +bool ModInfo::isSeparatorName(const QString& name) +{ + static QRegExp separatorExp(".*_separator"); + return separatorExp.exactMatch(name); +} + +bool ModInfo::isBackupName(const QString& name) +{ + static QRegExp backupExp(".*backup[0-9]*"); + return backupExp.exactMatch(name); +} + +bool ModInfo::isRegularName(const QString& name) +{ + return !isSeparatorName(name) && !isBackupName(name); +} + ModInfo::Ptr ModInfo::createFrom(PluginContainer *pluginContainer, const MOBase::IPluginGame *game, const QDir &dir, DirectoryEntry **directoryStructure) { QMutexLocker locker(&s_Mutex); - // int id = s_NextID++; - static QRegExp backupExp(".*backup[0-9]*"); - static QRegExp separatorExp(".*_separator"); ModInfo::Ptr result; - if (backupExp.exactMatch(dir.dirName())) { + + if (isBackupName(dir.dirName())) { result = ModInfo::Ptr(new ModInfoBackup(pluginContainer, game, dir, directoryStructure)); - } else if(separatorExp.exactMatch(dir.dirName())){ + } else if (isSeparatorName(dir.dirName())) { result = Ptr(new ModInfoSeparator(pluginContainer, game, dir, directoryStructure)); } else { result = ModInfo::Ptr(new ModInfoRegular(pluginContainer, game, dir, directoryStructure)); diff --git a/src/modinfo.h b/src/modinfo.h index e395f45b..30a115c7 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -229,6 +229,18 @@ public: */ static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, MOShared::DirectoryEntry **directoryStructure, PluginContainer *pluginContainer); + // whether the given name is used for separators + // + static bool isSeparatorName(const QString& name); + + // whether the given name is used for backups + // + static bool isBackupName(const QString& name); + + // whether the given name is used for regular mods + // + static bool isRegularName(const QString& name); + /** * @brief retieve a name for one of the CONTENT_ enums * @param contentType the content value -- cgit v1.3.1 From de48d430eca4042f48be461fe5dc70d7e75d5596 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 19:55:44 -0400 Subject: fixed tab order fixed list items being selected, but not focused, which broke keyboard nav --- src/editexecutablesdialog.cpp | 27 ++++++++++++++++++--------- src/editexecutablesdialog.h | 1 + src/editexecutablesdialog.ui | 11 +++++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index b56fdc41..b58531e5 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -81,7 +81,7 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget setDirty(false); if (sel >= 0 && sel < ui->list->count()) { - ui->list->item(sel)->setSelected(true); + selectIndex(sel); } auto* m = new QMenu; @@ -205,6 +205,14 @@ void EditExecutablesDialog::setDirty(bool b) } } +void EditExecutablesDialog::selectIndex(int i) +{ + if (i >= 0 && i < ui->list->count()) { + ui->list->selectionModel()->setCurrentIndex( + ui->list->model()->index(i, 0), QItemSelectionModel::ClearAndSelect); + } +} + QListWidgetItem* EditExecutablesDialog::selectedItem() { const auto selection = ui->list->selectedItems(); @@ -243,7 +251,7 @@ void EditExecutablesDialog::fillList() // select the first one in the list, if any if (ui->list->count() > 0) { - ui->list->item(0)->setSelected(true); + selectIndex(0); } else { updateUI(nullptr, nullptr); } @@ -459,13 +467,14 @@ void EditExecutablesDialog::move(QListWidgetItem* item, int direction) return; } - const auto row = ui->list->row(item); + const auto oldRow = ui->list->row(item); + const auto newRow = oldRow + (direction > 0 ? 1 : -1); // removing item - ui->list->takeItem(row); - ui->list->insertItem(row + (direction > 0 ? 1 : -1), item); - item->setSelected(true); + ui->list->takeItem(oldRow); + ui->list->insertItem(newRow, item); + selectIndex(newRow); setDirty(true); } @@ -530,10 +539,10 @@ void EditExecutablesDialog::on_remove_clicked() 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); + selectIndex(ui->list->count() - 1); } } else { - ui->list->item(currentRow)->setSelected(true); + selectIndex(currentRow); } setDirty(true); @@ -689,8 +698,8 @@ void EditExecutablesDialog::addNew(Executable e) auto* item = createListItem(e); ui->list->addItem(item); - item->setSelected(true); + selectIndex(ui->list->count() - 1); setDirty(true); } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 949aff66..c8be0bf6 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -227,6 +227,7 @@ private: bool isTitleConflicting(const QString& s); void commitChanges(); void setDirty(bool b); + void selectIndex(int i); void addFromFile(); void addEmpty(); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 64d5140d..5f7fee09 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -478,14 +478,25 @@ Right now the only case I know of where this needs to be overwritten is for the + add + remove + up + down + reset + list + title binary browseBinary workingDirectory browseWorkingDirectory + arguments overwriteSteamAppID steamAppID createFilesInMod mods + forceLoadLibraries + configureLibraries + useApplicationIcon -- cgit v1.3.1 From 21f55da7d3688eafc9e2dbae4535cf0c554be121 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 23:03:45 -0400 Subject: added hide flag to executables --- src/editexecutablesdialog.cpp | 11 ++++ src/editexecutablesdialog.ui | 21 ++++-- src/executableslist.cpp | 16 ++++- src/executableslist.h | 6 +- src/mainwindow.cpp | 145 ++++++++++++++++++++++++++++++------------ src/mainwindow.h | 3 +- 6 files changed, 149 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index b58531e5..3efadc4c 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -97,6 +97,7 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, QWidget connect(ui->steamAppID, &QLineEdit::textChanged, [&]{ save(); }); connect(ui->mods, &QComboBox::currentTextChanged, [&]{ save(); }); connect(ui->useApplicationIcon, &QCheckBox::toggled, [&]{ save(); }); + connect(ui->hide, &QCheckBox::toggled, [&]{ save(); }); connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&]{ saveOrder(); }); } @@ -316,6 +317,8 @@ void EditExecutablesDialog::clearEdits() ui->configureLibraries->setEnabled(false); ui->useApplicationIcon->setEnabled(false); ui->useApplicationIcon->setChecked(false); + ui->hide->setEnabled(false); + ui->hide->setChecked(false); m_lastGoodTitle = ""; } @@ -330,6 +333,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->steamAppID->setEnabled(!e.steamAppID().isEmpty()); ui->steamAppID->setText(e.steamAppID()); ui->useApplicationIcon->setChecked(e.usesOwnIcon()); + ui->hide->setChecked(e.hide()); m_lastGoodTitle = e.title(); @@ -375,6 +379,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->useApplicationIcon->setEnabled(true); ui->createFilesInMod->setEnabled(true); ui->forceLoadLibraries->setEnabled(true); + ui->hide->setEnabled(true); } void EditExecutablesDialog::save() @@ -434,6 +439,12 @@ void EditExecutablesDialog::save() e->flags(e->flags() & (~Executable::UseApplicationIcon)); } + if (ui->hide->isChecked()) { + e->flags(e->flags() | Executable::Hide); + } else { + e->flags(e->flags() & (~Executable::Hide)); + } + setDirty(true); } diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 5f7fee09..6f50b3b3 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -378,7 +378,7 @@ Right now the only case I know of where this needs to be overwritten is for the If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. - Create Files in Mod instead of Overwrite (*) + Create files in mod instead of overwrite (*) @@ -399,7 +399,7 @@ Right now the only case I know of where this needs to be overwritten is for the If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - Force Load Libraries (*) + Force load libraries (*) @@ -431,14 +431,27 @@ Right now the only case I know of where this needs to be overwritten is for the - Use Application's Icon for desktop shortcuts + Use application's icon for desktop shortcuts + + + + + + + This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + + + This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + + + Hide in user interface - (*) Profile Specific + (*) Profile specific 5 diff --git a/src/executableslist.cpp b/src/executableslist.cpp index dce9181b..1be34b53 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -84,6 +84,9 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) if (map["ownicon"].toBool()) flags |= Executable::UseApplicationIcon; + if (map["hide"].toBool()) + flags |= Executable::Hide; + if (map.contains("custom")) { // the "custom" setting only exists in older versions needsUpgrade = true; @@ -116,6 +119,7 @@ void ExecutablesList::store(Settings& s) map["title"] = item.title(); map["toolbar"] = item.isShownOnToolbar(); map["ownicon"] = item.usesOwnIcon(); + map["hide"] = item.hide(); map["binary"] = item.binaryInfo().absoluteFilePath(); map["arguments"] = item.arguments(); map["workingDirectory"] = item.workingDirectory(); @@ -343,6 +347,10 @@ void ExecutablesList::dump() const flags.push_back("icon"); } + if (e.flags() & Executable::Hide) { + flags.push_back("hide"); + } + log::debug( " . executable '{}'\n" " binary: {}\n" @@ -456,11 +464,13 @@ bool Executable::usesOwnIcon() const return m_flags.testFlag(UseApplicationIcon); } -void Executable::mergeFrom(const Executable& other) +bool Executable::hide() const { - // flags on plugin executables that the user is allowed to change - const auto allow = ShowInToolbar; + return m_flags.testFlag(Hide); +} +void Executable::mergeFrom(const Executable& other) +{ // this happens after executables are loaded from settings and plugin // executables are being added, or when users are modifying executables diff --git a/src/executableslist.h b/src/executableslist.h index 23cf3cfe..a18042db 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -39,8 +39,9 @@ class Executable public: enum Flag { - ShowInToolbar = 0x02, - UseApplicationIcon = 0x04 + ShowInToolbar = 0x02, + UseApplicationIcon = 0x04, + Hide = 0x08 }; Q_DECLARE_FLAGS(Flags, Flag); @@ -69,6 +70,7 @@ public: bool isShownOnToolbar() const; void setShownOnToolbar(bool state); bool usesOwnIcon() const; + bool hide() const; void mergeFrom(const Executable& other); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ba02267a..946caf08 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -757,7 +757,7 @@ void MainWindow::updatePinnedExecutables() bool hasLinks = false; for (const auto& exe : *m_OrganizerCore.executablesList()) { - if (exe.isShownOnToolbar()) { + if (!exe.hide() && exe.isShownOnToolbar()) { hasLinks = true; QAction *exeAction = new QAction( @@ -1792,23 +1792,44 @@ bool MainWindow::refreshProfiles(bool selectProfile) void MainWindow::refreshExecutablesList() { - QComboBox* executablesList = findChild("executablesListBox"); - executablesList->setEnabled(false); - executablesList->clear(); - executablesList->addItem(tr("")); + QAbstractItemModel *model = ui->executablesListBox->model(); + + auto add = [&](const QString& title, const QFileInfo& binary) { + QIcon icon; + if (!binary.fileName().isEmpty()) { + icon = iconForExecutable(binary.filePath()); + } + + ui->executablesListBox->addItem(icon, title); + + const auto i = ui->executablesListBox->count() - 1; + + model->setData( + model->index(i, 0), + QSize(0, ui->executablesListBox->iconSize().height() + 4), + Qt::SizeHintRole); + }; - QAbstractItemModel *model = executablesList->model(); - int i = 0; + ui->executablesListBox->setEnabled(false); + ui->executablesListBox->clear(); + + add(tr(""), {}); + 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; + if (!exe.hide()) { + add(exe.title(), exe.binaryInfo()); + } + } + + if (ui->executablesListBox->count() == 1) { + // all executables are hidden, add an empty one to at least be able to + // switch to edit + add(tr("(no executables)"), QFileInfo(":badfile")); } ui->executablesListBox->setCurrentIndex(1); - executablesList->setEnabled(true); + ui->executablesListBox->setEnabled(true); } @@ -2321,27 +2342,42 @@ void MainWindow::installMod(QString fileName) } } -void MainWindow::on_startButton_clicked() { - ui->startButton->setEnabled(false); +void MainWindow::on_startButton_clicked() +{ try { - const Executable &selectedExecutable(getSelectedExecutable()); - 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())) { + const Executable* selectedExecutable = getSelectedExecutable(); + if (!selectedExecutable) { + return; + } + + ui->startButton->setEnabled(false); + + auto* profile = m_OrganizerCore.currentProfile(); + + const QString customOverwrite = profile->setting( + "custom_overwrites", selectedExecutable->title()).toString(); + + auto forcedLibraries = profile->determineForcedLibraries( + selectedExecutable->title()); + + if (!profile->forcedLibrariesEnabled(selectedExecutable->title())) { forcedLibraries.clear(); } + m_OrganizerCore.spawnBinary( - selectedExecutable.binaryInfo(), selectedExecutable.arguments(), - selectedExecutable.workingDirectory().length() != 0 - ? selectedExecutable.workingDirectory() - : selectedExecutable.binaryInfo().absolutePath(), - selectedExecutable.steamAppID(), - customOverwrite, - forcedLibraries); + selectedExecutable->binaryInfo(), + selectedExecutable->arguments(), + selectedExecutable->workingDirectory().length() != 0 ? + selectedExecutable->workingDirectory() : + selectedExecutable->binaryInfo().absolutePath(), + selectedExecutable->steamAppID(), + customOverwrite, + forcedLibraries); } catch (...) { ui->startButton->setEnabled(true); throw; } + ui->startButton->setEnabled(true); } @@ -2376,7 +2412,13 @@ void MainWindow::on_executablesListBox_currentIndexChanged(int index) if (index == 0) { modifyExecutablesDialog(previousIndex - 1); - ui->executablesListBox->setCurrentIndex(previousIndex); + const auto newCount = ui->executablesListBox->count(); + + if (previousIndex >= 0 && previousIndex < newCount) { + ui->executablesListBox->setCurrentIndex(previousIndex); + } else { + ui->executablesListBox->setCurrentIndex(newCount - 1); + } } } @@ -2452,8 +2494,14 @@ void MainWindow::on_actionAdd_Profile_triggered() void MainWindow::on_actionModify_Executables_triggered() { const auto sel = (m_OldExecutableIndex > 0 ? m_OldExecutableIndex - 1 : 0); + if (modifyExecutablesDialog(sel)) { - ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); + const auto newCount = ui->executablesListBox->count(); + if (m_OldExecutableIndex >= 0 && m_OldExecutableIndex < newCount) { + ui->executablesListBox->setCurrentIndex(m_OldExecutableIndex); + } else { + ui->executablesListBox->setCurrentIndex(newCount - 1); + } } } @@ -4972,33 +5020,43 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) void MainWindow::linkToolbar() { - Executable& exe = getSelectedExecutable(); + Executable* exe = getSelectedExecutable(); + if (!exe) { + return; + } - exe.setShownOnToolbar(!exe.isShownOnToolbar()); + exe->setShownOnToolbar(!exe->isShownOnToolbar()); updatePinnedExecutables(); } void MainWindow::linkDesktop() { - env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::Desktop); + if (auto* exe=getSelectedExecutable()) { + env::Shortcut(*exe).toggle(env::Shortcut::Desktop); + } } void MainWindow::linkMenu() { - env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::StartMenu); + if (auto* exe=getSelectedExecutable()) { + env::Shortcut(*exe).toggle(env::Shortcut::StartMenu); + } } void MainWindow::on_linkButton_pressed() { - const Executable& exe = getSelectedExecutable(); + const Executable* exe = getSelectedExecutable(); + if (!exe) { + return; + } const QIcon addIcon(":/MO/gui/link"); const QIcon removeIcon(":/MO/gui/remove"); - env::Shortcut shortcut(exe); + env::Shortcut shortcut(*exe); m_LinkToolbar->setIcon( - exe.isShownOnToolbar() ? removeIcon : addIcon); + exe->isShownOnToolbar() ? removeIcon : addIcon); m_LinkDesktop->setIcon( shortcut.exists(env::Shortcut::Desktop) ? removeIcon : addIcon); @@ -6327,16 +6385,19 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) modFilterActive(m_ModListSortProxy->isFilterActive()); } -const Executable &MainWindow::getSelectedExecutable() const +Executable* MainWindow::getSelectedExecutable() { - const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->get(name); -} + const QString name = ui->executablesListBox->itemText( + ui->executablesListBox->currentIndex()); -Executable &MainWindow::getSelectedExecutable() -{ - const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->get(name); + try + { + return &m_OrganizerCore.executablesList()->get(name); + } + catch(std::runtime_error&) + { + return nullptr; + } } void MainWindow::on_showHiddenBox_toggled(bool checked) diff --git a/src/mainwindow.h b/src/mainwindow.h index fa6a81cf..dbb1a0d5 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -397,8 +397,7 @@ private: // when painting the count QIcon m_originalNotificationIcon; - Executable const &getSelectedExecutable() const; - Executable &getSelectedExecutable(); + Executable* getSelectedExecutable(); private slots: -- cgit v1.3.1 From 4d3495b3fb00b644c57e773bbcdbfb2eee7c0ea6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 8 Oct 2019 23:17:48 -0400 Subject: theme fixes for QToolButton with a menu --- src/stylesheets/Night Eyes.qss | 5 +++++ src/stylesheets/vs15 Dark-Green.qss | 7 +++++++ src/stylesheets/vs15 Dark-Orange.qss | 7 +++++++ src/stylesheets/vs15 Dark-Purple.qss | 7 +++++++ src/stylesheets/vs15 Dark-Red.qss | 7 +++++++ src/stylesheets/vs15 Dark-Yellow.qss | 7 +++++++ src/stylesheets/vs15 Dark.qss | 7 +++++++ 7 files changed, 47 insertions(+) (limited to 'src') diff --git a/src/stylesheets/Night Eyes.qss b/src/stylesheets/Night Eyes.qss index 142acb6a..0430a8c8 100644 --- a/src/stylesheets/Night Eyes.qss +++ b/src/stylesheets/Night Eyes.qss @@ -80,6 +80,11 @@ QToolButton:pressed background: #181818; } +QToolButton::menu-indicator +{ + width: 8px; +} + /* Left Pane & File Trees ----------------------------------------------------- */ diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 9cf26a31..88e7651f 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -209,6 +209,13 @@ QToolButton { QToolButton:pressed { background-color: #009933; } +QToolButton::menu-indicator { + image: url(./vs15/combobox-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + padding-top: 10%; + padding-right: 5%; } + /* Group Boxes #QGroupBox */ QGroupBox { border-color: #3F3F46; diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index 041f1b00..488da3c4 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -210,6 +210,13 @@ QToolButton { QToolButton:pressed { background-color: #CC6600; } +QToolButton::menu-indicator { + image: url(./vs15/combobox-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + padding-top: 10%; + padding-right: 5%; } + /* Group Boxes #QGroupBox */ QGroupBox { border-color: #3F3F46; diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index bc8bbcde..24c8705a 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -210,6 +210,13 @@ QToolButton { QToolButton:pressed { background-color: #7E2AD2; } +QToolButton::menu-indicator { + image: url(./vs15/combobox-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + padding-top: 10%; + padding-right: 5%; } + /* Group Boxes #QGroupBox */ QGroupBox { border-color: #3F3F46; diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 0c9143cd..0c0e21a8 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -210,6 +210,13 @@ QToolButton { QToolButton:pressed { background-color: #990000; } +QToolButton::menu-indicator { + image: url(./vs15/combobox-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + padding-top: 10%; + padding-right: 5%; } + /* Group Boxes #QGroupBox */ QGroupBox { border-color: #3F3F46; diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index 2eb42534..2cf1cb2e 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -210,6 +210,13 @@ QToolButton { QToolButton:pressed { background-color: #9A9A00; } +QToolButton::menu-indicator { + image: url(./vs15/combobox-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + padding-top: 10%; + padding-right: 5%; } + /* Group Boxes #QGroupBox */ QGroupBox { border-color: #3F3F46; diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index d67cce35..a5781d72 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -209,6 +209,13 @@ QToolButton { QToolButton:pressed { background-color: #3399FF; } +QToolButton::menu-indicator { + image: url(./vs15/combobox-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + padding-top: 10%; + padding-right: 5%; } + /* Group Boxes #QGroupBox */ QGroupBox { border-color: #3F3F46; -- cgit v1.3.1 From dfb3020f8982e325e804f9e43b28a33b54c798f9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 9 Oct 2019 01:38:53 -0400 Subject: added usvfs version in log and about dialog --- src/aboutdialog.cpp | 3 +++ src/aboutdialog.ui | 7 +++++++ src/main.cpp | 5 +++-- src/shared/util.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 1 + 5 files changed, 60 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 41fd24f7..6ae7f56d 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "aboutdialog.h" #include "ui_aboutdialog.h" +#include "util.h" #include #include @@ -85,6 +86,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); #endif + + ui->usvfsLabel->setText(ui->usvfsLabel->text() + " " + MOShared::getUsvfsVersionString()); ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); } diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index c72a85f8..415ce0a7 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -104,6 +104,13 @@ + + + + usvfs: + + + diff --git a/src/main.cpp b/src/main.cpp index f08ba066..776c3775 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -504,8 +504,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { log::info( - "starting Mod Organizer version {} revision {} in {}", - getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath()); + "starting Mod Organizer version {} revision {} in {}, usvfs: {}", + getVersionDisplayString(), GITID, QCoreApplication::applicationDirPath(), + MOShared::getUsvfsVersionString()); preloadSsl(); if (!QSslSocket::supportsSsl()) { diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 58f4eb2b..32eb825c 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,8 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "mainwindow.h" +#include +#include namespace MOShared { @@ -244,6 +246,50 @@ MOBase::VersionInfo createVersionInfo() } } +QString getUsvfsDLLVersion() +{ + // once 2.2.2 is released, this can be changed to call USVFSVersionString() + // directly; until then, using GetProcAddress() allows for mixing up devbuilds + // and usvfs dlls + + using USVFSVersionStringType = const char* WINAPI (); + + QString s; + + const auto m = ::LoadLibraryW(L"usvfs_x64.dll"); + + if (m) { + auto* f = reinterpret_cast( + ::GetProcAddress(m, "USVFSVersionString")); + + if (f) { + s = f(); + } + + ::FreeLibrary(m); + } + + if (s.isEmpty()) { + s = "?"; + } + + return s; +} + +QString getUsvfsVersionString() +{ + const QString dll = getUsvfsDLLVersion(); + const QString header = USVFS_VERSION_STRING; + + QString usvfsVersion; + + if (dll == header) { + return dll; + } else { + return "dll is " + dll + ", compiled against " + header; + } +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index aea6f200..e87244b6 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -45,6 +45,7 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); MOBase::VersionInfo createVersionInfo(); +QString getUsvfsVersionString(); } // namespace MOShared -- cgit v1.3.1 From 324fa12a2d491be039c1cf720ee2786864d010bc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 10 Oct 2019 10:25:46 -0400 Subject: uses new usvfsParameters, also updates spawn delay --- src/organizercore.cpp | 6 ++++-- src/organizercore.h | 2 +- src/usvfsconnector.cpp | 44 ++++++++++++++++++++++++++++++++------------ src/usvfsconnector.h | 2 +- 4 files changed, 38 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 335910da..df824c2b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -484,10 +484,11 @@ void OrganizerCore::prepareVFS() } void OrganizerCore::updateVFSParams( - log::Levels logLevel, CrashDumpsType crashDumpsType, QString executableBlacklist) + log::Levels logLevel, CrashDumpsType crashDumpsType, + std::chrono::seconds spawnDelay, QString executableBlacklist) { setGlobalCrashDumpsType(crashDumpsType); - m_USVFS.updateParams(logLevel, crashDumpsType, executableBlacklist); + m_USVFS.updateParams(logLevel, crashDumpsType, spawnDelay, executableBlacklist); } void OrganizerCore::setLogLevel(log::Levels level) @@ -497,6 +498,7 @@ void OrganizerCore::setLogLevel(log::Levels level) updateVFSParams( m_Settings.diagnostics().logLevel(), m_Settings.diagnostics().crashDumpsType(), + m_Settings.diagnostics().spawnDelay(), m_Settings.executablesBlacklist()); log::getDefault().setLevel(m_Settings.diagnostics().logLevel()); diff --git a/src/organizercore.h b/src/organizercore.h index 5de550df..5b7dd87b 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -195,7 +195,7 @@ public: void updateVFSParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - QString executableBlacklist); + std::chrono::seconds spawnDelay, QString executableBlacklist); void setLogLevel(MOBase::log::Levels level); diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 3ea811be..9a4fa742 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -162,15 +162,24 @@ QString toString(CrashDumpsType t) UsvfsConnector::UsvfsConnector() { + using namespace std::chrono; + const auto& s = Settings::instance(); - USVFSParameters params; - const LogLevel level = toUsvfsLogLevel(s.diagnostics().logLevel()); + const LogLevel logLevel = toUsvfsLogLevel(s.diagnostics().logLevel()); const CrashDumpsType dumpType = s.diagnostics().crashDumpsType(); - const auto delay = s.diagnostics().spawnDelay(); - + const auto delay = duration_cast(s.diagnostics().spawnDelay()); std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); - USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str(), delay); + + usvfsParameters* params = usvfsCreateParameters(); + + usvfsSetInstanceName(params, SHMID); + usvfsSetDebugMode(params, false); + usvfsSetLogLevel(params, logLevel); + usvfsSetCrashDumpType(params, dumpType); + usvfsSetCrashDumpPath(params, dumpPath.c_str()); + usvfsSetProcessDelay(params, delay.count()); + InitLogging(false); log::debug( @@ -178,12 +187,13 @@ UsvfsConnector::UsvfsConnector() " . instance: {}\n" " . log: {}\n" " . dump: {} ({})", - params.instanceName, - toString(params.logLevel), - params.crashDumpsPath, - toString(params.crashDumpsType)); + SHMID, + toString(logLevel), + dumpPath.c_str(), + toString(dumpType)); - CreateVFS(¶ms); + usvfsCreateVFS(params); + usvfsFreeParameters(params); ClearExecutableBlacklist(); for (auto exec : s.executablesBlacklist().split(";")) { @@ -253,9 +263,19 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) void UsvfsConnector::updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - QString executableBlacklist) + std::chrono::seconds spawnDelay, QString executableBlacklist) { - USVFSUpdateParams(toUsvfsLogLevel(logLevel), crashDumpsType); + using namespace std::chrono; + + usvfsParameters* p = usvfsCreateParameters(); + + usvfsSetLogLevel(p, toUsvfsLogLevel(logLevel)); + usvfsSetCrashDumpType(p, crashDumpsType); + usvfsSetProcessDelay(p, duration_cast(spawnDelay).count()); + + usvfsUpdateParameters(p); + usvfsFreeParameters(p); + ClearExecutableBlacklist(); for (auto exec : executableBlacklist.split(";")) { std::wstring buf = exec.toStdWString(); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index cd5d56b2..a4647eaf 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -88,7 +88,7 @@ public: void updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - QString executableBlacklist); + std::chrono::seconds spawnDelay, QString executableBlacklist); void updateForcedLibraries(const QList &forcedLibraries); -- cgit v1.3.1 From 1aa3e1f96c5dc43638a8871afe593d122aed4ed9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 10 Oct 2019 11:48:41 -0400 Subject: visible property was false --- src/mainwindow.ui | 3 --- 1 file changed, 3 deletions(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index cbfed73e..b8aeeeb5 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -750,9 +750,6 @@ p, li { white-space: pre-wrap; } - - false - Sort -- cgit v1.3.1 From 0b480089c327e4037ada6f4bb649b180660f2923 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 12 Oct 2019 19:11:46 -0400 Subject: fixed loot not being hooked bumped to alpha 4.1 --- src/mainwindow.cpp | 1 + src/version.rc | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8d42d020..177c2a23 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6566,6 +6566,7 @@ void MainWindow::on_bossButton_clicked() sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); sp.arguments = parameters.join(" "); sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.hooked = true; sp.stdOut = stdOutWrite; HANDLE loot = spawn::startBinary(this, sp); diff --git a/src/version.rc b/src/version.rc index 433bcb40..5a893759 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2alpha3\0" +#define VER_FILEVERSION 2,2,2,4 +#define VER_FILEVERSION_STR "2.2.2alpha4.1\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 20c7b2f49859b14719e459e335816adff6689283 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 14 Oct 2019 04:45:46 -0400 Subject: title wasn't always trimmed, did all sorts of weird things --- src/editexecutablesdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 3efadc4c..09210ddf 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -408,7 +408,7 @@ void EditExecutablesDialog::save() // get the new title, but ignore it if it's conflicting with an already // existing executable - QString newTitle = ui->title->text(); + QString newTitle = ui->title->text().trimmed(); if (isTitleConflicting(newTitle)) { newTitle = e->title(); } -- cgit v1.3.1 From c0afb8c6730e13aaa54295ec23f39ae521d0fb1d Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 15 Oct 2019 16:20:58 -0500 Subject: Only flag plugins as light if the game supports light plugins --- src/pluginlist.cpp | 12 +++++++----- src/pluginlist.h | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 5f1ae347..f35f9409 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -170,6 +170,7 @@ void PluginList::refresh(const QString &profileName ChangeBracket layoutChange(this); QStringList primaryPlugins = m_GamePlugin->primaryPlugins(); + bool lightPluginsAreSupported = m_GamePlugin->feature()->lightPluginsAreSupported(); m_CurrentProfile = profileName; @@ -223,7 +224,7 @@ void PluginList::refresh(const QString &profileName originName = modInfo->name(); } - m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni, loadedArchives)); + m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni, loadedArchives, lightPluginsAreSupported)); m_ESPs.rbegin()->m_Priority = -1; } catch (const std::exception &e) { reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); @@ -843,6 +844,7 @@ void PluginList::generatePluginIndexes() { int numESLs = 0; int numSkipped = 0; + bool lightPluginsSupported = m_GamePlugin->feature()->lightPluginsAreSupported(); for (int l = 0; l < m_ESPs.size(); ++l) { int i = m_ESPsByPriority.at(l); if (!m_ESPs[i].m_Enabled) { @@ -850,7 +852,7 @@ void PluginList::generatePluginIndexes() ++numSkipped; continue; } - if (m_ESPs[i].m_IsLight || m_ESPs[i].m_IsLightFlagged) { + if (lightPluginsSupported && (m_ESPs[i].m_IsLight || m_ESPs[i].m_IsLightFlagged)) { int ESLpos = 254 + ((numESLs + 1) / 4096); m_ESPs[i].m_Index = QString("%1:%2").arg(ESLpos, 2, 16, QChar('0')).arg((numESLs) % 4096, 3, 16, QChar('0')).toUpper(); ++numESLs; @@ -1355,7 +1357,7 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, - bool hasIni, std::set archives) + bool hasIni, std::set archives, bool lightPluginsAreSupported) : m_Name(name), m_FullPath(fullPath), m_Enabled(enabled), m_ForceEnabled(enabled), m_Priority(0), m_LoadOrder(-1), m_OriginName(originName), m_HasIni(hasIni), m_Archives(archives), m_ModSelected(false) { @@ -1363,8 +1365,8 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, ESP::File file(ToWString(fullPath)); m_IsMaster = file.isMaster(); auto extension = name.right(3).toLower(); - m_IsLight = (extension == "esl"); - m_IsLightFlagged = file.isLight(); + m_IsLight = lightPluginsAreSupported && (extension == "esl"); + m_IsLightFlagged = lightPluginsAreSupported && file.isLight(); m_Author = QString::fromLatin1(file.author().c_str()); m_Description = QString::fromLatin1(file.description().c_str()); diff --git a/src/pluginlist.h b/src/pluginlist.h index 228ccdec..092ba378 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -296,7 +296,7 @@ private: struct ESPInfo { - ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni, std::set archives); + ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni, std::set archives, bool lightSupported); QString m_Name; QString m_FullPath; bool m_Enabled; -- cgit v1.3.1 From e3629c193ad100edde4039868ae809f8fc2aab06 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 18 Oct 2019 14:55:33 -0500 Subject: Improve selection of mod ID when querying info The mod ID selection was fairly unreliable when the mod author decided to use certain values in the mod name or filename. A filename of "K-9 - Armor-1-2-3" would automatically set the mod ID to 9 with no chance for the user to correct it. Other filenames would only present 2 choices of mod ID to the user, neither being correct. The primary intent of this change is to present the user with all the numbers found in the filename as options for the mod ID. This should allow the correct mod ID to be easily chosen in all cases. --- src/nexusinterface.cpp | 94 +++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 0e2bb45b..12310d4b 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -280,52 +280,60 @@ void NexusInterface::setUserAccount(const APIUserAccount& user) void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) { - //Look for something along the lines of modulename-Vn-m + any old rubbish. - static std::regex exp(R"exp(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]*).*\.(zip|rar|7z))exp"); - static std::regex simpleexp("^([a-zA-Z0-9_]+)"); - - QByteArray fileNameUTF8 = fileName.toUtf8(); - std::cmatch result; - if (std::regex_search(fileNameUTF8.constData(), result, exp)) { - modName = QString::fromUtf8(result[1].str().c_str()); - modName = modName.replace('_', ' ').trimmed(); - - std::string candidate = result[3].str(); - std::string candidate2 = result[2].str(); - if (candidate2.length() != 0 && (candidate2.find_last_of("VvRr") == std::string::npos)) { - // well, that second match might be an id too... - size_t offset = strspn(candidate2.c_str(), "-_ "); - if (offset < candidate2.length() && query) { - SelectionDialog selection(tr("Failed to guess mod id for \"%1\", please pick the correct one").arg(fileName)); - QString r2Highlight(fileName); - r2Highlight.insert(result.position(2) + result.length(2), "* ") - .insert(result.position(2) + static_cast(offset), " *"); - QString r3Highlight(fileName); - r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *"); - - selection.addChoice(candidate.c_str(), r3Highlight, static_cast(strtol(candidate.c_str(), nullptr, 10))); - selection.addChoice(candidate2.c_str() + offset, r2Highlight, static_cast(abs(strtol(candidate2.c_str() + offset, nullptr, 10)))); - if (selection.exec() == QDialog::Accepted) { - modID = selection.getChoiceData().toInt(); - } else { - modID = -1; - } - } else { - modID = -1; + // guess the mod name from the file name + static const QRegularExpression complex("^([a-zA-Z0-9_'\"\\-.() ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]*).*\\.(zip|rar|7z)"); + static const QRegularExpression simple("^[a-zA-Z0-9_]+"); + auto complexMatch = complex.match(fileName); + auto simpleMatch = simple.match(fileName); + if (complexMatch.hasMatch()) { + modName = complexMatch.captured(1); + } + else if (simpleMatch.hasMatch()) { + modName = simpleMatch.captured(0); + } + else { + modName.clear(); + } + + if (query) { + SelectionDialog selection(tr("Please pick the mod ID for \"%1\"").arg(fileName)); + int index = 0; + auto splits = fileName.split(QRegExp("[^0-9]"), QString::KeepEmptyParts); + for (auto substr : splits) { + bool ok = false; + int value = substr.toInt(&ok); + if (ok) { + QString highlight(fileName); + highlight.insert(index, " *"); + highlight.insert(index + substr.length() + 2, "* "); + + QStringList choice; + choice << substr; + choice << (index > 0 ? fileName.left(index - 1) : substr); + selection.addChoice(substr, highlight, choice); } - } else { - modID = strtol(candidate.c_str(), nullptr, 10); + index += substr.length() + 1; } - log::debug("mod id guessed: {} -> {}", fileName, modID); - } else if (std::regex_search(fileNameUTF8.constData(), result, simpleexp)) { - log::debug("simple expression matched, using name only"); - modName = QString::fromUtf8(result[1].str().c_str()); - modName = modName.replace('_', ' ').trimmed(); - modID = -1; - } else { - log::debug("no expression matched!"); - modName.clear(); + if (selection.numChoices() > 0) { + if (selection.exec() == QDialog::Accepted) { + auto choice = selection.getChoiceData().toStringList(); + modID = choice.at(0).toInt(); + modName = choice.at(1); + modName = modName.replace('_', ' ').trimmed(); + log::debug("user selected mod ID {} and mod name \"{}\"", modID, modName); + } + else { + log::debug("user canceled mod ID selection"); + modID = -1; + } + } + else { + log::debug("no possible mod IDs found in file name"); + modID = -1; + } + } + else { modID = -1; } } -- cgit v1.3.1 From 09450f744b1b3e4cf862bd68a82e0fbaf0e418dc Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 18 Oct 2019 19:06:11 -0500 Subject: Remove mention of 32-bit Java MO doesn't really care about which version of Java is installed. Saying that 32-bit Java is required is misleading. --- src/editexecutablesdialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 09210ddf..c6c90c3f 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -804,8 +804,8 @@ void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) if (java.isEmpty()) { QMessageBox::information( - this, tr("Java (32-bit) required"), - tr("MO requires 32-bit java to run this application. If you already " + this, tr("Java required"), + tr("MO requires Java to run this application. If you already " "have it installed, select javaw.exe from that installation as " "the binary.")); } -- cgit v1.3.1 From 77a9c64fda9cb522aae35777df2b09e19441bab1 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sat, 19 Oct 2019 02:35:33 -0500 Subject: Add button in modinfo image preview to use preview plugin dialog - Disabled if no compatible preview is found - Now renders an image for unsupported formats instructing the user --- src/modinfodialog.ui | 7 +++++++ src/modinfodialogimages.cpp | 45 +++++++++++++++++++++++++++++++++++++++------ src/modinfodialogimages.h | 3 +++ 3 files changed, 49 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index ba2db799..13a245f1 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -267,6 +267,13 @@ 0 + + + + Open with Preview Plugin + + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index c5b04538..48b3bb3b 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -53,6 +53,8 @@ ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : ui->tabImagesSplitter->setStretchFactor(0, 0); ui->tabImagesSplitter->setStretchFactor(1, 1); + ui->previewPluginButton->setEnabled(false); + ui->imagesThumbnails->setTab(this); ui->imagesScrollerVBar->setTab(this); @@ -65,6 +67,7 @@ ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); }); + connect(ui->previewPluginButton, &QAbstractButton::clicked, [&] { onPreviewButton(); }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); @@ -251,13 +254,34 @@ void ImagesTab::select(std::size_t i, Visibility v) ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); + if (plugin().previewGenerator().previewSupported(QString::fromStdWString(std::filesystem::path(f->path().toStdWString()).extension().wstring()).remove(0,1))) + ui->previewPluginButton->setEnabled(true); + else + ui->previewPluginButton->setEnabled(false); ui->imagesSize->setText(dimensionString(f->original().size())); - m_image->setImage(f->original()); + if (f->original().isNull()) { + m_image->clear(); + + QImage image(300, 100, QImage::Format_RGBA64); + QPainter paint; + paint.begin(&image); + paint.fillRect(0, 0, 300, 100, QBrush(QColor(0, 0, 0, 255))); + paint.setPen(m_theme.textColor); + paint.setFont(m_theme.font); + paint.drawImage(QPoint(150-16, 50-20-16), QImage(":/MO/gui/warning")); + const auto flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextWordWrap; + paint.drawText(0, 46, 300, 54, flags, "This image format is not supported by Qt, but the preview plugin may be able to display it. Use the button above."); + paint.end(); + + m_image->setImage(image); + } else + m_image->setImage(f->original()); ensureVisible(i, v); } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); + ui->previewPluginButton->setEnabled(false); ui->imagesSize->clear(); m_image->clear(); } @@ -560,6 +584,11 @@ void ImagesTab::onShowDDS() } } +void ImagesTab::onPreviewButton() +{ + core().previewFileWithAlternatives(parentWidget(), m_files.selectedFile()->path()); +} + void ImagesTab::onFilterChanged() { update(); @@ -958,13 +987,17 @@ void File::load(const Geometry& geo) ensureOriginalLoaded(); if (m_failed) { - return; - } + QImage warning(":/MO/gui/warning"); + const auto scaledSize = geo.scaledImageSize(warning.size()); - const auto scaledSize = geo.scaledImageSize(m_original.size()); + m_thumbnail = warning.scaled( + scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } else { + const auto scaledSize = geo.scaledImageSize(m_original.size()); - m_thumbnail = m_original.scaled( - scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + m_thumbnail = m_original.scaled( + scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 8d9b965b..5717b8b9 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -4,6 +4,8 @@ #include "modinfodialogtab.h" #include "filterwidget.h" #include +#include "plugincontainer.h" +#include "organizercore.h" class ImagesTab; @@ -359,6 +361,7 @@ private: void showTooltip(QHelpEvent* e); void onExplore(); void onShowDDS(); + void onPreviewButton(); void onFilterChanged(); void select(std::size_t i, Visibility v=Visibility::Full); -- cgit v1.3.1 From 741893c735cda3cbd261e6535450641f99c9f0f7 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 19 Oct 2019 20:45:59 -0500 Subject: Update version to 2.2.2alpha5 --- src/version.rc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 5a893759..be73324c 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,2,4 -#define VER_FILEVERSION_STR "2.2.2alpha4.1\0" +#define VER_FILEVERSION 2,2,2,5 +#define VER_FILEVERSION_STR "2.2.2alpha5\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 70b966dc0e0dd65f574538b8f09e0e36894fda7d Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Tue, 22 Oct 2019 23:27:39 -0500 Subject: Improve automatic naming of new executables When using the "Add from file..." option, the extension will be removed. E.g., "zEdit 1.2.3.exe" will be shortened to "zEdit 1.2.3". When using the "Add empty" option and subsequently browsing for a binary, the complete base name will be used instead of just the base name. E.g., "zEdit 1.2.3.exe" will be shorted to "zEdit 1.2.3" instead of "zEdit 1". When using the "Add as Executable" option of the data tab, the complete base name will be used instead of just the base name. E.g., "zEdit 1.2.3.exe" will be shorted to "zEdit 1.2.3" instead of "zEdit 1". --- src/editexecutablesdialog.cpp | 4 ++-- src/mainwindow.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index c6c90c3f..8535b7a7 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -677,7 +677,7 @@ void EditExecutablesDialog::addFromFile() return; } - addNew(Executable(binary.fileName())); + addNew(Executable(binary.completeBaseName())); setBinary(binary); } @@ -726,7 +726,7 @@ void EditExecutablesDialog::setBinary(const QFileInfo& binary) // setting title if some variation of "New Executable" if (ui->title->text().startsWith(tr("New Executable"), Qt::CaseInsensitive)) { - const auto prefix = binary.baseName(); + const auto prefix = binary.completeBaseName(); const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); if (newTitle) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 177c2a23..12ed40b3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5329,7 +5329,7 @@ void MainWindow::addAsExecutable() case FileExecutionTypes::Executable: { QString name = QInputDialog::getText(this, tr("Enter Name"), tr("Please enter a name for the executable"), QLineEdit::Normal, - targetInfo.baseName()); + targetInfo.completeBaseName()); if (!name.isEmpty()) { //Note: If this already exists, you'll lose custom settings -- cgit v1.3.1 From a523a0b21b66f4114598a340ea7346d24d6ea0e5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 09:06:09 -0400 Subject: make sure all the data is sent before disconnecting don't fail if waitForReadyRead() returns false but there's data available --- src/singleinstance.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index 7abf3879..aa62d40a 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "singleinstance.h" #include "utility.h" #include +#include #include static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; @@ -93,17 +94,33 @@ void SingleInstance::sendMessage(const QString &message) } socket.disconnectFromServer(); + socket.waitForDisconnected(); } void SingleInstance::receiveMessage() { QLocalSocket *socket = m_Server.nextPendingConnection(); - if (!socket->waitForReadyRead(s_Timeout)) { - reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); + if (!socket) { return; } + if (!socket->waitForReadyRead(s_Timeout)) { + // check if there are bytes available; if so, it probably means the data was + // already received by the time waitForReadyRead() was called and the + // connection has been closed + const auto av = socket->bytesAvailable(); + + if (av <= 0) { + MOBase::log::error( + "failed to receive data from secondary instance: {}", + socket->errorString()); + + reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); + return; + } + } + QString message = QString::fromUtf8(socket->readAll().constData()); emit messageSent(message); socket->disconnectFromServer(); -- cgit v1.3.1 From 33f5b59bee9fe5a1b3515737819ddfec4f596423 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 1 Nov 2019 10:14:17 -0400 Subject: added crash dump path to parameters, if a mechanism to customize it is ever created moved toString() for log level and dump type to usvfs, which needs them for logging --- src/organizercore.cpp | 5 ++++- src/organizercore.h | 3 ++- src/usvfsconnector.cpp | 51 ++++++-------------------------------------------- src/usvfsconnector.h | 6 ++++-- 4 files changed, 16 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index df824c2b..5613e8ce 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -485,10 +485,12 @@ void OrganizerCore::prepareVFS() void OrganizerCore::updateVFSParams( log::Levels logLevel, CrashDumpsType crashDumpsType, + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, QString executableBlacklist) { setGlobalCrashDumpsType(crashDumpsType); - m_USVFS.updateParams(logLevel, crashDumpsType, spawnDelay, executableBlacklist); + m_USVFS.updateParams( + logLevel, crashDumpsType, crashDumpsPath, spawnDelay, executableBlacklist); } void OrganizerCore::setLogLevel(log::Levels level) @@ -498,6 +500,7 @@ void OrganizerCore::setLogLevel(log::Levels level) updateVFSParams( m_Settings.diagnostics().logLevel(), m_Settings.diagnostics().crashDumpsType(), + QString::fromStdWString(crashDumpsPath()), m_Settings.diagnostics().spawnDelay(), m_Settings.executablesBlacklist()); diff --git a/src/organizercore.h b/src/organizercore.h index 5b7dd87b..0d0a092c 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -195,7 +195,8 @@ public: void updateVFSParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - std::chrono::seconds spawnDelay, QString executableBlacklist); + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist); void setLogLevel(MOBase::log::Levels level); diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 9a4fa742..311c6dd3 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -118,48 +118,6 @@ CrashDumpsType crashDumpsType(int type) } } -QString toString(LogLevel lv) -{ - switch (lv) - { - case LogLevel::Debug: - return "debug"; - - case LogLevel::Info: - return "info"; - - case LogLevel::Warning: - return "warning"; - - case LogLevel::Error: - return "error"; - - default: - return QString("%1").arg(static_cast(lv)); - } -} - -QString toString(CrashDumpsType t) -{ - switch (t) - { - case CrashDumpsType::None: - return "none"; - - case CrashDumpsType::Mini: - return "mini"; - - case CrashDumpsType::Data: - return "data"; - - case CrashDumpsType::Full: - return "full"; - - default: - return QString("%1").arg(static_cast(t)); - } -} - UsvfsConnector::UsvfsConnector() { using namespace std::chrono; @@ -188,9 +146,9 @@ UsvfsConnector::UsvfsConnector() " . log: {}\n" " . dump: {} ({})", SHMID, - toString(logLevel), + usvfsLogLevelToString(logLevel), dumpPath.c_str(), - toString(dumpType)); + usvfsCrashDumpTypeToString(dumpType)); usvfsCreateVFS(params); usvfsFreeParameters(params); @@ -263,14 +221,17 @@ void UsvfsConnector::updateMapping(const MappingType &mapping) void UsvfsConnector::updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - std::chrono::seconds spawnDelay, QString executableBlacklist) + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist) { using namespace std::chrono; usvfsParameters* p = usvfsCreateParameters(); + usvfsSetDebugMode(p, FALSE); usvfsSetLogLevel(p, toUsvfsLogLevel(logLevel)); usvfsSetCrashDumpType(p, crashDumpsType); + usvfsSetCrashDumpPath(p, crashDumpsPath.toStdString().c_str()); usvfsSetProcessDelay(p, duration_cast(spawnDelay).count()); usvfsUpdateParameters(p); diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index a4647eaf..d0071678 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -88,9 +88,11 @@ public: void updateParams( MOBase::log::Levels logLevel, CrashDumpsType crashDumpsType, - std::chrono::seconds spawnDelay, QString executableBlacklist); + const QString& crashDumpsPath, std::chrono::seconds spawnDelay, + QString executableBlacklist); - void updateForcedLibraries(const QList &forcedLibraries); + void updateForcedLibraries( + const QList &forcedLibraries); private: -- cgit v1.3.1 From d91580cc2669e2ab018c2aaab472cd763e5441d8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 18 Oct 2019 15:39:07 -0400 Subject: initial Spawner and SpawnedProcess added steam app id to spawn parameters removed threadHandle, unused moved most of the stuff from OrganizerCore::spawnBinaryProcess() to Spawner replaced m_UserInterface by m_MainWindow --- src/mainwindow.cpp | 4 +- src/organizercore.cpp | 192 ++++++++++++++++-------------------------------- src/organizercore.h | 9 ++- src/plugincontainer.cpp | 1 + src/spawn.cpp | 131 +++++++++++++++++++++++++++++++-- src/spawn.h | 34 +++++++++ 6 files changed, 230 insertions(+), 141 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 12ed40b3..b5b9ab0d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -411,7 +411,7 @@ MainWindow::MainWindow(Settings &settings m_Tutorial.expose("modList", m_OrganizerCore.modList()); m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); - m_OrganizerCore.setUserInterface(this, this); + m_OrganizerCore.setUserInterface(this); for (const QString &fileName : m_PluginContainer.pluginFileNames()) { installTranslator(QFileInfo(fileName).baseName()); } @@ -595,7 +595,7 @@ MainWindow::~MainWindow() cleanup(); m_PluginContainer.setUserInterface(nullptr, nullptr); - m_OrganizerCore.setUserInterface(nullptr, nullptr); + m_OrganizerCore.setUserInterface(nullptr); m_IntegratedBrowser.close(); delete ui; } catch (std::exception &e) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 5613e8ce..3c986004 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,5 +1,5 @@ #include "organizercore.h" - +#include "mainwindow.h" #include "delayedfilewriter.h" #include "guessedvalue.h" #include "imodinterface.h" @@ -127,7 +127,7 @@ QStringList toStringList(InputIterator current, InputIterator end) OrganizerCore::OrganizerCore(Settings &settings) - : m_UserInterface(nullptr) + : m_MainWindow(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) @@ -249,44 +249,43 @@ void OrganizerCore::updateExecutablesList() m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } -void OrganizerCore::setUserInterface(IUserInterface *userInterface, - QWidget *widget) +void OrganizerCore::setUserInterface(MainWindow* mainWindow) { storeSettings(); - m_UserInterface = userInterface; + m_MainWindow = mainWindow; - if (widget != nullptr) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), widget, + if (m_MainWindow != nullptr) { + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), widget, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow, SLOT(modlistChanged(QModelIndexList, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), widget, + connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget, + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, + connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, + connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(clearOverwrite()), widget, + connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow, SLOT(clearOverwrite())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), widget, + connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow, SLOT(modorder_changed())); - connect(&m_PluginList, SIGNAL(writePluginsList()), widget, + connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow, SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), widget, + connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow, SLOT(esplist_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow, SLOT(showMessage(QString))); } - m_InstallationManager.setParentWidget(widget); - m_Updater.setUserInterface(widget); + m_InstallationManager.setParentWidget(m_MainWindow); + m_Updater.setUserInterface(m_MainWindow); checkForUpdates(); } @@ -295,7 +294,7 @@ void OrganizerCore::checkForUpdates() { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (m_UserInterface != nullptr) { + if (m_MainWindow != nullptr) { m_Updater.testForUpdate(m_Settings); } } @@ -755,13 +754,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, int modIndex = ModInfo::getIndex(modName); if (modIndex != UINT_MAX) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && (m_UserInterface != nullptr) + if (hasIniTweaks && (m_MainWindow != nullptr) && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation( + m_MainWindow->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); @@ -822,13 +821,13 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (hasIniTweaks && m_UserInterface != nullptr + if (hasIniTweaks && m_MainWindow != nullptr && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation( + m_MainWindow->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } @@ -1250,11 +1249,11 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; - if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); } else { - // i.e. when running command line shortcuts there is no m_UserInterface + // i.e. when running command line shortcuts there is no user interface dlg.reset(new LockedDialog); dlg->show(); dlg->setEnabled(true); @@ -1262,8 +1261,8 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, } ON_BLOCK_EXIT([&]() { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); + if (m_MainWindow != nullptr) { + m_MainWindow->unlock(); } }); DWORD ignoreExitCode; @@ -1275,35 +1274,21 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, } -HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, - const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries) +HANDLE OrganizerCore::spawnBinaryProcess( + const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries) { spawn::SpawnParameters sp; sp.binary = binary; sp.arguments = arguments; sp.currentDirectory = currentDirectory; + sp.steamAppID = steamAppID; sp.hooked = true; prepareStart(); - QWidget *window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } - - if (!spawn::checkBinary(window, sp)) { - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkSteam(window, sp, managedGame()->gameDirectory(), steamAppID, m_Settings)) { - return INVALID_HANDLE_VALUE; - } - while (m_DirectoryUpdate) { ::Sleep(100); QCoreApplication::processEvents(); @@ -1315,72 +1300,25 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, } // TODO: should also pass arguments - if (m_AboutToRun(binary.absoluteFilePath())) { - try { - m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); - m_USVFS.updateForcedLibraries(forcedLibraries); - - } catch (const UsvfsConnectorException &e) { - log::debug(e.what()); - return INVALID_HANDLE_VALUE; - } catch (const std::exception &e) { - QMessageBox::warning(window, tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkEnvironment(window, sp)) { - return INVALID_HANDLE_VALUE; - } - - if (!spawn::checkBlacklist(window, sp, m_Settings)) { - return INVALID_HANDLE_VALUE; - } - - QString modsPath = settings().paths().mods(); - - // Check if this a request with either an executable or a working directory under our mods folder - // then will start the process in a virtualized "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = m_GamePlugin->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = m_GamePlugin->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; - } - - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), arguments); + if (!m_AboutToRun(binary.absoluteFilePath())) { + log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); + return INVALID_HANDLE_VALUE; + } - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + try { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); - return spawn::startBinary(window, sp); - } else { - log::debug("Spawning direct process <{}, {}, {}>", binPath, arguments, cwdPath); - return spawn::startBinary(window, sp); - } - } else { - log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); + } catch (const UsvfsConnectorException &e) { + log::debug(e.what()); + return INVALID_HANDLE_VALUE; + } catch (const std::exception &e) { + QMessageBox::warning(m_MainWindow, tr("Error"), e.what()); return INVALID_HANDLE_VALUE; } + + auto process = spawn::Spawner().spawn(m_MainWindow, m_GamePlugin, sp, m_Settings); + return process.releaseHandle(); } HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) @@ -1498,13 +1436,13 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) return true; ILockedWaitingForProcess* uilock = nullptr; - if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); } ON_BLOCK_EXIT([&] () { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); + if (m_MainWindow != nullptr) { + m_MainWindow->unlock(); } }); return waitForProcessCompletion(handle, exitCode, uilock); } @@ -1762,8 +1700,8 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives = m_DefaultArchives; } - if (m_UserInterface != nullptr) { - m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); + if (m_MainWindow != nullptr) { + m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives); } m_ArchivesInit = true; @@ -1879,8 +1817,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().writeImmediately(false); } std::vector archives = enabledArchives(); @@ -2064,8 +2002,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } modInfo->clearCaches(); @@ -2114,8 +2052,8 @@ void OrganizerCore::modStatusChanged(QList index) { origin.enable(false); } } - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } @@ -2290,8 +2228,8 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); + if (m_MainWindow != nullptr) { + m_MainWindow->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); diff --git a/src/organizercore.h b/src/organizercore.h index 0d0a092c..04c96ba6 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -3,7 +3,7 @@ #include "selfupdater.h" -#include "iuserinterface.h" //should be class IUserInterface; +#include "ilockedwaitingforprocess.h" #include "settings.h" #include "modlist.h" #include "modinfo.h" @@ -26,6 +26,8 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; +class MainWindow; + namespace MOBase { template class GuessedValue; class IModInterface; @@ -101,7 +103,7 @@ public: ~OrganizerCore(); - void setUserInterface(IUserInterface *userInterface, QWidget *widget); + void setUserInterface(MainWindow* mainWindow); void connectPlugins(PluginContainer *container); void disconnectPlugins(); @@ -328,8 +330,7 @@ private: static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - - IUserInterface *m_UserInterface; + MainWindow* m_MainWindow; PluginContainer *m_PluginContainer; QString m_GameName; MOBase::IPluginGame *m_GamePlugin; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index c0706ba8..767d3eb8 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -3,6 +3,7 @@ #include "organizerproxy.h" #include "report.h" #include +#include #include #include #include diff --git a/src/spawn.cpp b/src/spawn.cpp index a34230b2..3c7d64ce 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "envmodule.h" #include "settings.h" #include "settingsdialogworkarounds.h" +#include #include #include #include @@ -440,7 +441,7 @@ QMessageBox::StandardButton confirmBlacklisted( namespace spawn { -DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHandle) +DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) { BOOL inheritHandles = FALSE; @@ -494,7 +495,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle, HANDLE& threadHand } processHandle = pi.hProcess; - threadHandle = pi.hThread; + ::CloseHandle(pi.hThread); return ERROR_SUCCESS; } @@ -618,8 +619,7 @@ bool startSteam(QWidget* parent) (password.isEmpty() ? "no" : "yes")); HANDLE ph = INVALID_HANDLE_VALUE; - HANDLE th = INVALID_HANDLE_VALUE; - const auto e = spawn(sp, ph, th); + const auto e = spawn(sp, ph); if (e != ERROR_SUCCESS) { // make sure username and passwords are not shown @@ -772,17 +772,59 @@ bool checkBlacklist( } +void adjustForVirtualized( + const IPluginGame* game, SpawnParameters& sp, const Settings& settings) +{ + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} + + HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { - HANDLE processHandle, threadHandle; - const auto e = spawn(sp, processHandle, threadHandle); + HANDLE handle = INVALID_HANDLE_VALUE; + const auto e = spawn::spawn(sp, handle); switch (e) { case ERROR_SUCCESS: { - ::CloseHandle(threadHandle); - return processHandle; + return handle; } case ERROR_ELEVATION_REQUIRED: @@ -799,6 +841,79 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } + + +SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp) + : m_handle(handle), m_parameters(std::move(sp)) +{ +} + +SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) + : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) +{ + other.m_handle = INVALID_HANDLE_VALUE; +} + +SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) +{ + if (this != &other) { + destroy(); + + m_handle = other.m_handle; + other.m_handle = INVALID_HANDLE_VALUE; + + m_parameters = std::move(other.m_parameters); + } + + return *this; +} + +SpawnedProcess::~SpawnedProcess() +{ + destroy(); +} + +HANDLE SpawnedProcess::releaseHandle() +{ + const auto h = m_handle; + m_handle = INVALID_HANDLE_VALUE; + return h; +} + +void SpawnedProcess::destroy() +{ + if (m_handle != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } +} + + +SpawnedProcess Spawner::spawn( + QWidget* parent, const IPluginGame* game, + SpawnParameters sp, Settings& settings) +{ + if (!checkBinary(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!spawn::checkEnvironment(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!spawn::checkBlacklist(parent, sp, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + adjustForVirtualized(game, sp, settings); + + return {startBinary(parent, sp), sp}; +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index 31b44739..d2853cd5 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include class Settings; +namespace MOBase { class IPluginGame; } namespace spawn { @@ -46,6 +47,7 @@ struct SpawnParameters QFileInfo binary; QString arguments; QDir currentDirectory; + QString steamAppID; bool hooked = false; HANDLE stdOut = INVALID_HANDLE_VALUE; HANDLE stdErr = INVALID_HANDLE_VALUE; @@ -69,6 +71,38 @@ bool checkBlacklist( **/ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp); + +class SpawnedProcess +{ +public: + SpawnedProcess(HANDLE handle, SpawnParameters sp); + + SpawnedProcess(const SpawnedProcess&) = delete; + SpawnedProcess& operator=(const SpawnedProcess&) = delete; + SpawnedProcess(SpawnedProcess&& other); + SpawnedProcess& operator=(SpawnedProcess&& other); + ~SpawnedProcess(); + + HANDLE releaseHandle(); + +private: + HANDLE m_handle; + SpawnParameters m_parameters; + + void destroy(); +}; + + +class Spawner +{ +public: + SpawnedProcess spawn( + QWidget* parent, const MOBase::IPluginGame* game, + SpawnParameters sp, Settings& settings); + +private: +}; + } // namespace -- cgit v1.3.1 From 74631ae88e36f7ccc1ea80c5a56d686637c325c6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 18 Oct 2019 22:42:47 -0400 Subject: merged spawnBinaryDirect() and spawnBinaryProcess() --- src/organizercore.cpp | 98 +++++++++++++++++++++++++-------------------------- src/organizercore.h | 23 +++++------- 2 files changed, 56 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3c986004..3bb6af70 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1208,15 +1208,17 @@ bool OrganizerCore::previewFile( return true; } -void OrganizerCore::spawnBinary(const QFileInfo &binary, - const QString &arguments, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries) +void OrganizerCore::spawnBinary( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries) { DWORD processExitCode = 0; - HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); + HANDLE processHandle = spawnBinaryDirect( + binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, + customOverwrite, forcedLibraries, &processExitCode); + if (processHandle != INVALID_HANDLE_VALUE) { refreshDirectoryStructure(); // need to remove our stored load order because it may be outdated if a foreign tool changed the @@ -1235,50 +1237,12 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, } } -HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, - const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) -{ - HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite, forcedLibraries); - if (Settings::instance().interface().lockGUI() && processHandle != INVALID_HANDLE_VALUE) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; - - if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } - - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); - - DWORD ignoreExitCode; - waitForProcessCompletion(processHandle, exitCode ? exitCode : &ignoreExitCode, uilock); - cycleDiagnostics(); - } - - return processHandle; -} - - -HANDLE OrganizerCore::spawnBinaryProcess( +HANDLE OrganizerCore::spawnBinaryDirect( const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, - const QList &forcedLibraries) + const QList &forcedLibraries, + LPDWORD exitCode) { spawn::SpawnParameters sp; sp.binary = binary; @@ -1317,10 +1281,44 @@ HANDLE OrganizerCore::spawnBinaryProcess( return INVALID_HANDLE_VALUE; } - auto process = spawn::Spawner().spawn(m_MainWindow, m_GamePlugin, sp, m_Settings); - return process.releaseHandle(); + HANDLE handle = spawn::Spawner() + .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings) + .releaseHandle(); + + if (handle == INVALID_HANDLE_VALUE) { + // failed + return INVALID_HANDLE_VALUE; + } + + if (Settings::instance().interface().lockGUI()) { + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + + ON_BLOCK_EXIT([&]() { + if (m_MainWindow != nullptr) { + m_MainWindow->unlock(); + } }); + + DWORD ignoreExitCode; + waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + cycleDiagnostics(); + } + + return handle; } + HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) { if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) diff --git a/src/organizercore.h b/src/organizercore.h index 04c96ba6..4a8f90d1 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -168,21 +168,6 @@ public: const QString &customOverwrite = "", const QList &forcedLibraries = QList()); - HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList(), - LPDWORD exitCode = nullptr); - - HANDLE spawnBinaryProcess(const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList()); - void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -315,6 +300,14 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); + HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries = QList(), + LPDWORD exitCode = nullptr); + bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); private slots: -- cgit v1.3.1 From 4fb79cae456986795be3f504d440048e476dffb3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 18 Oct 2019 23:11:19 -0400 Subject: renamed executeFileVirtualized() to runFile() renamed spawnBinary() to runExecutable() renamed spawnBinaryDirect() to spawnAndWait() --- src/organizercore.cpp | 78 +++++++++++++++++++++++++-------------------------- src/organizercore.h | 15 +++++----- 2 files changed, 47 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3bb6af70..4270f3b4 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1046,40 +1046,6 @@ bool OrganizerCore::getFileExecutionContext( } } -bool OrganizerCore::executeFileVirtualized( - QWidget* parent, const QFileInfo& targetInfo) -{ - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; - - if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { - return false; - } - - switch (type) - { - case FileExecutionTypes::Executable: { - spawnBinaryDirect( - binaryInfo, arguments, currentProfile()->name(), - targetInfo.absolutePath(), "", ""); - - return true; - } - - case FileExecutionTypes::Other: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - - return true; - } - } - - // nop - return false; -} - bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { @@ -1208,14 +1174,48 @@ bool OrganizerCore::previewFile( return true; } -void OrganizerCore::spawnBinary( +bool OrganizerCore::runFile( + QWidget* parent, const QFileInfo& targetInfo) +{ + QFileInfo binaryInfo; + QString arguments; + FileExecutionTypes type; + + if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { + return false; + } + + switch (type) + { + case FileExecutionTypes::Executable: { + runExecutable( + binaryInfo, arguments, currentProfile()->name(), + targetInfo.absolutePath()); + + return true; + } + + case FileExecutionTypes::Other: { + ::ShellExecuteW(nullptr, L"open", + ToWString(targetInfo.absoluteFilePath()).c_str(), + nullptr, nullptr, SW_SHOWNORMAL); + + return true; + } + } + + // nop + return false; +} + +void OrganizerCore::runExecutable( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, const QList &forcedLibraries) { DWORD processExitCode = 0; - HANDLE processHandle = spawnBinaryDirect( + HANDLE processHandle = spawnAndWait( binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); @@ -1237,7 +1237,7 @@ void OrganizerCore::spawnBinary( } } -HANDLE OrganizerCore::spawnBinaryDirect( +HANDLE OrganizerCore::spawnAndWait( const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, @@ -1334,7 +1334,7 @@ HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) forcedLibaries.clear(); } - return spawnBinaryDirect( + return spawnAndWait( exe.binaryInfo(), exe.arguments(), m_CurrentProfile->name(), exe.workingDirectory().length() != 0 @@ -1419,7 +1419,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, if (ignoreCustomOverwrite) customOverwrite.clear(); - return spawnBinaryDirect(binary, + return spawnAndWait(binary, arguments, profileName, currentDirectory, diff --git a/src/organizercore.h b/src/organizercore.h index 4a8f90d1..6628452c 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -158,15 +158,16 @@ public: QWidget* parent, const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); - bool executeFileVirtualized(QWidget* parent, const QFileInfo& targetInfo); bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); - void spawnBinary(const QFileInfo &binary, const QString &arguments = "", - const QDir ¤tDirectory = QDir(), - const QString &steamAppID = "", - const QString &customOverwrite = "", - const QList &forcedLibraries = QList()); + bool runFile(QWidget* parent, const QFileInfo& targetInfo); + + void runExecutable( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID={}, + const QString &customOverwrite={}, + const QList &forcedLibraries={}); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -300,7 +301,7 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); - HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, + HANDLE spawnAndWait(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, -- cgit v1.3.1 From e43500eaf5d40bcd28ba5a820cdbc754cfb47e40 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 02:28:20 -0400 Subject: renamed runExecutable() to runExecutablefile() added runExecutable() for Executable class, also used by runShortcut() --- src/mainwindow.cpp | 24 ++----------- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 79 +++++++++++++++++++++++++----------------- src/organizercore.h | 10 ++++-- 4 files changed, 59 insertions(+), 56 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5b9ab0d..2474fdc0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1542,26 +1542,8 @@ void MainWindow::startExeAction() } action->setEnabled(false); - 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); + m_OrganizerCore.runExecutable(*itor); action->setEnabled(true); - } void MainWindow::activateSelectedProfile() @@ -2387,7 +2369,7 @@ void MainWindow::on_startButton_clicked() forcedLibraries.clear(); } - m_OrganizerCore.spawnBinary( + m_OrganizerCore.runExecutableFile( selectedExecutable->binaryInfo(), selectedExecutable->arguments(), selectedExecutable->workingDirectory().length() != 0 ? @@ -5477,7 +5459,7 @@ void MainWindow::openDataFile() } QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - m_OrganizerCore.executeFileVirtualized(this, targetInfo); + m_OrganizerCore.runFile(this, targetInfo); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 36559a75..68b1be6b 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().executeFileVirtualized(parentWidget(), item->fileName()); + core().runFile(parentWidget(), item->fileName()); return true; }); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 4270f3b4..6f36ee3e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1188,7 +1188,7 @@ bool OrganizerCore::runFile( switch (type) { case FileExecutionTypes::Executable: { - runExecutable( + runExecutableFile( binaryInfo, arguments, currentProfile()->name(), targetInfo.absolutePath()); @@ -1208,25 +1208,33 @@ bool OrganizerCore::runFile( return false; } -void OrganizerCore::runExecutable( +bool OrganizerCore::runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite, - const QList &forcedLibraries) + const QList &forcedLibraries, + bool refresh) { DWORD processExitCode = 0; HANDLE processHandle = spawnAndWait( binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, forcedLibraries, &processExitCode); - if (processHandle != INVALID_HANDLE_VALUE) { + if (processHandle == INVALID_HANDLE_VALUE) { + // failed + return false; + } + + if (refresh) { refreshDirectoryStructure(); + // need to remove our stored load order because it may be outdated if a foreign tool changed the // file time. After removing that file, refreshESPList will use the file time as the order if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { log::debug("removing loadorder.txt"); QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } + refreshDirectoryStructure(); refreshESPList(true); @@ -1235,6 +1243,42 @@ void OrganizerCore::runExecutable( //These callbacks should not fiddle with directoy structure and ESPs. m_FinishedRun(binary.absoluteFilePath(), processExitCode); } + + return true; +} + +bool OrganizerCore::runExecutable(const Executable& exe, bool refresh) +{ + const QString customOverwrite = m_CurrentProfile->setting( + "custom_overwrites", exe.title()).toString(); + + QList forcedLibraries; + + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + + return runExecutableFile( + exe.binaryInfo(), + exe.arguments(), + exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), + exe.steamAppID(), + customOverwrite, + forcedLibraries, + refresh); +} + +bool OrganizerCore::runShortcut(const MOShortcut& shortcut) +{ + if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } + + const Executable& exe = m_ExecutablesList.get(shortcut.executable()); + return runExecutable(exe, false); } HANDLE OrganizerCore::spawnAndWait( @@ -1318,33 +1362,6 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } - -HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) -{ - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); - - const Executable& exe = m_ExecutablesList.get(shortcut.executable()); - - auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable()); - if (!m_CurrentProfile->forcedLibrariesEnabled(shortcut.executable())) { - forcedLibaries.clear(); - } - - return spawnAndWait( - exe.binaryInfo(), exe.arguments(), - m_CurrentProfile->name(), - exe.workingDirectory().length() != 0 - ? exe.workingDirectory() - : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - "", - forcedLibaries); -} - HANDLE OrganizerCore::startApplication(const QString &executable, const QStringList &args, const QString &cwd, diff --git a/src/organizercore.h b/src/organizercore.h index 6628452c..ca87a05c 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -163,11 +163,16 @@ public: bool runFile(QWidget* parent, const QFileInfo& targetInfo); - void runExecutable( + bool runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID={}, const QString &customOverwrite={}, - const QList &forcedLibraries={}); + const QList &forcedLibraries={}, + bool refresh=true); + + bool runExecutable(const Executable& exe, bool refresh=true); + + bool runShortcut(const MOShortcut& shortcut); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -222,7 +227,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - HANDLE runShortcut(const MOShortcut& shortcut); HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); -- cgit v1.3.1 From b60f2aa786cf748e1839f4320604030863279032 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 02:52:54 -0400 Subject: renamed startApplication() to runExecutableOrExecutableFile() --- src/main.cpp | 3 ++- src/organizercore.cpp | 40 +++++++++++++++++++--------------------- src/organizercore.h | 7 ++++++- src/organizerproxy.cpp | 12 ++++++++---- 4 files changed, 35 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 776c3775..49ecc084 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -653,7 +653,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary try { - organizer.startApplication(exeName, arguments, QString(), QString()); + organizer.runExecutableOrExecutableFile( + exeName, arguments, QString(), QString()); return 0; } catch (const std::exception &e) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 6f36ee3e..6bf2c290 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1362,45 +1362,44 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -HANDLE OrganizerCore::startApplication(const QString &executable, - const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) +HANDLE OrganizerCore::runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) { - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; QString profileName = profile; - if (profile.length() == 0) { + if (profile == "") { if (m_CurrentProfile != nullptr) { profileName = m_CurrentProfile->name(); } else { throw MyException(tr("No profile set")); } } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; QString steamAppID; QString customOverwrite; QList forcedLibraries; + if (executable.contains('\\') || executable.contains('/')) { // file path binary = QFileInfo(executable); if (binary.isRelative()) { // relative path, should be relative to game directory - binary = QFileInfo( - managedGame()->gameDirectory().absoluteFilePath(executable)); + binary = managedGame()->gameDirectory().absoluteFilePath(executable); } - if (cwd.length() == 0) { + + if (currentDirectory == "") { currentDirectory = binary.absolutePath(); } + try { - const Executable &exe = m_ExecutablesList.getByBinary(binary); + const Executable& exe = m_ExecutablesList.getByBinary(binary); steamAppID = exe.steamAppID(); - customOverwrite - = m_CurrentProfile->setting("custom_overwrites", exe.title()) - .toString(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); } @@ -1412,9 +1411,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, try { const Executable &exe = m_ExecutablesList.get(executable); steamAppID = exe.steamAppID(); - customOverwrite - = m_CurrentProfile->setting("custom_overwrites", exe.title()) - .toString(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); } @@ -1422,7 +1419,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, arguments = exe.arguments(); } binary = exe.binaryInfo(); - if (cwd.length() == 0) { + if (currentDirectory == "") { currentDirectory = exe.workingDirectory(); } } catch (const std::runtime_error &) { @@ -1433,6 +1430,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, if (!forcedCustomOverwrite.isEmpty()) customOverwrite = forcedCustomOverwrite; + if (ignoreCustomOverwrite) customOverwrite.clear(); diff --git a/src/organizercore.h b/src/organizercore.h index ca87a05c..fa05a20f 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -174,6 +174,12 @@ public: bool runShortcut(const MOShortcut& shortcut); + HANDLE runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + + void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -227,7 +233,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); bool onModInstalled(const std::function &func); diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index d6996b3a..2ea1761a 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -107,10 +107,14 @@ QString OrganizerProxy::pluginDataPath() const return m_Proxied->pluginDataPath(); } -HANDLE OrganizerProxy::startApplication(const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) -{ - return m_Proxied->startApplication(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); +HANDLE OrganizerProxy::startApplication( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + return m_Proxied->runExecutableOrExecutableFile( + executable, args, cwd, profile, + forcedCustomOverwrite, ignoreCustomOverwrite); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const -- cgit v1.3.1 From 4e8dcc5157706e1478396179f5dc11305532b159 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 04:27:22 -0400 Subject: moved findJavaInstallation() and getFileExecutionContext() to spawn fixed env::get() returning garbage after value --- src/editexecutablesdialog.cpp | 3 +- src/env.cpp | 31 +++-- src/mainwindow.cpp | 68 +++++------ src/organizercore.cpp | 264 +++++++++++++++--------------------------- src/organizercore.h | 12 -- src/spawn.cpp | 183 ++++++++++++++++++++++++++++- src/spawn.h | 19 +++ 7 files changed, 351 insertions(+), 229 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8535b7a7..32b31357 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "modlist.h" #include "forcedloaddialog.h" #include "organizercore.h" +#include "spawn.h" #include #include @@ -800,7 +801,7 @@ QFileInfo EditExecutablesDialog::browseBinary(const QString& initial) void EditExecutablesDialog::setJarBinary(const QFileInfo& binary) { - auto java = OrganizerCore::findJavaInstallation(binary.absoluteFilePath()); + auto java = spawn::findJavaInstallation(binary.absoluteFilePath()); if (java.isEmpty()) { QMessageBox::information( diff --git a/src/env.cpp b/src/env.cpp index 78b5dc96..507607d1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -190,19 +190,36 @@ QString setPath(const QString& s) QString get(const QString& name) { - std::wstring s(4000, L' '); + std::size_t bufferSize = 4000; + auto buffer = std::make_unique(bufferSize); DWORD realSize = ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); - if (realSize > s.size()) { - s.resize(realSize); + if (realSize > bufferSize) { + bufferSize = realSize; + buffer = std::make_unique(bufferSize); - ::GetEnvironmentVariableW( - name.toStdWString().c_str(), s.data(), static_cast(s.size())); + realSize = ::GetEnvironmentVariableW( + name.toStdWString().c_str(), + buffer.get(), static_cast(bufferSize)); } - return QString::fromStdWString(s); + if (realSize == 0) { + const auto e = ::GetLastError(); + + // don't log if not found + if (e != ERROR_ENVVAR_NOT_FOUND) { + log::error( + "failed to get environment variable '{}', {}", + name, formatSystemMessage(e)); + } + + return {}; + } + + return QString::fromWCharArray(buffer.get(), realSize); } QString set(const QString& n, const QString& v) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2474fdc0..0181a335 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5295,43 +5295,42 @@ void MainWindow::addAsExecutable() return; } - using FileExecutionTypes = OrganizerCore::FileExecutionTypes; + const QFileInfo target(m_ContextItem->data(0, Qt::UserRole).toString()); + const auto fec = spawn::getFileExecutionContext(this, target); - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; - - if (!OrganizerCore::getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { - return; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - QString name = QInputDialog::getText(this, tr("Enter Name"), - tr("Please enter a name for the executable"), QLineEdit::Normal, - targetInfo.completeBaseName()); - - if (!name.isEmpty()) { - //Note: If this already exists, you'll lose custom settings - m_OrganizerCore.executablesList()->setExecutable(Executable() - .title(name) - .binaryInfo(binaryInfo) - .arguments(arguments) - .workingDirectory(targetInfo.absolutePath())); - - refreshExecutablesList(); - } - - break; + case spawn::FileExecutionTypes::Executable: + { + const QString name = QInputDialog::getText( + this, tr("Enter Name"), + tr("Enter a name for the executable"), + QLineEdit::Normal, + target.completeBaseName()); + + if (!name.isEmpty()) { + //Note: If this already exists, you'll lose custom settings + m_OrganizerCore.executablesList()->setExecutable(Executable() + .title(name) + .binaryInfo(fec.binary) + .arguments(fec.arguments) + .workingDirectory(target.absolutePath())); + + refreshExecutablesList(); } - case FileExecutionTypes::Other: // fall-through - default: { - QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); - break; - } + break; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + QMessageBox::information( + this, tr("Not an executable"), + tr("This is not a recognized executable.")); + + break; + } } } @@ -5458,7 +5457,8 @@ void MainWindow::openDataFile() return; } - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); m_OrganizerCore.runFile(this, targetInfo); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 6bf2c290..78f9517c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -976,76 +976,6 @@ 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)) { - log::debug( - "failed to determine binary type of \"{}\": {}", - QString::fromWCharArray(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) -{ - QString extension = targetInfo.suffix(); - if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) || - (extension.compare("com", Qt::CaseInsensitive) == 0) || - (extension.compare("bat", Qt::CaseInsensitive) == 0)) { - binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); - arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { - binaryInfo = targetInfo; - type = FileExecutionTypes::Executable; - return true; - } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - auto java = findJavaInstallation(targetInfo.absoluteFilePath()); - - if (java.isEmpty()) { - java = QFileDialog::getOpenFileName( - parent, QObject::tr("Select binary"), - QString(), QObject::tr("Binary") + " (*.exe)"); - } - - if (java.isEmpty()) { - return false; - } - - binaryInfo = QFileInfo(java); - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - type = FileExecutionTypes::Executable; - - return true; - } else { - type = FileExecutionTypes::Other; - return true; - } -} - bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { @@ -1177,35 +1107,23 @@ bool OrganizerCore::previewFile( bool OrganizerCore::runFile( QWidget* parent, const QFileInfo& targetInfo) { - QFileInfo binaryInfo; - QString arguments; - FileExecutionTypes type; + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { - return false; - } - - switch (type) + switch (fec.type) { - case FileExecutionTypes::Executable: { - runExecutableFile( - binaryInfo, arguments, currentProfile()->name(), - targetInfo.absolutePath()); - + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); return true; } - case FileExecutionTypes::Other: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - - return true; + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + const auto r = shell::Open(targetInfo.absoluteFilePath()); + return r.success(); } } - - // nop - return false; } bool OrganizerCore::runExecutableFile( @@ -1281,6 +1199,87 @@ bool OrganizerCore::runShortcut(const MOShortcut& shortcut) return runExecutable(exe, false); } +HANDLE OrganizerCore::runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + QString profileName = profile; + if (profile == "") { + if (m_CurrentProfile != nullptr) { + profileName = m_CurrentProfile->name(); + } else { + throw MyException(tr("No profile set")); + } + } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString steamAppID; + QString customOverwrite; + QList forcedLibraries; + + if (executable.contains('\\') || executable.contains('/')) { + // file path + + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = managedGame()->gameDirectory().absoluteFilePath(executable); + } + + if (currentDirectory == "") { + currentDirectory = binary.absolutePath(); + } + + try { + const Executable& exe = m_ExecutablesList.getByBinary(binary); + steamAppID = exe.steamAppID(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_ExecutablesList.get(executable); + steamAppID = exe.steamAppID(); + customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); + } + if (arguments == "") { + arguments = exe.arguments(); + } + binary = exe.binaryInfo(); + if (currentDirectory == "") { + currentDirectory = exe.workingDirectory(); + } + } catch (const std::runtime_error &) { + log::warn("\"{}\" not set up as executable", executable); + binary = QFileInfo(executable); + } + } + + if (!forcedCustomOverwrite.isEmpty()) + customOverwrite = forcedCustomOverwrite; + + if (ignoreCustomOverwrite) + customOverwrite.clear(); + + return spawnAndWait(binary, + arguments, + profileName, + currentDirectory, + steamAppID, + customOverwrite, + forcedLibraries); +} + HANDLE OrganizerCore::spawnAndWait( const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID, @@ -1362,87 +1361,6 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -HANDLE OrganizerCore::runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) -{ - QString profileName = profile; - if (profile == "") { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } - } - - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; - - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = managedGame()->gameDirectory().absoluteFilePath(executable); - } - - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); - } - - try { - const Executable& exe = m_ExecutablesList.getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - } catch (const std::runtime_error &) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - if (arguments == "") { - arguments = exe.arguments(); - } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); - } - } catch (const std::runtime_error &) { - log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); - } - } - - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); - - return spawnAndWait(binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); -} - bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { if (!Settings::instance().interface().lockGUI()) diff --git a/src/organizercore.h b/src/organizercore.h index fa05a20f..f802c8cb 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -91,12 +91,6 @@ private: typedef boost::signals2::signal SignalModInstalled; public: - enum class FileExecutionTypes - { - Executable = 1, - Other = 2 - }; - static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } OrganizerCore(Settings &settings); @@ -152,12 +146,6 @@ public: void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } void loggedInAction(QWidget* parent, std::function f); - static QString findJavaInstallation(const QString& jarFile={}); - - static bool getFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); - bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); diff --git a/src/spawn.cpp b/src/spawn.cpp index 3c7d64ce..dd93bfaa 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -901,11 +901,11 @@ SpawnedProcess Spawner::spawn( return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkEnvironment(parent, sp)) { + if (!checkEnvironment(parent, sp)) { return {INVALID_HANDLE_VALUE, sp}; } - if (!spawn::checkBlacklist(parent, sp, settings)) { + if (!checkBlacklist(parent, sp, settings)) { return {INVALID_HANDLE_VALUE, sp}; } @@ -914,6 +914,185 @@ SpawnedProcess Spawner::spawn( return {startBinary(parent, sp), sp}; } + +QString getExecutableForJarFile(const QString& jarFile) +{ + const std::wstring jarFileW = jarFile.toStdWString(); + + WCHAR buffer[MAX_PATH]; + + const auto hinst = ::FindExecutableW(jarFileW.c_str(), nullptr, buffer); + const auto r = static_cast(reinterpret_cast(hinst)); + + // anything <= 32 signals failure + if (r <= 32) { + log::warn( + "failed to find executable associated with file '{}', {}", + jarFile, shell::formatError(r)); + + return {}; + } + + DWORD binaryType = 0; + + if (!::GetBinaryTypeW(buffer, &binaryType)) { + const auto e = ::GetLastError(); + + log::warn( + "failed to determine binary type of '{}', {}", + QString::fromWCharArray(buffer), formatSystemMessage(e)); + + return {}; + } + + if (binaryType != SCS_32BIT_BINARY && binaryType != SCS_64BIT_BINARY) { + log::warn( + "unexpected binary type {} for file '{}'", + binaryType, QString::fromWCharArray(buffer)); + + return {}; + } + + return QString::fromWCharArray(buffer); +} + +QString getJavaHome() +{ + const QString key = "HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment"; + const QString value = "CurrentVersion"; + + QSettings reg(key, QSettings::NativeFormat); + + if (!reg.contains(value)) { + log::warn("key '{}\\{}' doesn't exist", key, value); + return {}; + } + + const QString currentVersion = reg.value("CurrentVersion").toString(); + const QString javaHome = QString("%1/JavaHome").arg(currentVersion); + + if (!reg.contains(javaHome)) { + log::warn( + "java version '{}' was found at '{}\\{}', but '{}\\{}' doesn't exist", + currentVersion, key, value, key, javaHome); + + return {}; + } + + const auto path = reg.value(javaHome).toString(); + return path + "\\bin\\javaw.exe"; +} + +QString findJavaInstallation(const QString& jarFile) +{ + // try to find java automatically based on the given jar file + if (!jarFile.isEmpty()) { + const auto s = getExecutableForJarFile(jarFile); + if (!s.isEmpty()) { + return s; + } + } + + // second attempt: look to the registry + const auto s = getJavaHome(); + if (!s.isEmpty()) { + return s; + } + + // not found + return {}; +} + +bool isBatchFile(const QFileInfo& target) +{ + const auto batchExtensions = {"cmd", "bat"}; + + const QString extension = target.suffix(); + for (auto&& e : batchExtensions) { + if (extension.compare(e, Qt::CaseInsensitive) == 0) { + return true; + } + } + + return false; +} + +bool isExeFile(const QFileInfo& target) +{ + return (target.suffix().compare("exe", Qt::CaseInsensitive) == 0); +} + +bool isJavaFile(const QFileInfo& target) +{ + return (target.suffix().compare("jar", Qt::CaseInsensitive) == 0); +} + +QFileInfo getCmdPath() +{ + const auto p = env::get("COMSPEC2"); + if (!p.isEmpty()) { + return p; + } + + QString systemDirectory; + + const std::size_t buffer_size = 1000; + wchar_t buffer[buffer_size + 1] = {}; + + const auto length = ::GetSystemDirectoryW(buffer, buffer_size); + if (length != 0) { + systemDirectory = QString::fromWCharArray(buffer, length); + + if (!systemDirectory.endsWith("\\")) { + systemDirectory += "\\"; + } + } else { + systemDirectory = "C:\\Windows\\System32\\"; + } + + return systemDirectory + "cmd.exe"; +} + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target) +{ + if (isExeFile(target)) { + return { + target, + "", + FileExecutionTypes::Executable + }; + } + + if (isBatchFile(target)) { + return { + getCmdPath(), + QString("/C \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + + if (isJavaFile(target)) { + auto java = findJavaInstallation(target.absoluteFilePath()); + + if (java.isEmpty()) { + java = QFileDialog::getOpenFileName( + parent, QObject::tr("Select binary"), + QString(), QObject::tr("Binary") + " (*.exe)"); + } + + if (!java.isEmpty()) { + return { + QFileInfo(java), + QString("-jar \"%1\"").arg(QDir::toNativeSeparators(target.absoluteFilePath())), + FileExecutionTypes::Executable + }; + } + } + + return {{}, {}, FileExecutionTypes::Other}; +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index d2853cd5..866e1795 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -103,6 +103,25 @@ public: private: }; + +enum class FileExecutionTypes +{ + Executable = 1, + Other +}; + +struct FileExecutionContext +{ + QFileInfo binary; + QString arguments; + FileExecutionTypes type; +}; + +QString findJavaInstallation(const QString& jarFile); + +FileExecutionContext getFileExecutionContext( + QWidget* parent, const QFileInfo& target); + } // namespace -- cgit v1.3.1 From 8f24f6298f62e36db1c7a624052e70b41c5e7e27 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 05:27:39 -0400 Subject: wait for executable when opening files --- src/organizercore.cpp | 22 ++++++++++++++++++++-- src/organizercore.h | 5 ++++- src/spawn.cpp | 4 ++-- 3 files changed, 26 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 78f9517c..d3f4a83c 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1120,8 +1120,19 @@ bool OrganizerCore::runFile( case spawn::FileExecutionTypes::Other: // fall-through default: { - const auto r = shell::Open(targetInfo.absoluteFilePath()); - return r.success(); + auto r = shell::Open(targetInfo.absoluteFilePath()); + if (!r.success()) { + return false; + } + + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer) + if (r.processHandle() != INVALID_HANDLE_VALUE) { + // steal because it gets closed after the wait + return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); + } + + return true; } } } @@ -1333,6 +1344,13 @@ HANDLE OrganizerCore::spawnAndWait( return INVALID_HANDLE_VALUE; } + waitForProcessCompletionWithLock(handle, exitCode); + return handle; +} + +bool OrganizerCore::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ if (Settings::instance().interface().lockGUI()) { std::unique_ptr dlg; ILockedWaitingForProcess* uilock = nullptr; diff --git a/src/organizercore.h b/src/organizercore.h index f802c8cb..3d3c7325 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -306,7 +306,10 @@ private: const QList &forcedLibraries = QList(), LPDWORD exitCode = nullptr); - bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + + bool waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); private slots: diff --git a/src/spawn.cpp b/src/spawn.cpp index dd93bfaa..fe1e9e3e 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1029,7 +1029,7 @@ bool isJavaFile(const QFileInfo& target) QFileInfo getCmdPath() { - const auto p = env::get("COMSPEC2"); + const auto p = env::get("COMSPEC"); if (!p.isEmpty()) { return p; } @@ -1111,7 +1111,7 @@ bool helperExec( { SHELLEXECUTEINFOW execInfo = {}; - ULONG flags = SEE_MASK_FLAG_NO_UI ; + ULONG flags = SEE_MASK_FLAG_NO_UI; if (!async) flags |= SEE_MASK_NOCLOSEPROCESS; -- cgit v1.3.1 From bff5a22f48b933fe9eba3a15497882ddf2a03990 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 24 Oct 2019 07:11:08 -0400 Subject: spawning an executable now only waits for that particular process added waitForAllUSVFSProcesses() to OrganizerCore, used when closing MO --- src/envmodule.cpp | 73 ++++++++++++++++++++++--- src/envmodule.h | 3 ++ src/mainwindow.cpp | 14 ++--- src/organizercore.cpp | 145 ++++++++++++++++++++++++++------------------------ src/organizercore.h | 11 +++- src/spawn.cpp | 63 ++++++++++++++++++++++ src/spawn.h | 14 +++++ 7 files changed, 233 insertions(+), 90 deletions(-) (limited to 'src') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 3f1f8912..abbe02e5 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -408,7 +408,8 @@ std::vector getLoadedModules() } -std::vector getRunningProcesses() +template +void forEachRunningProcess(F&& f) { HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); @@ -416,7 +417,7 @@ std::vector getRunningProcesses() { const auto e = GetLastError(); log::error("CreateToolhelp32Snapshot() failed, {}", formatSystemMessage(e)); - return {}; + return; } PROCESSENTRY32 entry = {}; @@ -427,16 +428,14 @@ std::vector getRunningProcesses() if (!Process32First(snapshot.get(), &entry)) { const auto e = GetLastError(); log::error("Process32First() failed, {}", formatSystemMessage(e)); - return {}; + return; } - std::vector v; - for (;;) { - v.push_back(Process( - entry.th32ProcessID, - QString::fromStdWString(entry.szExeFile))); + if (!f(entry)) { + break; + } // next process if (!Process32Next(snapshot.get(), &entry)) @@ -450,8 +449,66 @@ std::vector getRunningProcesses() break; } } +} + +std::vector getRunningProcesses() +{ + std::vector v; + + forEachRunningProcess([&](auto&& entry) { + v.push_back(Process( + entry.th32ProcessID, + QString::fromStdWString(entry.szExeFile))); + + return true; + }); return v; } +QString getProcessName(HANDLE process) +{ + const QString badName = "unknown"; + + if (process == 0 || process == INVALID_HANDLE_VALUE) { + return badName; + } + + const DWORD bufferSize = MAX_PATH; + wchar_t buffer[bufferSize + 1] = {}; + + const auto realSize = ::GetProcessImageFileNameW(process, buffer, bufferSize); + + if (realSize == 0) { + const auto e = ::GetLastError(); + log::error("GetProcessImageFileNameW() failed, {}", formatSystemMessage(e)); + return badName; + } + + auto s = QString::fromWCharArray(buffer, realSize); + + const auto lastSlash = s.lastIndexOf("\\"); + if (lastSlash != -1) { + s = s.mid(lastSlash + 1); + } + + return s; +} + +DWORD getProcessParentID(DWORD pid) +{ + DWORD ppid = 0; + + forEachRunningProcess([&](auto&& entry) { + if (entry.th32ProcessID == pid) { + ppid = entry.th32ParentProcessID; + return false; + } + + return true; + }); + + return ppid; +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index deb7520f..212f6f7b 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -120,6 +120,9 @@ private: std::vector getRunningProcesses(); std::vector getLoadedModules(); +QString getProcessName(HANDLE process); +DWORD getProcessParentID(DWORD pid); + } // namespace env #endif // ENV_MODULE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0181a335..bbb63333 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1311,16 +1311,10 @@ bool MainWindow::canExit() } } - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); - if (injected_process_still_running != INVALID_HANDLE_VALUE) - { - m_exitAfterWait = true; - m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_exitAfterWait) { // if operation cancelled - return false; - } + m_exitAfterWait = true; + m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + if (!m_exitAfterWait) { // if operation cancelled + return false; } setCursor(Qt::WaitCursor); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d3f4a83c..2a228fda 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -34,6 +34,7 @@ #include "instancemanager.h" #include #include "previewdialog.h" +#include "envmodule.h" #include #include @@ -74,47 +75,6 @@ using namespace MOBase; //static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static std::wstring getProcessName(HANDLE process) -{ - wchar_t buffer[MAX_PATH]; - const wchar_t *fileName = L"unknown"; - - if (process == nullptr) return fileName; - - if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { - fileName = wcsrchr(buffer, L'\\'); - if (fileName == nullptr) { - fileName = buffer; - } - else { - fileName += 1; - } - } - - return fileName; -} - -// Get parent PID for the given process, return 0 on failure -static DWORD getProcessParentID(DWORD pid) -{ - HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe = { 0 }; - pe.dwSize = sizeof(PROCESSENTRY32); - - DWORD res = 0; - if (Process32First(th, &pe)) - do { - if (pe.th32ProcessID == pid) { - res = pe.th32ParentProcessID; - break; - } - } while (Process32Next(th, &pe)); - - CloseHandle(th); - - return res; -} - template QStringList toStringList(InputIterator current, InputIterator end) { @@ -1348,35 +1308,46 @@ HANDLE OrganizerCore::spawnAndWait( return handle; } -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) +void OrganizerCore::withLock(std::function f) { - if (Settings::instance().interface().lockGUI()) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_MainWindow != nullptr) { + uilock = m_MainWindow->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + ON_BLOCK_EXIT([&]() { if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } + m_MainWindow->unlock(); + } }); - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); + f(uilock); +} + +bool OrganizerCore::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) { + return true; + } + + bool r = false; + withLock([&](auto* uilock) { DWORD ignoreExitCode; - waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); cycleDiagnostics(); - } + }); - return handle; + return r; } bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) @@ -1393,30 +1364,64 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) if (m_MainWindow != nullptr) { m_MainWindow->unlock(); } }); + return waitForProcessCompletion(handle, exitCode, uilock); } -bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +bool OrganizerCore::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto r = spawn::waitForProcess(handle, exitCode, uilock); + + switch (r) + { + case spawn::WaitResults::Completed: // fall-through + case spawn::WaitResults::Unlocked: + return true; + + case spawn::WaitResults::Error: // fall-through + default: + return false; + } +} + +bool OrganizerCore::waitForAllUSVFSProcessesWithLock() { + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + // Certain process names we wish to "hide" for aesthetic reason: + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + bool originalHandle = true; bool newHandle = true; bool uiunlocked = false; + HANDLE handle = findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); + DWORD* exitCode = nullptr; DWORD currentPID = 0; QString processName; + auto waitForChildUntil = GetTickCount64(); if (handle != INVALID_HANDLE_VALUE) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); } - // Certain process names we wish to "hide" for aesthetic reason: bool waitingOnHidden = false; - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; + // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" @@ -1489,7 +1494,7 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL newHandle = handle != INVALID_HANDLE_VALUE; if (newHandle) { currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); + processName = env::getProcessName(handle); for (QString hide : hiddenList) if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; @@ -1541,13 +1546,13 @@ HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hidde continue; } - QString pname = QString::fromStdWString(getProcessName(handle)); + QString pname = env::getProcessName(handle); bool phidden = false; for (auto hide : hiddenList) if (pname.contains(hide, Qt::CaseInsensitive)) phidden = true; - bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid; + bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid; if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { if (best_match != INVALID_HANDLE_VALUE) diff --git a/src/organizercore.h b/src/organizercore.h index 3d3c7325..ffdb6830 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -167,6 +167,8 @@ public: const QString &profile, const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + bool waitForAllUSVFSProcessesWithLock(); void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -221,8 +223,6 @@ public: DownloadManager *downloadManager(); PluginList *pluginList(); ModList *modList(); - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); bool onModInstalled(const std::function &func); bool onAboutToRun(const std::function &func); bool onFinishedRun(const std::function &func); @@ -311,6 +311,13 @@ private: bool waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); + + void withLock(std::function f); + + HANDLE findAndOpenAUSVFSProcess( + const std::vector& hiddenList, DWORD preferedParentPid); + private slots: void directory_refreshed(); diff --git a/src/spawn.cpp b/src/spawn.cpp index fe1e9e3e..f0b3b2c7 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "settingsdialogworkarounds.h" #include +#include #include #include #include @@ -1093,6 +1094,68 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } +WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + const DWORD pid = ::GetProcessId(handle); + const QString processName = QString("%1 (%2)") + .arg(env::getProcessName(handle)) + .arg(pid); + + if (uilock) + uilock->setProcessName(processName); + + constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; + DWORD res = WAIT_TIMEOUT; + + log::debug( + "waiting for process completion '{}' ({})", + processName, pid); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion '{}' ({}), {}", + processName, pid, formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res == WAIT_OBJECT_0) { + // completed + log::debug("process '{}' ({}) completed", processName, pid); + + if (exitCode) { + if (!::GetExitCodeProcess(handle, exitCode)) { + const auto e = ::GetLastError(); + log::warn( + "failed to get exit code of process '{}' ({}): {}", + processName, pid, formatSystemMessage(e)); + } + } + + return WaitResults::Completed; + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + + if (uilock && uilock->unlockForced()) { + log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid); + return WaitResults::Unlocked; + } + } +} + } // namespace diff --git a/src/spawn.h b/src/spawn.h index 866e1795..441cad2c 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,6 +27,8 @@ along with Mod Organizer. If not, see . #include class Settings; +class ILockedWaitingForProcess; + namespace MOBase { class IPluginGame; } namespace spawn @@ -84,6 +86,7 @@ public: ~SpawnedProcess(); HANDLE releaseHandle(); + void wait(); private: HANDLE m_handle; @@ -122,6 +125,17 @@ QString findJavaInstallation(const QString& jarFile); FileExecutionContext getFileExecutionContext( QWidget* parent, const QFileInfo& target); + +enum class WaitResults +{ + Completed = 1, + Error, + Unlocked +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); + } // namespace -- cgit v1.3.1 From 18b438cf27a552e69e984bfee63187b6471682ab Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 07:28:12 -0400 Subject: split to getRunningUSVFSProcesses() simplified waitForAllUSVFSProcesses() to always get the list of running processes after one process completes --- src/envmodule.cpp | 6 ++ src/envmodule.h | 2 + src/organizercore.cpp | 224 ++++++++++++++----------------------------------- src/spawn.cpp | 53 ++++++++---- src/spawn.h | 4 + src/usvfsconnector.cpp | 47 +++++++++++ src/usvfsconnector.h | 2 + 7 files changed, 163 insertions(+), 175 deletions(-) (limited to 'src') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index abbe02e5..160a54fa 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -511,4 +511,10 @@ DWORD getProcessParentID(DWORD pid) return ppid; } + +DWORD getProcessParentID(HANDLE handle) +{ + return getProcessParentID(GetProcessId(handle)); +} + } // namespace diff --git a/src/envmodule.h b/src/envmodule.h index 212f6f7b..6c0a028d 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -121,7 +121,9 @@ std::vector getRunningProcesses(); std::vector getLoadedModules(); QString getProcessName(HANDLE process); + DWORD getProcessParentID(DWORD pid); +DWORD getProcessParentID(HANDLE handle); } // namespace env diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2a228fda..2a97b998 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1355,17 +1355,13 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) if (!Settings::instance().interface().lockGUI()) return true; - ILockedWaitingForProcess* uilock = nullptr; - if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } + bool r = false; - ON_BLOCK_EXIT([&] () { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); + withLock([&](auto* uilock) { + r = waitForProcessCompletion(handle, exitCode, uilock); + }); - return waitForProcessCompletion(handle, exitCode, uilock); + return r; } bool OrganizerCore::waitForProcessCompletion( @@ -1387,6 +1383,9 @@ bool OrganizerCore::waitForProcessCompletion( bool OrganizerCore::waitForAllUSVFSProcessesWithLock() { + if (!Settings::instance().interface().lockGUI()) + return true; + bool r = false; withLock([&](auto* uilock) { @@ -1396,178 +1395,81 @@ bool OrganizerCore::waitForAllUSVFSProcessesWithLock() return r; } -bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +HANDLE getInterestingProcess( + const std::vector& handles, + const std::vector& hidden, DWORD preferedParentPid) { - // Certain process names we wish to "hide" for aesthetic reason: - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - - bool originalHandle = true; - bool newHandle = true; - bool uiunlocked = false; - - HANDLE handle = findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); - DWORD* exitCode = nullptr; - DWORD currentPID = 0; - QString processName; - - auto waitForChildUntil = GetTickCount64(); - if (handle != INVALID_HANDLE_VALUE) { - currentPID = GetProcessId(handle); - processName = env::getProcessName(handle); - } - - bool waitingOnHidden = false; - for (QString hide : hiddenList) - if (processName.contains(hide, Qt::CaseInsensitive)) - waitingOnHidden = true; - - // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. - // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want - // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" - // process. For this reason we use exponential backoff and also start with a delibrately low value to improve - // the responsiveness of the initial update - DWORD64 nextHiddenCheck = GetTickCount64(); - DWORD64 nextHiddenCheckDelay = 50; - - constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; - DWORD res = WAIT_TIMEOUT; - while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT)) - { - if (newHandle) { - processName += QString(" (%1)").arg(currentPID); - if (uilock) - uilock->setProcessName(processName); - - log::debug( - "Waiting for {} process completion: {}", - (originalHandle ? "spawned" : "usvfs"), processName); - - newHandle = false; - } + HANDLE best_match = INVALID_HANDLE_VALUE; + bool best_match_hidden = true; - // Wait for a an event on the handle, a key press, mouse click or timeout - res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON); - if (res == WAIT_FAILED) { - log::warn("Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED {}", GetLastError()); - break; - } + for (auto handle : handles) { + const QString pname = env::getProcessName(handle); - // keep processing events so the app doesn't appear dead - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); + bool phidden = false; + for (auto h : hidden) + if (pname.contains(h, Qt::CaseInsensitive)) + phidden = true; - if (uilock && uilock->unlockForced()) { - uiunlocked = true; - break; - } + bool pprefered = preferedParentPid && env::getProcessParentID(handle) == preferedParentPid; - if (res == WAIT_OBJECT_0) { - // process we were waiting on has completed - if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode)) - log::warn("Failed getting exit code of complete process: {}", GetLastError()); - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; - originalHandle = false; - // if the previous process spawned a child process and immediately exits we may miss it if we check immediately - waitForChildUntil = GetTickCount64() + 800; + if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { + best_match = handle; + best_match_hidden = phidden; } - // search for another process to wait on if either: - // 1. we just completed waiting for a process and need to find/wait for an inject child - // 2. we are currently waiting on a hidden process so periodically check if there is a non-hidden process to wait on - bool firstIteration = true; - while ((handle == INVALID_HANDLE_VALUE && GetTickCount64() <= waitForChildUntil) - || (waitingOnHidden && GetTickCount64() >= nextHiddenCheck)) - { - if (firstIteration) - firstIteration = false; - else { - QThread::msleep(200); - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); - } - - // search if there is another usvfs process active - handle = findAndOpenAUSVFSProcess(hiddenList, currentPID); - waitingOnHidden = false; - newHandle = handle != INVALID_HANDLE_VALUE; - if (newHandle) { - currentPID = GetProcessId(handle); - processName = env::getProcessName(handle); - for (QString hide : hiddenList) - if (processName.contains(hide, Qt::CaseInsensitive)) - waitingOnHidden = true; - } - if (waitingOnHidden) { - nextHiddenCheck = GetTickCount64() + nextHiddenCheckDelay; - nextHiddenCheckDelay = std::min(nextHiddenCheckDelay * 2, (DWORD64) 2000); - } - else { - nextHiddenCheck = GetTickCount64(); - nextHiddenCheckDelay = 200; - } - } + if (!phidden && pprefered) + return best_match; } - if (res == WAIT_OBJECT_0) - log::debug("Waiting for process completion successfull"); - else if (uiunlocked) - log::debug("Waiting for process completion aborted by UI"); - else - log::debug("Waiting for process completion not successfull: {}", res); - - if (handle != INVALID_HANDLE_VALUE) - ::CloseHandle(handle); - - return res == WAIT_OBJECT_0; + return best_match; } -HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid) { - // for practical reasons a querySize of 1 is probably enough, we use a larger query as a heuristics - // to find a more "aesthetic injected processes (attempting to comply to hiddenList and preferedParentPid) - constexpr size_t querySize = 100; - DWORD pids[querySize]; - size_t found = querySize; - if (!::GetVFSProcessList(&found, pids)) { - log::warn("Failed seeking USVFS processes : GetVFSProcessList failed?!"); - return INVALID_HANDLE_VALUE; - } - - HANDLE best_match = INVALID_HANDLE_VALUE; - bool best_match_hidden = true; - for (size_t i = 0; i < found; ++i) { - if (pids[i] == GetCurrentProcessId()) - continue; // obviously don't wait for MO process +bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + // Certain process names we wish to "hide" for aesthetic reason: + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); - if (handle == INVALID_HANDLE_VALUE) { - log::warn("Failed opening USVFS process {}: OpenProcess failed {}", pids[i], GetLastError()); - continue; + for (;;) { + const auto handles = getRunningUSVFSProcesses(); + if (handles.empty()) { + break; } - QString pname = env::getProcessName(handle); - bool phidden = false; - for (auto hide : hiddenList) - if (pname.contains(hide, Qt::CaseInsensitive)) - phidden = true; + const auto interesting = getInterestingProcess( + handles, hiddenList, GetCurrentProcessId()); - bool pprefered = preferedParentPid && env::getProcessParentID(pids[i]) == preferedParentPid; + if (uilock) { + const DWORD pid = ::GetProcessId(interesting); + const QString processName = QString("%1 (%2)") + .arg(env::getProcessName(interesting)) + .arg(pid); - if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { - if (best_match != INVALID_HANDLE_VALUE) - CloseHandle(best_match); - best_match = handle; - best_match_hidden = phidden; + uilock->setProcessName(processName); } - else - CloseHandle(handle); - if (!phidden && pprefered) - return best_match; + const auto r = spawn::waitForProcess(interesting, nullptr, uilock); + + switch (r) + { + case spawn::WaitResults::Completed: + // this process is completed, check for others + break; + + case spawn::WaitResults::Unlocked: + // force unlocked + log::debug("waiting for process completion aborted by UI"); + return true; + + case spawn::WaitResults::Error: // fall-through + default: + log::debug("waiting for process completion not successful"); + return false; + } } - return best_match; + log::debug("Waiting for process completion successful"); + return true; } bool OrganizerCore::onAboutToRun( diff --git a/src/spawn.cpp b/src/spawn.cpp index f0b3b2c7..1003024f 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1094,7 +1094,8 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } -WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) { if (handle == INVALID_HANDLE_VALUE) { return WaitResults::Error; @@ -1108,37 +1109,62 @@ WaitResults waitForProcess(HANDLE handle, DWORD* exitCode, ILockedWaitingForProc if (uilock) uilock->setProcessName(processName); - constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; - DWORD res = WAIT_TIMEOUT; - log::debug( "waiting for process completion '{}' ({})", processName, pid); + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + const auto r = waitForProcesses(handles, exitCodes, uilock); + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } + + return r; +} + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + ILockedWaitingForProcess* uilock) +{ + if (handles.empty()) { + return WaitResults::Completed; + } + + const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); + for (;;) { // Wait for a an event on the handle, a key press, mouse click or timeout const auto res = MsgWaitForMultipleObjects( - 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + static_cast(handles.size()), &handles[0], + TRUE, 50, QS_KEY | QS_MOUSEBUTTON); if (res == WAIT_FAILED) { // error const auto e = ::GetLastError(); log::error( - "failed waiting for process completion '{}' ({}), {}", - processName, pid, formatSystemMessage(e)); + "failed waiting for process completion, {}", formatSystemMessage(e)); return WaitResults::Error; - } else if (res == WAIT_OBJECT_0) { + } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { // completed - log::debug("process '{}' ({}) completed", processName, pid); + exitCodes.resize(handles.size()); + std::fill(exitCodes.begin(), exitCodes.end(), 0); + + for (std::size_t i=0; iunlockForced()) { - log::debug("waiting for process '{}' ({}) aborted by UI", processName, pid); return WaitResults::Unlocked; } } diff --git a/src/spawn.h b/src/spawn.h index 441cad2c..6b947f2f 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -136,6 +136,10 @@ enum class WaitResults WaitResults waitForProcess( HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + ILockedWaitingForProcess* uilock); + } // namespace diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 311c6dd3..3dba3efc 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "usvfsconnector.h" #include "settings.h" #include "organizercore.h" +#include "envmodule.h" #include "shared/util.h" #include #include @@ -256,3 +257,49 @@ void UsvfsConnector::updateForcedLibraries(const QList getRunningUSVFSProcesses() +{ + std::vector pids; + + { + size_t count = 0; + DWORD* buffer = nullptr; + if (!::GetVFSProcessList2(&count, &buffer)) { + log::error("failed to get usvfs process list"); + return {}; + } + + if (buffer) { + pids.assign(buffer, buffer + count); + std::free(buffer); + } + } + + const auto thisPid = GetCurrentProcessId(); + std::vector v; + + for (auto&& pid : pids) { + if (pid == thisPid) { + continue; // obviously don't wait for MO process + } + + HANDLE handle = ::OpenProcess( + PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pid); + + if (handle == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + log::warn( + "failed to open usvfs process {}: {}", + pid, formatSystemMessage(e)); + + continue; + } + + v.push_back(handle); + } + + return v; +} diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index d0071678..5982778b 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -103,4 +103,6 @@ private: CrashDumpsType crashDumpsType(int type); +std::vector getRunningUSVFSProcesses(); + #endif // USVFSCONNECTOR_H -- cgit v1.3.1 From 8c72077febaea485200adcf1e9f615902e930def Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 10:03:59 -0400 Subject: replaced uilock by a progress callback in spawn waiting for process now gets the whole process tree to find an interesting process --- src/env.h | 17 ------ src/envmodule.cpp | 107 +++++++++++++++++++++++++++++++++- src/envmodule.h | 35 ++++++++++- src/envsecurity.cpp | 1 + src/envwindows.cpp | 1 + src/ilockedwaitingforprocess.h | 1 + src/lockeddialogbase.cpp | 7 ++- src/lockeddialogbase.h | 4 +- src/organizercore.cpp | 128 ++++++++++++++++++++++++++--------------- src/spawn.cpp | 29 ++++------ src/spawn.h | 7 +-- 11 files changed, 246 insertions(+), 91 deletions(-) (limited to 'src') diff --git a/src/env.h b/src/env.h index 1760c7fe..f95d1013 100644 --- a/src/env.h +++ b/src/env.h @@ -13,23 +13,6 @@ class WindowsInfo; class Metrics; -// used by HandlePtr, calls CloseHandle() as the deleter -// -struct HandleCloser -{ - using pointer = HANDLE; - - void operator()(HANDLE h) - { - if (h != INVALID_HANDLE_VALUE) { - ::CloseHandle(h); - } - } -}; - -using HandlePtr = std::unique_ptr; - - // used by DesktopDCPtr, calls ReleaseDC(0, dc) as the deleter // struct DesktopDCReleaser diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 160a54fa..0e2e8ec7 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -320,19 +320,61 @@ QString Module::getMD5() const } -Process::Process(DWORD pid, QString name) - : m_pid(pid), m_name(std::move(name)) +Process::Process() + : Process(0, 0, {}) { } +Process::Process(HANDLE h) + : Process(::GetProcessId(h), 0, {}) +{ +} + +Process::Process(DWORD pid, DWORD ppid, QString name) + : m_pid(pid), m_ppid(ppid), m_name(std::move(name)) +{ +} + +bool Process::isValid() const +{ + return (m_pid != 0); +} + DWORD Process::pid() const { return m_pid; } +DWORD Process::ppid() const +{ + if (!m_ppid) { + m_ppid = getProcessParentID(m_pid); + } + + return *m_ppid; +} + const QString& Process::name() const { - return m_name; + if (!m_name) { + m_name = getProcessName(m_pid); + } + + return *m_name; +} + +HandlePtr Process::openHandleForWait() const +{ + HandlePtr h(OpenProcess( + PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, m_pid)); + + if (!h) { + const auto e = GetLastError(); + log::error("can't get name of process {}, {}", m_pid, formatSystemMessage(e)); + return {}; + } + + return h; } // whether this process can be accessed; fails if the current process doesn't @@ -353,6 +395,16 @@ bool Process::canAccess() const return true; } +void Process::addChild(Process p) +{ + m_children.push_back(p); +} + +std::vector& Process::children() +{ + return m_children; +} + std::vector getLoadedModules() { @@ -458,6 +510,7 @@ std::vector getRunningProcesses() forEachRunningProcess([&](auto&& entry) { v.push_back(Process( entry.th32ProcessID, + entry.th32ParentProcessID, QString::fromStdWString(entry.szExeFile))); return true; @@ -466,6 +519,54 @@ std::vector getRunningProcesses() return v; } +void findChildren(Process& parent, const std::vector& processes) +{ + for (auto&& p : processes) { + if (p.ppid() == parent.pid()) { + Process child = p; + findChildren(child, processes); + + parent.addChild(child); + } + } +} + +Process getProcessTree(HANDLE parent) +{ + const auto parentPID = ::GetProcessId(parent); + const auto v = getRunningProcesses(); + + Process root; + for (auto&& p : v) { + if (p.pid() == parentPID) { + root = p; + break; + } + } + + if (root.pid() == 0) { + log::error("process {} is not running", parentPID); + return {}; + } + + findChildren(root, v); + + return root; +} + +QString getProcessName(DWORD pid) +{ + HandlePtr h(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + log::error("can't get name of process {}, {}", pid, formatSystemMessage(e)); + return {}; + } + + return getProcessName(h.get()); +} + QString getProcessName(HANDLE process) { const QString badName = "unknown"; diff --git a/src/envmodule.h b/src/envmodule.h index 6c0a028d..d152b840 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -7,6 +7,23 @@ namespace env { +// used by HandlePtr, calls CloseHandle() as the deleter +// +struct HandleCloser +{ + using pointer = HANDLE; + + void operator()(HANDLE h) + { + if (h != INVALID_HANDLE_VALUE) { + ::CloseHandle(h); + } + } +}; + +using HandlePtr = std::unique_ptr; + + // represents one module // class Module @@ -101,25 +118,39 @@ private: class Process { public: - Process(DWORD pid, QString name); + Process(); + explicit Process(HANDLE h); + Process(DWORD pid, DWORD ppid, QString name); + bool isValid() const; DWORD pid() const; + DWORD ppid() const; const QString& name() const; + HandlePtr openHandleForWait() const; + // whether this process can be accessed; fails if the current process doesn't // have the proper permissions // bool canAccess() const; + void addChild(Process p); + std::vector& children(); + private: DWORD m_pid; - QString m_name; + mutable std::optional m_ppid; + mutable std::optional m_name; + std::vector m_children; }; std::vector getRunningProcesses(); std::vector getLoadedModules(); +Process getProcessTree(HANDLE parent); + +QString getProcessName(DWORD pid); QString getProcessName(HANDLE process); DWORD getProcessParentID(DWORD pid); diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 786291c6..6d62728b 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -1,5 +1,6 @@ #include "envsecurity.h" #include "env.h" +#include "envmodule.h" #include #include diff --git a/src/envwindows.cpp b/src/envwindows.cpp index 3932a9b5..98e78a3e 100644 --- a/src/envwindows.cpp +++ b/src/envwindows.cpp @@ -1,5 +1,6 @@ #include "envwindows.h" #include "env.h" +#include "envmodule.h" #include #include diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h index 9475ddb9..4d1e786f 100644 --- a/src/ilockedwaitingforprocess.h +++ b/src/ilockedwaitingforprocess.h @@ -8,6 +8,7 @@ class ILockedWaitingForProcess public: virtual bool unlockForced() const = 0; virtual void setProcessName(QString const &) = 0; + virtual void setProcessInformation(DWORD pid, const QString& name) = 0; }; #endif // ILOCKEDWAITINGFORPROCESS_H diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp index b18f7429..0876a511 100644 --- a/src/lockeddialogbase.cpp +++ b/src/lockeddialogbase.cpp @@ -18,7 +18,7 @@ along with Mod Organizer. If not, see . */ #include "lockeddialogbase.h" - +#include "envmodule.h" #include #include #include @@ -63,6 +63,11 @@ bool LockedDialogBase::canceled() const { return m_Canceled; } +void LockedDialogBase::setProcessInformation(DWORD pid, const QString& name) +{ + setProcessName(QString("%1 (%2)").arg(name).arg(pid)); +} + void LockedDialogBase::unlock() { m_Unlocked = true; } diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h index 3c974a38..4ebad4c7 100644 --- a/src/lockeddialogbase.h +++ b/src/lockeddialogbase.h @@ -30,7 +30,7 @@ class QWidget; /** * a small borderless dialog displayed while the Mod Organizer UI is locked * The dialog contains only a label and a button to force the UI to be unlocked - * + * * The UI gets locked while running external applications since they may modify the * data on which Mod Organizer works. After the UI is unlocked (manually or after the * external application closed) MO will refresh all of its data sources @@ -46,6 +46,8 @@ public: virtual bool canceled() const; + void setProcessInformation(DWORD pid, const QString& name) override; + protected: virtual void resizeEvent(QResizeEvent *event); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2a97b998..eda64c1d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -34,6 +34,7 @@ #include "instancemanager.h" #include #include "previewdialog.h" +#include "env.h" #include "envmodule.h" #include @@ -85,6 +86,45 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } +env::Process* getInterestingProcess(std::vector& processes) +{ + if (processes.empty()) { + return nullptr; + } + + // Certain process names we wish to "hide" for aesthetic reason: + const std::vector hiddenList = { + QFileInfo(QCoreApplication::applicationFilePath()).fileName() + }; + + auto isHidden = [&](auto&& p) { + for (auto h : hiddenList) { + if (p.name().contains(h, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }; + + + for (auto&& root : processes) { + if (!isHidden(root)) { + return &root; + } + + for (auto&& child : root.children()) { + if (!isHidden(child)) { + return &child; + } + } + } + + + // everything is hidden, just pick the first one + return &processes[0]; +} + OrganizerCore::OrganizerCore(Settings &settings) : m_MainWindow(nullptr) @@ -1367,12 +1407,31 @@ bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) bool OrganizerCore::waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) { - const auto r = spawn::waitForProcess(handle, exitCode, uilock); + const auto tree = env::getProcessTree(handle); + std::vector processes = {tree}; + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return true; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return true; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = spawn::waitForProcess( + interestingHandle.get(), exitCode, progress); switch (r) { case spawn::WaitResults::Completed: // fall-through - case spawn::WaitResults::Unlocked: + case spawn::WaitResults::Cancelled: return true; case spawn::WaitResults::Error: // fall-through @@ -1395,60 +1454,39 @@ bool OrganizerCore::waitForAllUSVFSProcessesWithLock() return r; } -HANDLE getInterestingProcess( - const std::vector& handles, - const std::vector& hidden, DWORD preferedParentPid) -{ - HANDLE best_match = INVALID_HANDLE_VALUE; - bool best_match_hidden = true; - - for (auto handle : handles) { - const QString pname = env::getProcessName(handle); - - bool phidden = false; - for (auto h : hidden) - if (pname.contains(h, Qt::CaseInsensitive)) - phidden = true; - - bool pprefered = preferedParentPid && env::getProcessParentID(handle) == preferedParentPid; - - if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { - best_match = handle; - best_match_hidden = phidden; - } - - if (!phidden && pprefered) - return best_match; - } - - return best_match; -} - bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) { - // Certain process names we wish to "hide" for aesthetic reason: - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - for (;;) { const auto handles = getRunningUSVFSProcesses(); if (handles.empty()) { break; } - const auto interesting = getInterestingProcess( - handles, hiddenList, GetCurrentProcessId()); + std::vector processes; + for (auto&& h : handles) { + auto p = env::getProcessTree(h); + if (p.isValid()) { + processes.emplace_back(std::move(p)); + } + } + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + break; + } if (uilock) { - const DWORD pid = ::GetProcessId(interesting); - const QString processName = QString("%1 (%2)") - .arg(env::getProcessName(interesting)) - .arg(pid); + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } - uilock->setProcessName(processName); + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + break; } - const auto r = spawn::waitForProcess(interesting, nullptr, uilock); + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = spawn::waitForProcess( + interestingHandle.get(), nullptr, progress); switch (r) { @@ -1456,7 +1494,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) // this process is completed, check for others break; - case spawn::WaitResults::Unlocked: + case spawn::WaitResults::Cancelled: // force unlocked log::debug("waiting for process completion aborted by UI"); return true; @@ -1468,7 +1506,7 @@ bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) } } - log::debug("Waiting for process completion successful"); + log::debug("waiting for process completion successful"); return true; } diff --git a/src/spawn.cpp b/src/spawn.cpp index 1003024f..0ea60641 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1095,32 +1095,25 @@ FileExecutionContext getFileExecutionContext( } WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock) + HANDLE handle, DWORD* exitCode, std::function progress) { if (handle == INVALID_HANDLE_VALUE) { return WaitResults::Error; } - const DWORD pid = ::GetProcessId(handle); - const QString processName = QString("%1 (%2)") - .arg(env::getProcessName(handle)) - .arg(pid); - - if (uilock) - uilock->setProcessName(processName); - - log::debug( - "waiting for process completion '{}' ({})", - processName, pid); + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); std::vector handles; handles.push_back(handle); std::vector exitCodes; - const auto r = waitForProcesses(handles, exitCodes, uilock); - if (exitCode && !exitCodes.empty()) { - *exitCode = exitCodes[0]; + const auto r = waitForProcesses(handles, exitCodes, progress); + + if (r == WaitResults::Completed) { + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } } return r; @@ -1128,7 +1121,7 @@ WaitResults waitForProcess( WaitResults waitForProcesses( const std::vector& handles, std::vector& exitCodes, - ILockedWaitingForProcess* uilock) + std::function progress) { if (handles.empty()) { return WaitResults::Completed; @@ -1175,8 +1168,8 @@ WaitResults waitForProcesses( QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); - if (uilock && uilock->unlockForced()) { - return WaitResults::Unlocked; + if (progress && progress()) { + return WaitResults::Cancelled; } } } diff --git a/src/spawn.h b/src/spawn.h index 6b947f2f..ad50bde6 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -27,7 +27,6 @@ along with Mod Organizer. If not, see . #include class Settings; -class ILockedWaitingForProcess; namespace MOBase { class IPluginGame; } @@ -130,15 +129,15 @@ enum class WaitResults { Completed = 1, Error, - Unlocked + Cancelled }; WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock); + HANDLE handle, DWORD* exitCode, std::function progress); WaitResults waitForProcesses( const std::vector& handles, std::vector& exitCodes, - ILockedWaitingForProcess* uilock); + std::function progress); } // namespace -- cgit v1.3.1 From 4d269c2e1a625e6d50b7e6272b4f474a921c6bfa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 11:34:06 -0400 Subject: split to processrunner added IUserInterface::qtWidget() put back IUserInterface in OrganizerCore now that there's a way to get the widget --- src/CMakeLists.txt | 3 + src/iuserinterface.h | 2 + src/main.cpp | 4 +- src/mainwindow.cpp | 13 +- src/mainwindow.h | 5 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 601 ++++++++------------------------------ src/organizercore.h | 60 +--- src/organizerproxy.cpp | 4 +- src/processrunner.cpp | 634 +++++++++++++++++++++++++++++++++++++++++ src/processrunner.h | 104 +++++++ src/spawn.cpp | 198 ------------- src/spawn.h | 48 ---- 13 files changed, 892 insertions(+), 786 deletions(-) create mode 100644 src/processrunner.cpp create mode 100644 src/processrunner.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 180422ef..5e909760 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -143,6 +143,7 @@ SET(organizer_SRCS envwindows.cpp colortable.cpp sanitychecks.cpp + processrunner.cpp shared/windows_error.cpp shared/error_report.cpp @@ -266,6 +267,7 @@ SET(organizer_HDRS envshortcut.h envwindows.h colortable.h + processrunner.h shared/windows_error.h shared/error_report.h @@ -348,6 +350,7 @@ set(core organizercore organizerproxy apiuseraccount + processrunner ) set(dialogs diff --git a/src/iuserinterface.h b/src/iuserinterface.h index a309ed9b..91487aee 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -33,6 +33,8 @@ public: virtual ILockedWaitingForProcess* lock() = 0; virtual void unlock() = 0; + + virtual QWidget* qtWidget() = 0; }; #endif // IUSERINTERFACE_H diff --git a/src/main.cpp b/src/main.cpp index 49ecc084..5ed7da5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -632,7 +632,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (MOShortcut shortcut{ arguments.at(1) }) { if (shortcut.hasExecutable()) { try { - organizer.runShortcut(shortcut); + organizer.processRunner().runShortcut(shortcut); return 0; } catch (const std::exception &e) { @@ -653,7 +653,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, arguments.removeFirst(); // remove binary name // pass the remaining parameters to the binary try { - organizer.runExecutableOrExecutableFile( + organizer.processRunner().runExecutableOrExecutableFile( exeName, arguments, QString(), QString()); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bbb63333..ce5280a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1312,7 +1312,7 @@ bool MainWindow::canExit() } m_exitAfterWait = true; - m_OrganizerCore.waitForAllUSVFSProcessesWithLock(); + m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock(); if (!m_exitAfterWait) { // if operation cancelled return false; } @@ -1536,7 +1536,7 @@ void MainWindow::startExeAction() } action->setEnabled(false); - m_OrganizerCore.runExecutable(*itor); + m_OrganizerCore.processRunner().runExecutable(*itor); action->setEnabled(true); } @@ -2294,6 +2294,11 @@ void MainWindow::unlock() } } +QWidget* MainWindow::qtWidget() +{ + return this; +} + void MainWindow::on_btnRefreshData_clicked() { m_OrganizerCore.refreshDirectoryStructure(); @@ -2363,7 +2368,7 @@ void MainWindow::on_startButton_clicked() forcedLibraries.clear(); } - m_OrganizerCore.runExecutableFile( + m_OrganizerCore.processRunner().runExecutableFile( selectedExecutable->binaryInfo(), selectedExecutable->arguments(), selectedExecutable->workingDirectory().length() != 0 ? @@ -5453,7 +5458,7 @@ void MainWindow::openDataFile() const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); - m_OrganizerCore.runFile(this, targetInfo); + m_OrganizerCore.processRunner().runFile(this, targetInfo); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/mainwindow.h b/src/mainwindow.h index dbbd0bd9..19723480 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -118,8 +118,9 @@ public: void processUpdates(Settings& settings); - virtual ILockedWaitingForProcess* lock() override; - virtual void unlock() override; + ILockedWaitingForProcess* lock() override; + void unlock() override; + QWidget* qtWidget() override; bool addProfile(); void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 68b1be6b..758112cc 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,7 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().runFile(parentWidget(), item->fileName()); + core().processRunner().runFile(parentWidget(), item->fileName()); return true; }); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index eda64c1d..0f767f46 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,5 +1,4 @@ #include "organizercore.h" -#include "mainwindow.h" #include "delayedfilewriter.h" #include "guessedvalue.h" #include "imodinterface.h" @@ -86,51 +85,13 @@ QStringList toStringList(InputIterator current, InputIterator end) return result; } -env::Process* getInterestingProcess(std::vector& processes) -{ - if (processes.empty()) { - return nullptr; - } - - // Certain process names we wish to "hide" for aesthetic reason: - const std::vector hiddenList = { - QFileInfo(QCoreApplication::applicationFilePath()).fileName() - }; - - auto isHidden = [&](auto&& p) { - for (auto h : hiddenList) { - if (p.name().contains(h, Qt::CaseInsensitive)) { - return true; - } - } - - return false; - }; - - - for (auto&& root : processes) { - if (!isHidden(root)) { - return &root; - } - - for (auto&& child : root.children()) { - if (!isHidden(child)) { - return &child; - } - } - } - - - // everything is hidden, just pick the first one - return &processes[0]; -} - OrganizerCore::OrganizerCore(Settings &settings) - : m_MainWindow(nullptr) + : m_UserInterface(nullptr) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) + , m_Runner(*this) , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() @@ -190,7 +151,7 @@ OrganizerCore::~OrganizerCore() m_RefresherThread.exit(); m_RefresherThread.wait(); - prepareStart(); + saveCurrentProfile(); // profile has to be cleaned up before the modinfo-buffer is cleared delete m_CurrentProfile; @@ -249,43 +210,49 @@ void OrganizerCore::updateExecutablesList() m_PluginContainer, m_Settings.interface().displayForeign(), managedGame()); } -void OrganizerCore::setUserInterface(MainWindow* mainWindow) +void OrganizerCore::setUserInterface(IUserInterface* ui) { storeSettings(); - m_MainWindow = mainWindow; + m_UserInterface = ui; + + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } - if (m_MainWindow != nullptr) { - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), m_MainWindow, + if (w) { + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndex, int)), w, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), m_MainWindow, + connect(&m_ModList, SIGNAL(modlistChanged(QModelIndexList, int)), w, SLOT(modlistChanged(QModelIndexList, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), w, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(modUninstalled(QString)), w, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), m_MainWindow, + connect(&m_ModList, SIGNAL(removeSelectedMods()), w, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(clearOverwrite()), m_MainWindow, + connect(&m_ModList, SIGNAL(clearOverwrite()), w, SLOT(clearOverwrite())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), m_MainWindow, + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), w, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), m_MainWindow, + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), w, SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), m_MainWindow, + connect(&m_ModList, SIGNAL(modorder_changed()), w, SLOT(modorder_changed())); - connect(&m_PluginList, SIGNAL(writePluginsList()), m_MainWindow, + connect(&m_PluginList, SIGNAL(writePluginsList()), w, SLOT(esplist_changed())); - connect(&m_PluginList, SIGNAL(esplist_changed()), m_MainWindow, + connect(&m_PluginList, SIGNAL(esplist_changed()), w, SLOT(esplist_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), m_MainWindow, + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), w, SLOT(showMessage(QString))); } - m_InstallationManager.setParentWidget(m_MainWindow); - m_Updater.setUserInterface(m_MainWindow); + m_InstallationManager.setParentWidget(w); + m_Updater.setUserInterface(w); + m_Runner.setUserInterface(ui); checkForUpdates(); } @@ -294,7 +261,7 @@ void OrganizerCore::checkForUpdates() { // this currently wouldn't work reliably if the ui isn't initialized yet to // display the result - if (m_MainWindow != nullptr) { + if (m_UserInterface != nullptr) { m_Updater.testForUpdate(m_Settings); } } @@ -390,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { if(moshortcut.hasExecutable()) - runShortcut(moshortcut); + m_Runner.runShortcut(moshortcut); } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); @@ -754,13 +721,13 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, int modIndex = ModInfo::getIndex(modName); if (modIndex != UINT_MAX) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && (m_MainWindow != nullptr) + if (hasIniTweaks && (m_UserInterface != nullptr) && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); @@ -821,13 +788,13 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modInfo->addInstalledFile(modID, fileID); - if (hasIniTweaks && m_MainWindow != nullptr + if (hasIniTweaks && m_UserInterface != nullptr && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), tr("This mod contains ini tweaks. Do you " "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_MainWindow->displayModInformation( + m_UserInterface->displayModInformation( modInfo, modIndex, ModInfoTabIDs::IniFiles); } @@ -1104,412 +1071,6 @@ bool OrganizerCore::previewFile( return true; } -bool OrganizerCore::runFile( - QWidget* parent, const QFileInfo& targetInfo) -{ - const auto fec = spawn::getFileExecutionContext(parent, targetInfo); - - switch (fec.type) - { - case spawn::FileExecutionTypes::Executable: - { - runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); - return true; - } - - case spawn::FileExecutionTypes::Other: // fall-through - default: - { - auto r = shell::Open(targetInfo.absoluteFilePath()); - if (!r.success()) { - return false; - } - - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer) - if (r.processHandle() != INVALID_HANDLE_VALUE) { - // steal because it gets closed after the wait - return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); - } - - return true; - } - } -} - -bool OrganizerCore::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - DWORD processExitCode = 0; - HANDLE processHandle = spawnAndWait( - binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, - customOverwrite, forcedLibraries, &processExitCode); - - if (processHandle == INVALID_HANDLE_VALUE) { - // failed - return false; - } - - if (refresh) { - refreshDirectoryStructure(); - - // need to remove our stored load order because it may be outdated if a foreign tool changed the - // file time. After removing that file, refreshESPList will use the file time as the order - if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - log::debug("removing loadorder.txt"); - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - - refreshDirectoryStructure(); - - refreshESPList(true); - savePluginList(); - - //These callbacks should not fiddle with directoy structure and ESPs. - m_FinishedRun(binary.absoluteFilePath(), processExitCode); - } - - return true; -} - -bool OrganizerCore::runExecutable(const Executable& exe, bool refresh) -{ - const QString customOverwrite = m_CurrentProfile->setting( - "custom_overwrites", exe.title()).toString(); - - QList forcedLibraries; - - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - - return runExecutableFile( - exe.binaryInfo(), - exe.arguments(), - exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries, - refresh); -} - -bool OrganizerCore::runShortcut(const MOShortcut& shortcut) -{ - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { - throw std::runtime_error( - QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(),shortcut.executable()) - .toLocal8Bit().constData()); - } - - const Executable& exe = m_ExecutablesList.get(shortcut.executable()); - return runExecutable(exe, false); -} - -HANDLE OrganizerCore::runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) -{ - QString profileName = profile; - if (profile == "") { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } - } - - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; - - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = managedGame()->gameDirectory().absoluteFilePath(executable); - } - - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); - } - - try { - const Executable& exe = m_ExecutablesList.getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - } catch (const std::runtime_error &) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()).toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); - } - if (arguments == "") { - arguments = exe.arguments(); - } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); - } - } catch (const std::runtime_error &) { - log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); - } - } - - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); - - return spawnAndWait(binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); -} - -HANDLE OrganizerCore::spawnAndWait( - const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) -{ - spawn::SpawnParameters sp; - sp.binary = binary; - sp.arguments = arguments; - sp.currentDirectory = currentDirectory; - sp.steamAppID = steamAppID; - sp.hooked = true; - - prepareStart(); - - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); - } - - // need to make sure all data is saved before we start the application - if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); - } - - // TODO: should also pass arguments - if (!m_AboutToRun(binary.absoluteFilePath())) { - log::debug("start of \"{}\" canceled by plugin", binary.absoluteFilePath()); - return INVALID_HANDLE_VALUE; - } - - try { - m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); - m_USVFS.updateForcedLibraries(forcedLibraries); - - } catch (const UsvfsConnectorException &e) { - log::debug(e.what()); - return INVALID_HANDLE_VALUE; - } catch (const std::exception &e) { - QMessageBox::warning(m_MainWindow, tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - HANDLE handle = spawn::Spawner() - .spawn(m_MainWindow, m_GamePlugin, sp, m_Settings) - .releaseHandle(); - - if (handle == INVALID_HANDLE_VALUE) { - // failed - return INVALID_HANDLE_VALUE; - } - - waitForProcessCompletionWithLock(handle, exitCode); - return handle; -} - -void OrganizerCore::withLock(std::function f) -{ - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; - - if (m_MainWindow != nullptr) { - uilock = m_MainWindow->lock(); - } - else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } - - ON_BLOCK_EXIT([&]() { - if (m_MainWindow != nullptr) { - m_MainWindow->unlock(); - } }); - - f(uilock); -} - -bool OrganizerCore::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) { - return true; - } - - bool r = false; - - withLock([&](auto* uilock) { - DWORD ignoreExitCode; - r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); - cycleDiagnostics(); - }); - - return r; -} - -bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); - }); - - return r; -} - -bool OrganizerCore::waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) -{ - const auto tree = env::getProcessTree(handle); - std::vector processes = {tree}; - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - return true; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - return true; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), exitCode, progress); - - switch (r) - { - case spawn::WaitResults::Completed: // fall-through - case spawn::WaitResults::Cancelled: - return true; - - case spawn::WaitResults::Error: // fall-through - default: - return false; - } -} - -bool OrganizerCore::waitForAllUSVFSProcessesWithLock() -{ - if (!Settings::instance().interface().lockGUI()) - return true; - - bool r = false; - - withLock([&](auto* uilock) { - r = waitForAllUSVFSProcesses(uilock); - }); - - return r; -} - -bool OrganizerCore::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) -{ - for (;;) { - const auto handles = getRunningUSVFSProcesses(); - if (handles.empty()) { - break; - } - - std::vector processes; - for (auto&& h : handles) { - auto p = env::getProcessTree(h); - if (p.isValid()) { - processes.emplace_back(std::move(p)); - } - } - - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - break; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - break; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = spawn::waitForProcess( - interestingHandle.get(), nullptr, progress); - - switch (r) - { - case spawn::WaitResults::Completed: - // this process is completed, check for others - break; - - case spawn::WaitResults::Cancelled: - // force unlocked - log::debug("waiting for process completion aborted by UI"); - return true; - - case spawn::WaitResults::Error: // fall-through - default: - log::debug("waiting for process completion not successful"); - return false; - } - } - - log::debug("waiting for process completion successful"); - return true; -} - bool OrganizerCore::onAboutToRun( const std::function &func) { @@ -1594,8 +1155,8 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives = m_DefaultArchives; } - if (m_MainWindow != nullptr) { - m_MainWindow->updateBSAList(m_DefaultArchives, m_ActiveArchives); + if (m_UserInterface != nullptr) { + m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); } m_ArchivesInit = true; @@ -1711,8 +1272,8 @@ void OrganizerCore::updateModsInDirectoryStructure(QMaparchivesWriter().writeImmediately(false); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(false); } std::vector archives = enabledArchives(); @@ -1896,8 +1457,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); origin.enable(false); } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } modInfo->clearCaches(); @@ -1946,8 +1507,8 @@ void OrganizerCore::modStatusChanged(QList index) { origin.enable(false); } } - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } @@ -2122,8 +1683,8 @@ bool OrganizerCore::saveCurrentLists() try { savePluginList(); - if (m_MainWindow != nullptr) { - m_MainWindow->archivesWriter().write(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } } catch (const std::exception &e) { reportError(tr("failed to save load order: %1").arg(e.what())); @@ -2147,11 +1708,12 @@ void OrganizerCore::savePluginList() m_PluginList.saveLoadOrder(*m_DirectoryStructure); } -void OrganizerCore::prepareStart() +void OrganizerCore::saveCurrentProfile() { if (m_CurrentProfile == nullptr) { return; } + m_CurrentProfile->writeModlist(); m_CurrentProfile->createTweakedIniFile(); saveCurrentLists(); @@ -2159,6 +1721,79 @@ void OrganizerCore::prepareStart() storeSettings(); } +ProcessRunner& OrganizerCore::processRunner() +{ + return m_Runner; +} + +bool OrganizerCore::beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries) +{ + saveCurrentProfile(); + + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } + + // need to make sure all data is saved before we start the application + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + + // TODO: should also pass arguments + if (!m_AboutToRun(binary.absoluteFilePath())) { + log::debug("start of \"{}\" cancelled by plugin", binary.absoluteFilePath()); + return false; + } + + try + { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + m_USVFS.updateForcedLibraries(forcedLibraries); + } + catch (const UsvfsConnectorException &e) + { + log::debug(e.what()); + return false; + } + catch (const std::exception &e) + { + QWidget* w = nullptr; + if (m_UserInterface) { + w = m_UserInterface->qtWidget(); + } + QMessageBox::warning(w, tr("Error"), e.what()); + return false; + } + + return true; +} + +void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) +{ + refreshDirectoryStructure(); + + // need to remove our stored load order because it may be outdated if a + // foreign tool changed the file time. After removing that file, + // refreshESPList will use the file time as the order + if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { + log::debug("removing loadorder.txt"); + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + + refreshDirectoryStructure(); + + refreshESPList(true); + savePluginList(); + cycleDiagnostics(); + + //These callbacks should not fiddle with directoy structure and ESPs. + m_FinishedRun(binary.absoluteFilePath(), exitCode); +} + std::vector OrganizerCore::fileMapping(const QString &profileName, const QString &customOverwrite) { diff --git a/src/organizercore.h b/src/organizercore.h index ffdb6830..2252c118 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -3,7 +3,6 @@ #include "selfupdater.h" -#include "ilockedwaitingforprocess.h" #include "settings.h" #include "modlist.h" #include "modinfo.h" @@ -14,6 +13,7 @@ #include "executableslist.h" #include "usvfsconnector.h" #include "moshortcut.h" +#include "processrunner.h" #include #include #include @@ -26,7 +26,7 @@ class ModListSortProxy; class PluginListSortProxy; class Profile; -class MainWindow; +class IUserInterface; namespace MOBase { template class GuessedValue; @@ -97,7 +97,7 @@ public: ~OrganizerCore(); - void setUserInterface(MainWindow* mainWindow); + void setUserInterface(IUserInterface* ui); void connectPlugins(PluginContainer *container); void disconnectPlugins(); @@ -134,7 +134,14 @@ public: bool saveCurrentLists(); - void prepareStart(); + ProcessRunner& processRunner(); + + bool beforeRun( + const QFileInfo& binary, const QString& profileName, + const QString& customOverwrite, + const QList& forcedLibraries); + + void afterRun(const QFileInfo& binary, DWORD exitCode); void refreshESPList(bool force = false); void refreshBSAList(); @@ -149,27 +156,6 @@ public: bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); - bool runFile(QWidget* parent, const QFileInfo& targetInfo); - - bool runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &forcedLibraries={}, - bool refresh=true); - - bool runExecutable(const Executable& exe, bool refresh=true); - - bool runShortcut(const MOShortcut& shortcut); - - HANDLE runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - bool waitForAllUSVFSProcessesWithLock(); - void loginSuccessfulUpdate(bool necessary); void loginFailedUpdate(const QString &message); @@ -273,6 +259,7 @@ signals: private: + void saveCurrentProfile(); void storeSettings(); bool queryApi(QString &apiKey); @@ -298,26 +285,6 @@ private: const MOShared::DirectoryEntry *directoryEntry, int createDestination); - HANDLE spawnAndWait(const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries = QList(), - LPDWORD exitCode = nullptr); - - bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); - - bool waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); - - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); - - void withLock(std::function f); - - HANDLE findAndOpenAUSVFSProcess( - const std::vector& hiddenList, DWORD preferedParentPid); - private slots: void directory_refreshed(); @@ -331,13 +298,14 @@ private: static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - MainWindow* m_MainWindow; + IUserInterface* m_UserInterface; PluginContainer *m_PluginContainer; QString m_GameName; MOBase::IPluginGame *m_GamePlugin; Profile *m_CurrentProfile; + ProcessRunner m_Runner; Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 2ea1761a..75b3ea41 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -112,14 +112,14 @@ HANDLE OrganizerProxy::startApplication( const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) { - return m_Proxied->runExecutableOrExecutableFile( + return m_Proxied->processRunner().runExecutableOrExecutableFile( executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->waitForApplication(handle, exitCode); + return m_Proxied->processRunner().waitForApplication(handle, exitCode); } bool OrganizerProxy::onAboutToRun(const std::function &func) diff --git a/src/processrunner.cpp b/src/processrunner.cpp new file mode 100644 index 00000000..81c4b99e --- /dev/null +++ b/src/processrunner.cpp @@ -0,0 +1,634 @@ +#include "processrunner.h" +#include "organizercore.h" +#include "instancemanager.h" +#include "lockeddialog.h" +#include "iuserinterface.h" +#include "envmodule.h" +#include + +using namespace MOBase; + +void adjustForVirtualized( + const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +{ + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} + +env::Process* getInterestingProcess(std::vector& processes) +{ + if (processes.empty()) { + return nullptr; + } + + // Certain process names we wish to "hide" for aesthetic reason: + const std::vector hiddenList = { + QFileInfo(QCoreApplication::applicationFilePath()).fileName() + }; + + auto isHidden = [&](auto&& p) { + for (auto h : hiddenList) { + if (p.name().contains(h, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }; + + + for (auto&& root : processes) { + if (!isHidden(root)) { + return &root; + } + + for (auto&& child : root.children()) { + if (!isHidden(child)) { + return &child; + } + } + } + + + // everything is hidden, just pick the first one + return &processes[0]; +} + + +SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) + : m_handle(handle), m_parameters(std::move(sp)) +{ +} + +SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) + : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) +{ + other.m_handle = INVALID_HANDLE_VALUE; +} + +SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) +{ + if (this != &other) { + destroy(); + + m_handle = other.m_handle; + other.m_handle = INVALID_HANDLE_VALUE; + + m_parameters = std::move(other.m_parameters); + } + + return *this; +} + +SpawnedProcess::~SpawnedProcess() +{ + destroy(); +} + +HANDLE SpawnedProcess::releaseHandle() +{ + const auto h = m_handle; + m_handle = INVALID_HANDLE_VALUE; + return h; +} + +void SpawnedProcess::destroy() +{ + if (m_handle != INVALID_HANDLE_VALUE) { + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + } +} + + +ProcessRunner::ProcessRunner(OrganizerCore& core) + : m_core(core), m_ui(nullptr) +{ +} + +void ProcessRunner::setUserInterface(IUserInterface* ui) +{ + m_ui = ui; +} + +bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +{ + if (!parent && m_ui) { + parent = m_ui->qtWidget(); + } + + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); + + switch (fec.type) + { + case spawn::FileExecutionTypes::Executable: + { + runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); + return true; + } + + case spawn::FileExecutionTypes::Other: // fall-through + default: + { + auto r = shell::Open(targetInfo.absoluteFilePath()); + if (!r.success()) { + return false; + } + + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer) + if (r.processHandle() != INVALID_HANDLE_VALUE) { + // steal because it gets closed after the wait + return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); + } + + return true; + } + } +} + +bool ProcessRunner::runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + bool refresh) +{ + DWORD processExitCode = 0; + HANDLE processHandle = spawnAndWait( + binary, arguments, m_core.currentProfile()->name(), + currentDirectory, steamAppID, customOverwrite, forcedLibraries, + &processExitCode); + + if (processHandle == INVALID_HANDLE_VALUE) { + // failed + return false; + } + + if (refresh) { + m_core.afterRun(binary, processExitCode); + } + + return true; +} + +bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + const QString customOverwrite = profile->setting( + "custom_overwrites", exe.title()).toString(); + + QList forcedLibraries; + + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + + return runExecutableFile( + exe.binaryInfo(), + exe.arguments(), + exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), + exe.steamAppID(), + customOverwrite, + forcedLibraries, + refresh); +} + +bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +{ + if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + } + + const Executable& exe = m_core.executablesList()->get(shortcut.executable()); + return runExecutable(exe, false); +} + +HANDLE ProcessRunner::runExecutableOrExecutableFile( + const QString& executable, const QStringList &args, const QString &cwd, + const QString& profileOverride, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) +{ + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } + + QString profileName = profileOverride; + if (profileName == "") { + profileName = profile->name(); + } + + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString steamAppID; + QString customOverwrite; + QList forcedLibraries; + + if (executable.contains('\\') || executable.contains('/')) { + // file path + + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); + } + + if (currentDirectory == "") { + currentDirectory = binary.absolutePath(); + } + + try { + const Executable& exe = m_core.executablesList()->getByBinary(binary); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_core.executablesList()->get(executable); + steamAppID = exe.steamAppID(); + customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + if (profile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = profile->determineForcedLibraries(exe.title()); + } + if (arguments == "") { + arguments = exe.arguments(); + } + binary = exe.binaryInfo(); + if (currentDirectory == "") { + currentDirectory = exe.workingDirectory(); + } + } catch (const std::runtime_error &) { + log::warn("\"{}\" not set up as executable", executable); + binary = QFileInfo(executable); + } + } + + if (!forcedCustomOverwrite.isEmpty()) + customOverwrite = forcedCustomOverwrite; + + if (ignoreCustomOverwrite) + customOverwrite.clear(); + + return spawnAndWait( + binary, + arguments, + profileName, + currentDirectory, + steamAppID, + customOverwrite, + forcedLibraries); +} + +HANDLE ProcessRunner::spawnAndWait( + const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + LPDWORD exitCode) +{ + spawn::SpawnParameters sp; + sp.binary = binary; + sp.arguments = arguments; + sp.currentDirectory = currentDirectory; + sp.steamAppID = steamAppID; + sp.hooked = true; + + if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) { + return INVALID_HANDLE_VALUE; + } + + HANDLE handle = spawn(sp).releaseHandle(); + + if (handle == INVALID_HANDLE_VALUE) { + // failed + return INVALID_HANDLE_VALUE; + } + + waitForProcessCompletionWithLock(handle, exitCode); + return handle; +} + +SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) +{ + QWidget* parent = nullptr; + if (m_ui) { + parent = m_ui->qtWidget(); + } + + if (!checkBinary(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + + if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkEnvironment(parent, sp)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + if (!checkBlacklist(parent, sp, settings)) { + return {INVALID_HANDLE_VALUE, sp}; + } + + adjustForVirtualized(game, sp, settings); + + return {startBinary(parent, sp), sp}; +} + +void ProcessRunner::withLock(std::function f) +{ + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; + + if (m_ui != nullptr) { + uilock = m_ui->lock(); + } + else { + // i.e. when running command line shortcuts there is no user interface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); + } + + Guard g([&]() { + if (m_ui != nullptr) { + m_ui->unlock(); + } + }); + + f(uilock); +} + +bool ProcessRunner::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) { + return true; + } + + bool r = false; + + withLock([&](auto* uilock) { + DWORD ignoreExitCode; + r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForProcessCompletion(handle, exitCode, uilock); + }); + + return r; +} + +bool ProcessRunner::waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto tree = env::getProcessTree(handle); + std::vector processes = {tree}; + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return true; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return true; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), exitCode, progress); + + switch (r) + { + case WaitResults::Completed: // fall-through + case WaitResults::Cancelled: + return true; + + case WaitResults::Error: // fall-through + default: + return false; + } +} + +bool ProcessRunner::waitForAllUSVFSProcessesWithLock() +{ + if (!Settings::instance().interface().lockGUI()) + return true; + + bool r = false; + + withLock([&](auto* uilock) { + r = waitForAllUSVFSProcesses(uilock); + }); + + return r; +} + +bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +{ + for (;;) { + const auto handles = getRunningUSVFSProcesses(); + if (handles.empty()) { + break; + } + + std::vector processes; + for (auto&& h : handles) { + auto p = env::getProcessTree(h); + if (p.isValid()) { + processes.emplace_back(std::move(p)); + } + } + + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + break; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + break; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + const auto r = waitForProcess( + interestingHandle.get(), nullptr, progress); + + switch (r) + { + case WaitResults::Completed: + // this process is completed, check for others + break; + + case WaitResults::Cancelled: + // force unlocked + log::debug("waiting for process completion aborted by UI"); + return true; + + case WaitResults::Error: // fall-through + default: + log::debug("waiting for process completion not successful"); + return false; + } + } + + log::debug("waiting for process completion successful"); + return true; +} + + + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } + + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + const auto r = waitForProcesses(handles, exitCodes, progress); + + if (r == WaitResults::Completed) { + if (exitCode && !exitCodes.empty()) { + *exitCode = exitCodes[0]; + } + } + + return r; +} + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress) +{ + if (handles.empty()) { + return WaitResults::Completed; + } + + const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + static_cast(handles.size()), &handles[0], + TRUE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion, {}", formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { + // completed + exitCodes.resize(handles.size()); + std::fill(exitCodes.begin(), exitCodes.end(), 0); + + for (std::size_t i=0; i + +class OrganizerCore; +class ILockedWaitingForProcess; +class IUserInterface; +class Executable; +class MOShortcut; + +class SpawnedProcess +{ +public: + SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp); + + SpawnedProcess(const SpawnedProcess&) = delete; + SpawnedProcess& operator=(const SpawnedProcess&) = delete; + SpawnedProcess(SpawnedProcess&& other); + SpawnedProcess& operator=(SpawnedProcess&& other); + ~SpawnedProcess(); + + HANDLE releaseHandle(); + void wait(); + +private: + HANDLE m_handle; + spawn::SpawnParameters m_parameters; + + void destroy(); +}; + + +class ProcessRunner +{ +public: + ProcessRunner(OrganizerCore& core); + + void setUserInterface(IUserInterface* ui); + + bool runFile(QWidget* parent, const QFileInfo& targetInfo); + + bool runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID={}, + const QString &customOverwrite={}, + const QList &forcedLibraries={}, + bool refresh=true); + + bool runExecutable(const Executable& exe, bool refresh=true); + + bool runShortcut(const MOShortcut& shortcut); + + HANDLE runExecutableOrExecutableFile( + const QString &executable, const QStringList &args, const QString &cwd, + const QString &profile, const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + + bool waitForAllUSVFSProcessesWithLock(); + +private: + OrganizerCore& m_core; + IUserInterface* m_ui; + + HANDLE spawnAndWait( + const QFileInfo &binary, const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries={}, + LPDWORD exitCode = nullptr); + + SpawnedProcess spawn(spawn::SpawnParameters sp); + + void withLock(std::function f); + + bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + + bool waitForProcessCompletion( + HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + + bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); +}; + + +enum class WaitResults +{ + Completed = 1, + Error, + Cancelled +}; + +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress); + +WaitResults waitForProcesses( + const std::vector& handles, std::vector& exitCodes, + std::function progress); + +#endif // PROCESSRUNNER_H diff --git a/src/spawn.cpp b/src/spawn.cpp index 0ea60641..c8d7c76a 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -772,50 +772,6 @@ bool checkBlacklist( } } - -void adjustForVirtualized( - const IPluginGame* game, SpawnParameters& sp, const Settings& settings) -{ - const QString modsPath = settings.paths().mods(); - - // Check if this a request with either an executable or a working directory - // under our mods folder then will start the process in a virtualized - // "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; - } - - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); - - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); - } -} - - HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) { HANDLE handle = INVALID_HANDLE_VALUE; @@ -842,80 +798,6 @@ HANDLE startBinary(QWidget* parent, const SpawnParameters& sp) } } - - -SpawnedProcess::SpawnedProcess(HANDLE handle, SpawnParameters sp) - : m_handle(handle), m_parameters(std::move(sp)) -{ -} - -SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) - : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) -{ - other.m_handle = INVALID_HANDLE_VALUE; -} - -SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) -{ - if (this != &other) { - destroy(); - - m_handle = other.m_handle; - other.m_handle = INVALID_HANDLE_VALUE; - - m_parameters = std::move(other.m_parameters); - } - - return *this; -} - -SpawnedProcess::~SpawnedProcess() -{ - destroy(); -} - -HANDLE SpawnedProcess::releaseHandle() -{ - const auto h = m_handle; - m_handle = INVALID_HANDLE_VALUE; - return h; -} - -void SpawnedProcess::destroy() -{ - if (m_handle != INVALID_HANDLE_VALUE) { - ::CloseHandle(m_handle); - m_handle = INVALID_HANDLE_VALUE; - } -} - - -SpawnedProcess Spawner::spawn( - QWidget* parent, const IPluginGame* game, - SpawnParameters sp, Settings& settings) -{ - if (!checkBinary(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkEnvironment(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - if (!checkBlacklist(parent, sp, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } - - adjustForVirtualized(game, sp, settings); - - return {startBinary(parent, sp), sp}; -} - - QString getExecutableForJarFile(const QString& jarFile) { const std::wstring jarFileW = jarFile.toStdWString(); @@ -1094,86 +976,6 @@ FileExecutionContext getFileExecutionContext( return {{}, {}, FileExecutionTypes::Other}; } -WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, std::function progress) -{ - if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; - } - - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); - - std::vector handles; - handles.push_back(handle); - - std::vector exitCodes; - - const auto r = waitForProcesses(handles, exitCodes, progress); - - if (r == WaitResults::Completed) { - if (exitCode && !exitCodes.empty()) { - *exitCode = exitCodes[0]; - } - } - - return r; -} - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress) -{ - if (handles.empty()) { - return WaitResults::Completed; - } - - const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); - - for (;;) { - // Wait for a an event on the handle, a key press, mouse click or timeout - const auto res = MsgWaitForMultipleObjects( - static_cast(handles.size()), &handles[0], - TRUE, 50, QS_KEY | QS_MOUSEBUTTON); - - if (res == WAIT_FAILED) { - // error - const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; - } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { - // completed - exitCodes.resize(handles.size()); - std::fill(exitCodes.begin(), exitCodes.end(), 0); - - for (std::size_t i=0; i progress); - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress); - } // namespace -- cgit v1.3.1 From b05cbeed900fbb3492e3cb5b624f5d9efaa288ea Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 12:08:13 -0400 Subject: refactored waiting into waitForProcesses() --- src/processrunner.cpp | 276 +++++++++++++++++++++----------------------------- src/processrunner.h | 15 --- 2 files changed, 116 insertions(+), 175 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 81c4b99e..dbcee3ab 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -8,45 +8,62 @@ using namespace MOBase; -void adjustForVirtualized( - const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +enum class WaitResults { - const QString modsPath = settings.paths().mods(); + Completed = 1, + Error, + Cancelled +}; - // Check if this a request with either an executable or a working directory - // under our mods folder then will start the process in a virtualized - // "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; - } +WaitResults waitForProcess( + HANDLE handle, DWORD* exitCode, std::function progress) +{ + if (handle == INVALID_HANDLE_VALUE) { + return WaitResults::Error; + } - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; + log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + + std::vector handles; + handles.push_back(handle); + + std::vector exitCodes; + + for (;;) { + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + + if (res == WAIT_FAILED) { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion, {}", formatSystemMessage(e)); + + return WaitResults::Error; + } else if (res == WAIT_OBJECT_0) { + // completed + if (exitCode) { + if (!::GetExitCodeProcess(handle, exitCode)) { + const auto e = ::GetLastError(); + log::warn( + "failed to get exit code of process, {}", + formatSystemMessage(e)); + } + } + + return WaitResults::Completed; } - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + if (progress && progress()) { + return WaitResults::Cancelled; + } } } @@ -84,11 +101,32 @@ env::Process* getInterestingProcess(std::vector& processes) } } - // everything is hidden, just pick the first one return &processes[0]; } +WaitResults waitForProcesses( + std::vector& processes, + LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + const auto* interesting = getInterestingProcess(processes); + if (!interesting) { + return WaitResults::Error; + } + + if (uilock) { + uilock->setProcessInformation(interesting->pid(), interesting->name()); + } + + auto interestingHandle = interesting->openHandleForWait(); + if (!interestingHandle) { + return WaitResults::Error; + } + + auto progress = [&]{ return uilock->unlockForced(); }; + return waitForProcess(interestingHandle.get(), exitCode, progress); +} + SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) : m_handle(handle), m_parameters(std::move(sp)) @@ -358,6 +396,48 @@ HANDLE ProcessRunner::spawnAndWait( return handle; } +void adjustForVirtualized( + const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) +{ + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} + SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) { QWidget* parent = nullptr; @@ -451,34 +531,8 @@ bool ProcessRunner::waitForProcessCompletion( const auto tree = env::getProcessTree(handle); std::vector processes = {tree}; - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - return true; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - return true; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = waitForProcess( - interestingHandle.get(), exitCode, progress); - - switch (r) - { - case WaitResults::Completed: // fall-through - case WaitResults::Cancelled: - return true; - - case WaitResults::Error: // fall-through - default: - return false; - } + const auto r = waitForProcesses(processes, exitCode, uilock); + return (r != WaitResults::Error); } bool ProcessRunner::waitForAllUSVFSProcessesWithLock() @@ -511,23 +565,7 @@ bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) } } - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - break; - } - - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); - } - - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - break; - } - - auto progress = [&]{ return uilock->unlockForced(); }; - const auto r = waitForProcess( - interestingHandle.get(), nullptr, progress); + const auto r = waitForProcesses(processes, nullptr, uilock); switch (r) { @@ -550,85 +588,3 @@ bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) log::debug("waiting for process completion successful"); return true; } - - - -WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, std::function progress) -{ - if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; - } - - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); - - std::vector handles; - handles.push_back(handle); - - std::vector exitCodes; - - const auto r = waitForProcesses(handles, exitCodes, progress); - - if (r == WaitResults::Completed) { - if (exitCode && !exitCodes.empty()) { - *exitCode = exitCodes[0]; - } - } - - return r; -} - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress) -{ - if (handles.empty()) { - return WaitResults::Completed; - } - - const auto WAIT_OBJECT_N = static_cast(WAIT_OBJECT_0 + handles.size()); - - for (;;) { - // Wait for a an event on the handle, a key press, mouse click or timeout - const auto res = MsgWaitForMultipleObjects( - static_cast(handles.size()), &handles[0], - TRUE, 50, QS_KEY | QS_MOUSEBUTTON); - - if (res == WAIT_FAILED) { - // error - const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; - } else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_N) { - // completed - exitCodes.resize(handles.size()); - std::fill(exitCodes.begin(), exitCodes.end(), 0); - - for (std::size_t i=0; i progress); - -WaitResults waitForProcesses( - const std::vector& handles, std::vector& exitCodes, - std::function progress); - #endif // PROCESSRUNNER_H -- cgit v1.3.1 From b5a5ea1b4d97d79fe6f4ff8a2da2e0120936b179 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 13:17:44 -0400 Subject: recheck the process tree if the current process is not that interesting --- src/processrunner.cpp | 223 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 165 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index dbcee3ab..4e1dda46 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -12,38 +12,27 @@ enum class WaitResults { Completed = 1, Error, - Cancelled + Cancelled, + StillRunning }; -WaitResults waitForProcess( - HANDLE handle, DWORD* exitCode, std::function progress) +WaitResults singleWait(HANDLE handle, DWORD* exitCode) { if (handle == INVALID_HANDLE_VALUE) { return WaitResults::Error; } - log::debug("waiting for completion on pid {}", ::GetProcessId(handle)); + const DWORD WAIT_EVENT = WAIT_OBJECT_0 + 1; - std::vector handles; - handles.push_back(handle); + // Wait for a an event on the handle, a key press, mouse click or timeout + const auto res = MsgWaitForMultipleObjects( + 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); - std::vector exitCodes; - - for (;;) { - // Wait for a an event on the handle, a key press, mouse click or timeout - const auto res = MsgWaitForMultipleObjects( - 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); - - if (res == WAIT_FAILED) { - // error - const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; - } else if (res == WAIT_OBJECT_0) { + switch (res) + { + case WAIT_OBJECT_0: + { // completed if (exitCode) { if (!::GetExitCodeProcess(handle, exitCode)) { @@ -57,20 +46,55 @@ WaitResults waitForProcess( return WaitResults::Completed; } - // keep processing events so the app doesn't appear dead - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); + case WAIT_TIMEOUT: + case WAIT_EVENT: + { + return WaitResults::StillRunning; + } - if (progress && progress()) { - return WaitResults::Cancelled; + case WAIT_FAILED: // fall-through + default: + { + // error + const auto e = ::GetLastError(); + + log::error( + "failed waiting for process completion, {}", formatSystemMessage(e)); + + return WaitResults::Error; } } } -env::Process* getInterestingProcess(std::vector& processes) +enum class Interest +{ + None = 0, + Weak, + Strong +}; + +QString toString(Interest i) +{ + switch (i) + { + case Interest::Weak: + return "weak"; + + case Interest::Strong: + return "strong"; + + case Interest::None: // fall-through + default: + return "no"; + } +} + + +std::pair findInterestingProcessInTrees( + std::vector& processes) { if (processes.empty()) { - return nullptr; + return {{}, Interest::None}; } // Certain process names we wish to "hide" for aesthetic reason: @@ -91,40 +115,132 @@ env::Process* getInterestingProcess(std::vector& processes) for (auto&& root : processes) { if (!isHidden(root)) { - return &root; + return {root, Interest::Strong}; } for (auto&& child : root.children()) { if (!isHidden(child)) { - return &child; + return {child, Interest::Strong}; } } } // everything is hidden, just pick the first one - return &processes[0]; + return {processes[0], Interest::Weak}; } -WaitResults waitForProcesses( - std::vector& processes, - LPDWORD exitCode, ILockedWaitingForProcess* uilock) +std::pair getInterestingProcess( + const std::vector& initialProcesses) { - const auto* interesting = getInterestingProcess(processes); - if (!interesting) { - return WaitResults::Error; + std::vector processes; + + log::debug("getting process tree for {} processes", initialProcesses.size()); + for (auto&& h : initialProcesses) { + auto tree = env::getProcessTree(h); + if (tree.isValid()) { + processes.push_back(tree); + } } - if (uilock) { - uilock->setProcessInformation(interesting->pid(), interesting->name()); + if (processes.empty()) { + log::debug("nothing to wait for"); + return {{}, Interest::None}; } - auto interestingHandle = interesting->openHandleForWait(); - if (!interestingHandle) { - return WaitResults::Error; + const auto interest = findInterestingProcessInTrees(processes); + if (!interest.first.isValid()) { + log::debug("no interesting process to wait for"); + return {{}, Interest::None}; } - auto progress = [&]{ return uilock->unlockForced(); }; - return waitForProcess(interestingHandle.get(), exitCode, progress); + return interest; +} + +const std::chrono::milliseconds Infinite(-1); + +WaitResults timedWait( + HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock, + std::chrono::milliseconds wait) +{ + using namespace std::chrono; + + high_resolution_clock::time_point start; + if (wait != Infinite) { + start = high_resolution_clock::now(); + } + + for (;;) { + const auto r = singleWait(handle, exitCode); + + if (r != WaitResults::StillRunning) { + return r; + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + + if (uilock && uilock->unlockForced()) { + return WaitResults::Cancelled; + } + + if (wait != Infinite) { + const auto now = high_resolution_clock::now(); + if (duration_cast(now - start) >= wait) { + return WaitResults::StillRunning; + } + } + } +} + +WaitResults waitForProcesses( + const std::vector& initialProcesses, + LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + using namespace std::chrono; + + if (initialProcesses.empty()) { + return WaitResults::Completed; + } + + DWORD currentPID = 0; + milliseconds wait(50); + + for (;;) { + auto [p, interest] = getInterestingProcess(initialProcesses); + + if (uilock) { + uilock->setProcessInformation(p.pid(), p.name()); + } + + auto interestingHandle = p.openHandleForWait(); + if (!interestingHandle) { + return WaitResults::Error; + } + + if (p.pid() != currentPID) { + currentPID = p.pid(); + + log::debug( + "waiting for completion on {} ({}), {} interest", + p.name(), p.pid(), toString(interest)); + } + + if (interest == Interest::Strong) { + wait = Infinite; + } + + const auto r = timedWait(interestingHandle.get(), exitCode, uilock, wait); + if (r != WaitResults::StillRunning) { + return r; + } + + wait = std::min(wait * 2, milliseconds(2000)); + + log::debug( + "looking for a more interesting process (next check in {}ms)", + wait.count()); + } } @@ -528,10 +644,9 @@ bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) bool ProcessRunner::waitForProcessCompletion( HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) { - const auto tree = env::getProcessTree(handle); - std::vector processes = {tree}; - + std::vector processes = {handle}; const auto r = waitForProcesses(processes, exitCode, uilock); + return (r != WaitResults::Error); } @@ -552,19 +667,11 @@ bool ProcessRunner::waitForAllUSVFSProcessesWithLock() bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) { for (;;) { - const auto handles = getRunningUSVFSProcesses(); - if (handles.empty()) { + const auto processes = getRunningUSVFSProcesses(); + if (processes.empty()) { break; } - std::vector processes; - for (auto&& h : handles) { - auto p = env::getProcessTree(h); - if (p.isValid()) { - processes.emplace_back(std::move(p)); - } - } - const auto r = waitForProcesses(processes, nullptr, uilock); switch (r) -- cgit v1.3.1 From f2cc71779feab647c508084c5dd5c175904c7f0f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 13:40:45 -0400 Subject: always wait until completion in waitForApplication(), regardless of lock setting --- src/processrunner.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 4e1dda46..014f98ee 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -592,8 +592,7 @@ void ProcessRunner::withLock(std::function f) if (m_ui != nullptr) { uilock = m_ui->lock(); - } - else { + } else { // i.e. when running command line shortcuts there is no user interface dlg.reset(new LockedDialog); dlg->show(); @@ -614,14 +613,14 @@ bool ProcessRunner::waitForProcessCompletionWithLock( HANDLE handle, LPDWORD exitCode) { if (!Settings::instance().interface().lockGUI()) { + log::debug("not waiting for process because user has disabled locking"); return true; } bool r = false; withLock([&](auto* uilock) { - DWORD ignoreExitCode; - r = waitForProcessCompletion(handle, exitCode ? exitCode : &ignoreExitCode, uilock); + r = waitForProcessCompletion(handle, exitCode, uilock); }); return r; @@ -629,8 +628,14 @@ bool ProcessRunner::waitForProcessCompletionWithLock( bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) { - if (!Settings::instance().interface().lockGUI()) - return true; + // don't check for lockGUI() setting; this _always_ locks the ui + // + // this is typically called only from OrganizerProxy, which allows plugins + // to wait on applications until they're finished + // + // the check_fnis plugin for example will start FNIS, wait for it to complete, + // and then check the exit code; this has to work regardless of the locking + // setting bool r = false; @@ -652,8 +657,10 @@ bool ProcessRunner::waitForProcessCompletion( bool ProcessRunner::waitForAllUSVFSProcessesWithLock() { - if (!Settings::instance().interface().lockGUI()) + if (!Settings::instance().interface().lockGUI()) { + log::debug("not waiting for usvfs processes because user has disabled locking"); return true; + } bool r = false; -- cgit v1.3.1 From 2aa70de49e89245467299d94d76825bad31c63a2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 29 Oct 2019 13:46:33 -0400 Subject: return failure if the user has unlocked in waitForApplication() --- src/processrunner.cpp | 34 ++++++++++++++++++---------------- src/processrunner.h | 3 --- 2 files changed, 18 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 014f98ee..5d4a9fde 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -243,6 +243,14 @@ WaitResults waitForProcesses( } } +WaitResults waitForProcess( + HANDLE initialProcess, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + std::vector processes = {initialProcess}; + return waitForProcesses(processes, exitCode, uilock); +} + + SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) : m_handle(handle), m_parameters(std::move(sp)) @@ -617,18 +625,20 @@ bool ProcessRunner::waitForProcessCompletionWithLock( return true; } - bool r = false; + auto r = WaitResults::Error; withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); + r = waitForProcess(handle, exitCode, uilock); }); - return r; + // completed/unlocked is fine + return (r != WaitResults::Error); } bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) { - // don't check for lockGUI() setting; this _always_ locks the ui + // don't check for lockGUI() setting; this _always_ locks the ui and waits + // for completion // // this is typically called only from OrganizerProxy, which allows plugins // to wait on applications until they're finished @@ -637,22 +647,14 @@ bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) // and then check the exit code; this has to work regardless of the locking // setting - bool r = false; + auto r = WaitResults::Error; withLock([&](auto* uilock) { - r = waitForProcessCompletion(handle, exitCode, uilock); + r = waitForProcess(handle, exitCode, uilock); }); - return r; -} - -bool ProcessRunner::waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) -{ - std::vector processes = {handle}; - const auto r = waitForProcesses(processes, exitCode, uilock); - - return (r != WaitResults::Error); + // treat unlocked as an error since this should always wait for completion + return (r == WaitResults::Completed); } bool ProcessRunner::waitForAllUSVFSProcessesWithLock() diff --git a/src/processrunner.h b/src/processrunner.h index d9423893..28f4da75 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,9 +80,6 @@ private: bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); - bool waitForProcessCompletion( - HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); }; -- cgit v1.3.1 From db0b92776b5c9a34ebb1a5ce5c3f0b105844ee16 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 30 Oct 2019 23:42:59 -0400 Subject: added lockwidget to replace all the other dialogs rewrote ProcessRunner to have a bunch of setters and then a run() fixed bad exit code when waiting on a process that's already completed removed lock()/unlock() from main window, ProcessRunner is in charge of that now --- src/CMakeLists.txt | 3 + src/envmodule.cpp | 1 - src/iuserinterface.h | 6 +- src/lockwidget.cpp | 224 ++++++++++++++++ src/lockwidget.h | 68 +++++ src/mainwindow.cpp | 44 +--- src/mainwindow.h | 7 - src/organizercore.cpp | 8 +- src/organizercore.h | 3 +- src/organizerproxy.cpp | 19 +- src/pch.h | 47 ++-- src/processrunner.cpp | 687 ++++++++++++++++++++++++++++--------------------- src/processrunner.h | 70 ++++- 13 files changed, 806 insertions(+), 381 deletions(-) create mode 100644 src/lockwidget.cpp create mode 100644 src/lockwidget.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e909760..168b79dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ SET(organizer_SRCS colortable.cpp sanitychecks.cpp processrunner.cpp + lockwidget.cpp shared/windows_error.cpp shared/error_report.cpp @@ -268,6 +269,7 @@ SET(organizer_HDRS envwindows.h colortable.h processrunner.h + lockwidget.h shared/windows_error.h shared/error_report.h @@ -486,6 +488,7 @@ set(widgets filterwidget icondelegate lcdnumber + lockwidget loglist loghighlighter modflagicondelegate diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 0e2e8ec7..09593e61 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -545,7 +545,6 @@ Process getProcessTree(HANDLE parent) } if (root.pid() == 0) { - log::error("process {} is not running", parentPID); return {}; } diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 91487aee..e5755f03 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -4,12 +4,13 @@ #include "modinfodialogfwd.h" #include "ilockedwaitingforprocess.h" +#include "lockwidget.h" #include #include #include - #include + class IUserInterface { public: @@ -31,9 +32,6 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0; - virtual ILockedWaitingForProcess* lock() = 0; - virtual void unlock() = 0; - virtual QWidget* qtWidget() = 0; }; diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp new file mode 100644 index 00000000..720cac36 --- /dev/null +++ b/src/lockwidget.cpp @@ -0,0 +1,224 @@ +#include "lockwidget.h" +#include "mainwindow.h" +#include +#include +#include + +QWidget* createTransparentWidget(QWidget* parent=nullptr) +{ + auto* w = new QWidget(parent); + + w->setWindowOpacity(0); + w->setAttribute(Qt::WA_NoSystemBackground); + w->setAttribute(Qt::WA_TranslucentBackground); + + return w; +} + + +LockWidget::LockWidget(QWidget* parent, Reasons reason) : + m_parent(parent), m_overlay(nullptr), m_info(nullptr), m_result(NoResult), + m_filter(nullptr) +{ + if (reason != NoReason) { + lock(reason); + } +} + +LockWidget::~LockWidget() +{ + unlock(); +} + +void LockWidget::lock(Reasons reason) +{ + m_result = StillLocked; + createUi(reason); +} + +void LockWidget::unlock() +{ + m_overlay.reset(); + + if (m_filter && m_parent) { + m_parent->removeEventFilter(m_filter.get()); + } + + enableAll(); +} + +void LockWidget::setInfo(DWORD pid, const QString& name) +{ + m_info->setText(QString("%1 (%2)").arg(name).arg(pid)); +} + +LockWidget::Results LockWidget::result() const +{ + return m_result; +} + +void LockWidget::createUi(Reasons reason) +{ + if (m_parent) { + m_overlay.reset(createTransparentWidget(m_parent)); + m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint); + m_overlay->setGeometry(m_parent->rect()); + } else { + m_overlay.reset(new QDialog); + } + + auto* center = new QFrame; + + if (m_parent) { + center->setFrameStyle(QFrame::StyledPanel); + center->setLineWidth(1); + center->setAutoFillBackground(true); + + auto* shadow = new QGraphicsDropShadowEffect; + shadow->setBlurRadius(50); + shadow->setOffset(0); + shadow->setColor(QColor(0, 0, 0, 100)); + center->setGraphicsEffect(shadow); + } + + m_info = new QLabel(" "); + m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + + auto* ly = new QVBoxLayout(center); + + if (!m_parent) { + ly->setContentsMargins(0, 0, 0, 0); + } + + auto* message = new QLabel; + ly->addWidget(message); + ly->addWidget(m_info); + + auto* buttons = new QWidget; + auto* buttonsLayout = new QHBoxLayout(buttons); + ly->addWidget(buttons); + + switch (reason) + { + case LockUI: + { + message->setText(QObject::tr( + "Mod Organizer is locked while the executable is running.")); + + auto* unlockButton = new QPushButton(QObject::tr("Unlock")); + QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); + buttonsLayout->addWidget(unlockButton); + + break; + } + + case OutputRequired: + { + message->setText(QObject::tr( + "The executable must run to completion because a its output is " + "required.")); + + auto* unlockButton = new QPushButton(QObject::tr("Unlock")); + QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); + buttonsLayout->addWidget(unlockButton); + + break; + } + + case PreventExit: + { + message->setText(QObject::tr( + "Mod Organizer is waiting on processes to finish before exiting.")); + + auto* exit = new QPushButton(QObject::tr("Exit Now")); + QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); }); + buttonsLayout->addWidget(exit); + + auto* cancel = new QPushButton(QObject::tr("Cancel")); + QObject::connect(cancel, &QPushButton::clicked, [&]{ onCancel(); }); + buttonsLayout->addWidget(cancel); + + break; + } + } + + auto* grid = new QGridLayout(m_overlay.get()); + grid->addWidget(createTransparentWidget(), 0, 1); + grid->addWidget(createTransparentWidget(), 2, 1); + grid->addWidget(createTransparentWidget(), 1, 0); + grid->addWidget(createTransparentWidget(), 1, 2); + grid->addWidget(center, 1, 1); + + if (!m_parent) { + grid->setContentsMargins(0, 0, 0, 0); + } + + grid->setRowStretch(0, 1); + grid->setRowStretch(2, 1); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(2, 1); + + disableAll(); + + if (m_parent) { + m_filter.reset(new Filter); + m_filter->resized = [=]{ m_overlay->setGeometry(m_parent->rect()); }; + m_parent->installEventFilter(m_filter.get()); + } + + m_overlay->setFocusPolicy(Qt::TabFocus); + m_overlay->setFocus(); + m_overlay->show(); + m_overlay->setEnabled(true); +} + +void LockWidget::onForceUnlock() +{ + m_result = ForceUnlocked; + unlock(); +} + +void LockWidget::onCancel() +{ + m_result = Cancelled; + unlock(); +} + +void LockWidget::disableAll() +{ + if (!m_parent) { + // nothing to disable without a main window + return; + } + + if (auto* mw=dynamic_cast(m_parent)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); + } + + for (auto* tb : m_parent->findChildren()) { + disable(tb); + } + + for (auto* d : m_parent->findChildren()) { + disable(d); + } +} + +void LockWidget::enableAll() +{ + for (auto* w : m_disabled) { + w->setEnabled(true); + } + + m_disabled.clear(); +} + +void LockWidget::disable(QWidget* w) +{ + if (w->isEnabled()) { + w->setEnabled(false); + m_disabled.push_back(w); + } +} diff --git a/src/lockwidget.h b/src/lockwidget.h new file mode 100644 index 00000000..bfb0b30f --- /dev/null +++ b/src/lockwidget.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +class LockWidget +{ +public: + enum Reasons + { + NoReason = 0, + LockUI, + OutputRequired, + PreventExit + }; + + enum Results + { + NoResult = 0, + StillLocked, + ForceUnlocked, + Cancelled + }; + + LockWidget(QWidget* parent, Reasons reason=NoReason); + ~LockWidget(); + + void lock(Reasons reason); + void unlock(); + + void setInfo(DWORD pid, const QString& name); + Results result() const; + +private: + class Filter : public QObject + { + public: + std::function resized; + + protected: + bool eventFilter(QObject* o, QEvent* e) override + { + if (e->type() == QEvent::Resize) { + if (resized) { + resized(); + } + } + + return QObject::eventFilter(o, e); + } + }; + + + QWidget* m_parent; + std::unique_ptr m_overlay; + QLabel* m_info; + Results m_result; + std::unique_ptr m_filter; + std::vector m_disabled; + + void createUi(Reasons reason); + + void onForceUnlock(); + void onCancel(); + + void disableAll(); + void enableAll(); + void disable(QWidget* w); +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ce5280a6..7d3d20b3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -57,7 +57,6 @@ along with Mod Organizer. If not, see . #include "downloadlistwidget.h" #include "messagedialog.h" #include "installationmanager.h" -#include "lockeddialog.h" #include "waitingonclosedialog.h" #include "downloadlistsortproxy.h" #include "motddialog.h" @@ -1311,9 +1310,10 @@ bool MainWindow::canExit() } } - m_exitAfterWait = true; - m_OrganizerCore.processRunner().waitForAllUSVFSProcessesWithLock(); - if (!m_exitAfterWait) { // if operation cancelled + const auto r = m_OrganizerCore.processRunner() + .waitForAllUSVFSProcessesWithLock(LockWidget::PreventExit); + + if (r == ProcessRunner::Cancelled) { return false; } @@ -2258,42 +2258,6 @@ void MainWindow::storeSettings() s.widgets().saveIndex(ui->executablesListBox); } -ILockedWaitingForProcess* MainWindow::lock() -{ - if (m_LockDialog != nullptr) { - ++m_LockCount; - return m_LockDialog; - } - if (m_exitAfterWait) - m_LockDialog = new WaitingOnCloseDialog(this); - else - m_LockDialog = new LockedDialog(this, true); - m_LockDialog->setModal(true); - m_LockDialog->show(); - setEnabled(false); - m_LockDialog->setEnabled(true); //What's the point otherwise? - ++m_LockCount; - return m_LockDialog; -} - -void MainWindow::unlock() -{ - //If you come through here with a null lock pointer, it's a bug! - if (m_LockDialog == nullptr) { - log::debug("Unlocking main window when already unlocked"); - return; - } - --m_LockCount; - if (m_LockCount == 0) { - if (m_exitAfterWait && m_LockDialog->canceled()) - m_exitAfterWait = false; - m_LockDialog->hide(); - m_LockDialog->deleteLater(); - m_LockDialog = nullptr; - setEnabled(true); - } -} - QWidget* MainWindow::qtWidget() { return this; diff --git a/src/mainwindow.h b/src/mainwindow.h index 19723480..c80287b2 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -38,7 +38,6 @@ along with Mod Organizer. If not, see . //when I get round to cleaning up main.cpp class Executable; class CategoryFactory; -class LockedDialogBase; class OrganizerCore; class PluginListSortProxy; @@ -118,8 +117,6 @@ public: void processUpdates(Settings& settings); - ILockedWaitingForProcess* lock() override; - void unlock() override; QWidget* qtWidget() override; bool addProfile(); @@ -381,11 +378,7 @@ private: bool m_DidUpdateMasterList; - LockedDialogBase *m_LockDialog { nullptr }; - uint64_t m_LockCount { 0 }; - bool m_showArchiveData{ true }; - bool m_exitAfterWait{ false }; MOBase::DelayedFileWriter m_ArchiveListWriter; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 0f767f46..89e8bd9e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -91,7 +91,6 @@ OrganizerCore::OrganizerCore(Settings &settings) , m_PluginContainer(nullptr) , m_GameName() , m_CurrentProfile(nullptr) - , m_Runner(*this) , m_Settings(settings) , m_Updater(NexusInterface::instance(m_PluginContainer)) , m_AboutToRun() @@ -252,7 +251,6 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); - m_Runner.setUserInterface(ui); checkForUpdates(); } @@ -357,7 +355,7 @@ void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { if(moshortcut.hasExecutable()) - m_Runner.runShortcut(moshortcut); + processRunner().runShortcut(moshortcut); } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); @@ -1721,9 +1719,9 @@ void OrganizerCore::saveCurrentProfile() storeSettings(); } -ProcessRunner& OrganizerCore::processRunner() +ProcessRunner OrganizerCore::processRunner() { - return m_Runner; + return ProcessRunner(*this, m_UserInterface); } bool OrganizerCore::beforeRun( diff --git a/src/organizercore.h b/src/organizercore.h index 2252c118..d4882b92 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -134,7 +134,7 @@ public: bool saveCurrentLists(); - ProcessRunner& processRunner(); + ProcessRunner processRunner(); bool beforeRun( const QFileInfo& binary, const QString& profileName, @@ -305,7 +305,6 @@ private: Profile *m_CurrentProfile; - ProcessRunner m_Runner; Settings& m_Settings; SelfUpdater m_Updater; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 75b3ea41..3ee35fe2 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -119,7 +119,24 @@ HANDLE OrganizerProxy::startApplication( bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->processRunner().waitForApplication(handle, exitCode); + const auto r = m_Proxied->processRunner().waitForApplication( + handle, exitCode, LockWidget::OutputRequired); + + switch (r) + { + case ProcessRunner::Completed: + return true; + + case ProcessRunner::Cancelled: // fall-through + case ProcessRunner::ForceUnlocked: + // this is always an error because the application should have run to + // completion + return false; + + case ProcessRunner::Error: // fall-through + default: + return false; + } } bool OrganizerProxy::onAboutToRun(const std::function &func) diff --git a/src/pch.h b/src/pch.h index 01a97357..b7c5d695 100644 --- a/src/pch.h +++ b/src/pch.h @@ -100,9 +100,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -111,13 +111,14 @@ #include #include #include -#include +#include #include +#include #include +#include #include #include #include -#include #include #include #include @@ -126,20 +127,21 @@ #include #include #include -#include #include +#include #include #include #include #include #include #include -#include #include #include +#include #include #include #include +#include #include #include #include @@ -190,53 +192,42 @@ #include #include #include -#include #include #include +#include #include #include -#include #include +#include +#include #include #include #include #include -#include #include #include #include +#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -255,4 +246,16 @@ #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 5d4a9fde..62c77efc 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -1,26 +1,58 @@ #include "processrunner.h" #include "organizercore.h" #include "instancemanager.h" -#include "lockeddialog.h" #include "iuserinterface.h" #include "envmodule.h" #include using namespace MOBase; -enum class WaitResults +void adjustForVirtualized( + const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) { - Completed = 1, - Error, - Cancelled, - StillRunning -}; + const QString modsPath = settings.paths().mods(); + + // Check if this a request with either an executable or a working directory + // under our mods folder then will start the process in a virtualized + // "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = sp.currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = sp.binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + QString adjustedCwd = cwdPath.mid(cwdOffset, -1); + cwdPath = game->dataDirectory().absolutePath(); + if (cwdOffset >= 0) + cwdPath += adjustedCwd; + + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + QString adjustedBin = binPath.mid(binOffset, -1); + binPath = game->dataDirectory().absolutePath(); + if (binOffset >= 0) + binPath += adjustedBin; + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), sp.arguments); + sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); + sp.arguments = cmdline; + sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + } +} -WaitResults singleWait(HANDLE handle, DWORD* exitCode) +std::optional singleWait(HANDLE handle, DWORD pid) { if (handle == INVALID_HANDLE_VALUE) { - return WaitResults::Error; + return ProcessRunner::Error; } const DWORD WAIT_EVENT = WAIT_OBJECT_0 + 1; @@ -33,23 +65,15 @@ WaitResults singleWait(HANDLE handle, DWORD* exitCode) { case WAIT_OBJECT_0: { - // completed - if (exitCode) { - if (!::GetExitCodeProcess(handle, exitCode)) { - const auto e = ::GetLastError(); - log::warn( - "failed to get exit code of process, {}", - formatSystemMessage(e)); - } - } - - return WaitResults::Completed; + log::debug("process {} completed", pid); + return ProcessRunner::Completed; } case WAIT_TIMEOUT: case WAIT_EVENT: { - return WaitResults::StillRunning; + // still running + return {}; } case WAIT_FAILED: // fall-through @@ -57,11 +81,8 @@ WaitResults singleWait(HANDLE handle, DWORD* exitCode) { // error const auto e = ::GetLastError(); - - log::error( - "failed waiting for process completion, {}", formatSystemMessage(e)); - - return WaitResults::Error; + log::error("failed waiting for {}, {}", pid, formatSystemMessage(e)); + return ProcessRunner::Error; } } } @@ -132,6 +153,11 @@ std::pair findInterestingProcessInTrees( std::pair getInterestingProcess( const std::vector& initialProcesses) { + if (initialProcesses.empty()) { + log::debug("nothing to wait for"); + return {{}, Interest::None}; + } + std::vector processes; log::debug("getting process tree for {} processes", initialProcesses.size()); @@ -143,7 +169,7 @@ std::pair getInterestingProcess( } if (processes.empty()) { - log::debug("nothing to wait for"); + log::debug("processes are already completed"); return {{}, Interest::None}; } @@ -158,9 +184,8 @@ std::pair getInterestingProcess( const std::chrono::milliseconds Infinite(-1); -WaitResults timedWait( - HANDLE handle, DWORD* exitCode, ILockedWaitingForProcess* uilock, - std::chrono::milliseconds wait) +std::optional timedWait( + HANDLE handle, DWORD pid, LockWidget& lock, std::chrono::milliseconds wait) { using namespace std::chrono; @@ -170,37 +195,66 @@ WaitResults timedWait( } for (;;) { - const auto r = singleWait(handle, exitCode); + const auto r = singleWait(handle, pid); - if (r != WaitResults::StillRunning) { - return r; + if (r) { + return *r; } + // still running + // keep processing events so the app doesn't appear dead QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); - if (uilock && uilock->unlockForced()) { - return WaitResults::Cancelled; + switch (lock.result()) + { + case LockWidget::StillLocked: + { + break; + } + + case LockWidget::ForceUnlocked: + { + log::debug("waiting for {} force unlocked by user", pid); + return ProcessRunner::ForceUnlocked; + } + + case LockWidget::Cancelled: + { + log::debug("waiting for {} cancelled by user", pid); + return ProcessRunner::Cancelled; + } + + case LockWidget::NoResult: // fall-through + default: + { + // shouldn't happen + log::debug( + "unexpected result {} while waiting for {}", + static_cast(lock.result()), pid); + + return ProcessRunner::Error; + } } if (wait != Infinite) { const auto now = high_resolution_clock::now(); if (duration_cast(now - start) >= wait) { - return WaitResults::StillRunning; + return {}; } } } } -WaitResults waitForProcesses( - const std::vector& initialProcesses, - LPDWORD exitCode, ILockedWaitingForProcess* uilock) +ProcessRunner::Results waitForProcesses( + const std::vector& initialProcesses, LockWidget& lock) { using namespace std::chrono; if (initialProcesses.empty()) { - return WaitResults::Completed; + // shouldn't happen + return ProcessRunner::Completed; } DWORD currentPID = 0; @@ -208,14 +262,16 @@ WaitResults waitForProcesses( for (;;) { auto [p, interest] = getInterestingProcess(initialProcesses); - - if (uilock) { - uilock->setProcessInformation(p.pid(), p.name()); + if (!p.isValid()) { + // nothing to wait on + return ProcessRunner::Completed; } + lock.setInfo(p.pid(), p.name()); + auto interestingHandle = p.openHandleForWait(); if (!interestingHandle) { - return WaitResults::Error; + return ProcessRunner::Error; } if (p.pid() != currentPID) { @@ -230,9 +286,9 @@ WaitResults waitForProcesses( wait = Infinite; } - const auto r = timedWait(interestingHandle.get(), exitCode, uilock, wait); - if (r != WaitResults::StillRunning) { - return r; + const auto r = timedWait(interestingHandle.get(), p.pid(), lock, wait); + if (r) { + return *r; } wait = std::min(wait * 2, milliseconds(2000)); @@ -243,11 +299,24 @@ WaitResults waitForProcesses( } } -WaitResults waitForProcess( - HANDLE initialProcess, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +ProcessRunner::Results waitForProcess( + HANDLE initialProcess, LPDWORD exitCode, LockWidget& lock) { std::vector processes = {initialProcess}; - return waitForProcesses(processes, exitCode, uilock); + + const auto r = waitForProcesses(processes, lock); + + // as long as it's not running anymore, try to get the exit code + if (exitCode && r != ProcessRunner::Running) { + if (!::GetExitCodeProcess(initialProcess, exitCode)) { + const auto e = ::GetLastError(); + log::warn( + "failed to get exit code of process, {}", + formatSystemMessage(e)); + } + } + + return r; } @@ -298,17 +367,64 @@ void SpawnedProcess::destroy() } -ProcessRunner::ProcessRunner(OrganizerCore& core) - : m_core(core), m_ui(nullptr) +ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : + m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(false), + m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { + m_sp.hooked = true; } -void ProcessRunner::setUserInterface(IUserInterface* ui) +ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary) { - m_ui = ui; + m_sp.binary = binary; + return *this; } -bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +ProcessRunner& ProcessRunner::setArguments(const QString& arguments) +{ + m_sp.arguments = arguments; + return *this; +} + +ProcessRunner& ProcessRunner::setCurrentDirectory(const QDir& directory) +{ + m_sp.currentDirectory = directory; + return *this; +} + +ProcessRunner& ProcessRunner::setSteamID(const QString& steamID) +{ + m_sp.steamAppID = steamID; + return *this; +} + +ProcessRunner& ProcessRunner::setCustomOverwrite(const QString& customOverwrite) +{ + m_customOverwrite = customOverwrite; + return *this; +} + +ProcessRunner& ProcessRunner::setForcedLibraries(const ForcedLibraries& forcedLibraries) +{ + m_forcedLibraries = forcedLibraries; + return *this; +} + +ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) +{ + m_profileName = profileName; + return *this; +} + +ProcessRunner& ProcessRunner::setWaitForCompletion( + LockWidget::Reasons reason, bool refresh) +{ + m_lock = reason; + m_refresh = refresh; + return *this; +} + +ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo) { if (!parent && m_ui) { parent = m_ui->qtWidget(); @@ -320,56 +436,24 @@ bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) { case spawn::FileExecutionTypes::Executable: { - runExecutableFile(fec.binary, fec.arguments, targetInfo.absoluteDir()); - return true; + setBinary(fec.binary); + setArguments(fec.arguments); + setCurrentDirectory(targetInfo.absoluteDir()); + break; } case spawn::FileExecutionTypes::Other: // fall-through default: { - auto r = shell::Open(targetInfo.absoluteFilePath()); - if (!r.success()) { - return false; - } - - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer) - if (r.processHandle() != INVALID_HANDLE_VALUE) { - // steal because it gets closed after the wait - return waitForProcessCompletionWithLock(r.stealProcessHandle(), nullptr); - } - - return true; + m_shellOpen = targetInfo.absoluteFilePath(); + break; } } -} -bool ProcessRunner::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - DWORD processExitCode = 0; - HANDLE processHandle = spawnAndWait( - binary, arguments, m_core.currentProfile()->name(), - currentDirectory, steamAppID, customOverwrite, forcedLibraries, - &processExitCode); - - if (processHandle == INVALID_HANDLE_VALUE) { - // failed - return false; - } - - if (refresh) { - m_core.afterRun(binary, processExitCode); - } - - return true; + return *this; } -bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe) { const auto* profile = m_core.currentProfile(); if (!profile) { @@ -379,25 +463,31 @@ bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) const QString customOverwrite = profile->setting( "custom_overwrites", exe.title()).toString(); - QList forcedLibraries; - + ForcedLibraries forcedLibraries; if (profile->forcedLibrariesEnabled(exe.title())) { forcedLibraries = profile->determineForcedLibraries(exe.title()); } - return runExecutableFile( - exe.binaryInfo(), - exe.arguments(), - exe.workingDirectory().length() != 0 ? exe.workingDirectory() : exe.binaryInfo().absolutePath(), - exe.steamAppID(), - customOverwrite, - forcedLibraries, - refresh); + QDir currentDirectory = exe.workingDirectory(); + if (currentDirectory.isEmpty()) { + currentDirectory.setPath(exe.binaryInfo().absolutePath()); + } + + setBinary(exe.binaryInfo()); + setArguments(exe.arguments()); + setCurrentDirectory(currentDirectory); + setSteamID(exe.steamAppID()); + setCustomOverwrite(customOverwrite); + setForcedLibraries(forcedLibraries); + + return *this; } -bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +ProcessRunner& ProcessRunner::setFromShortcut(const MOShortcut& shortcut) { - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) { + const auto currentInstance = InstanceManager::instance().currentInstance(); + + if (shortcut.hasInstance() && shortcut.instance() != currentInstance) { throw std::runtime_error( QString("Refusing to run executable from different instance %1:%2") .arg(shortcut.instance(),shortcut.executable()) @@ -405,12 +495,17 @@ bool ProcessRunner::runShortcut(const MOShortcut& shortcut) } const Executable& exe = m_core.executablesList()->get(shortcut.executable()); - return runExecutable(exe, false); + setFromExecutable(exe); + + return *this; } -HANDLE ProcessRunner::runExecutableOrExecutableFile( - const QString& executable, const QStringList &args, const QString &cwd, - const QString& profileOverride, const QString &forcedCustomOverwrite, +ProcessRunner& ProcessRunner::setFromFileOrExecutable( + const QString &executable, + const QStringList &args, + const QString &cwd, + const QString &profileOverride, + const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite) { const auto* profile = m_core.currentProfile(); @@ -418,37 +513,41 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile( throw MyException(QObject::tr("No profile set")); } - QString profileName = profileOverride; - if (profileName == "") { - profileName = profile->name(); - } + setProfileName(profileOverride); - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString steamAppID; - QString customOverwrite; - QList forcedLibraries; + //QFileInfo binary; + //QString arguments = args.join(" "); + //QString currentDirectory = cwd; + //QString steamAppID; + //QString customOverwrite; + //QList forcedLibraries; if (executable.contains('\\') || executable.contains('/')) { // file path - binary = QFileInfo(executable); + auto binary = QFileInfo(executable); + if (binary.isRelative()) { // relative path, should be relative to game directory binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); } - if (currentDirectory == "") { - currentDirectory = binary.absolutePath(); + setBinary(binary); + + if (cwd == "") { + setCurrentDirectory(binary.absolutePath()); + } else { + setCurrentDirectory(cwd); } try { const Executable& exe = m_core.executablesList()->getByBinary(binary); - steamAppID = exe.steamAppID(); - customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + + setSteamID(exe.steamAppID()); + setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString()); + if (profile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = profile->determineForcedLibraries(exe.title()); + setForcedLibraries(profile->determineForcedLibraries(exe.title())); } } catch (const std::runtime_error &) { // nop @@ -457,223 +556,244 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile( // only a file name, search executables list try { const Executable &exe = m_core.executablesList()->get(executable); - steamAppID = exe.steamAppID(); - customOverwrite = profile->setting("custom_overwrites", exe.title()).toString(); + + setSteamID(exe.steamAppID()); + setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString()); + if (profile->forcedLibrariesEnabled(exe.title())) { - forcedLibraries = profile->determineForcedLibraries(exe.title()); + setForcedLibraries(profile->determineForcedLibraries(exe.title())); } - if (arguments == "") { - arguments = exe.arguments(); + + if (args.isEmpty()) { + setArguments(exe.arguments()); + } else { + setArguments(args.join(" ")); } - binary = exe.binaryInfo(); - if (currentDirectory == "") { - currentDirectory = exe.workingDirectory(); + + setBinary(exe.binaryInfo()); + + if (cwd == "") { + setCurrentDirectory(exe.workingDirectory()); + } else { + setCurrentDirectory(cwd); } } catch (const std::runtime_error &) { log::warn("\"{}\" not set up as executable", executable); - binary = QFileInfo(executable); + setBinary(QFileInfo(executable)); } } - if (!forcedCustomOverwrite.isEmpty()) - customOverwrite = forcedCustomOverwrite; - - if (ignoreCustomOverwrite) - customOverwrite.clear(); + if (ignoreCustomOverwrite) { + setCustomOverwrite(""); + } else if (!forcedCustomOverwrite.isEmpty()) { + setCustomOverwrite(forcedCustomOverwrite); + } - return spawnAndWait( - binary, - arguments, - profileName, - currentDirectory, - steamAppID, - customOverwrite, - forcedLibraries); + return *this; } -HANDLE ProcessRunner::spawnAndWait( - const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - LPDWORD exitCode) +ProcessRunner::Results ProcessRunner::run() { - spawn::SpawnParameters sp; - sp.binary = binary; - sp.arguments = arguments; - sp.currentDirectory = currentDirectory; - sp.steamAppID = steamAppID; - sp.hooked = true; + if (!m_shellOpen.isEmpty()) { + auto r = shell::Open(m_shellOpen); + if (!r.success()) { + return Error; + } - if (!m_core.beforeRun(binary, profileName, customOverwrite, forcedLibraries)) { - return INVALID_HANDLE_VALUE; - } + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer) + m_handle = r.stealProcessHandle(); + } else { + if (m_profileName.isEmpty()) { + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); + } - HANDLE handle = spawn(sp).releaseHandle(); + m_profileName = profile->name(); + } - if (handle == INVALID_HANDLE_VALUE) { - // failed - return INVALID_HANDLE_VALUE; - } + if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) { + return Error; + } - waitForProcessCompletionWithLock(handle, exitCode); - return handle; -} + QWidget* parent = nullptr; + if (m_ui) { + parent = m_ui->qtWidget(); + } -void adjustForVirtualized( - const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) -{ - const QString modsPath = settings.paths().mods(); + if (!checkBinary(parent, m_sp)) { + return Error; + } - // Check if this a request with either an executable or a working directory - // under our mods folder then will start the process in a virtualized - // "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = sp.currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = sp.binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - QString adjustedCwd = cwdPath.mid(cwdOffset, -1); - cwdPath = game->dataDirectory().absolutePath(); - if (cwdOffset >= 0) - cwdPath += adjustedCwd; + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { + return Error; } - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - QString adjustedBin = binPath.mid(binOffset, -1); - binPath = game->dataDirectory().absolutePath(); - if (binOffset >= 0) - binPath += adjustedBin; + if (!checkEnvironment(parent, m_sp)) { + return Error; } - QString cmdline - = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), - QDir::toNativeSeparators(binPath), sp.arguments); + if (!checkBlacklist(parent, m_sp, settings)) { + return Error; + } - sp.binary = QFileInfo(QCoreApplication::applicationFilePath()); - sp.arguments = cmdline; - sp.currentDirectory.setPath(QCoreApplication::applicationDirPath()); + adjustForVirtualized(game, m_sp, settings); + + m_handle = startBinary(parent, m_sp); + if (m_handle == INVALID_HANDLE_VALUE) { + return Error; + } + } + + if (m_handle == INVALID_HANDLE_VALUE || m_lock == LockWidget::NoReason) { + return Running; + } else { + const auto r = waitForProcessCompletionWithLock( + m_handle, &m_exitCode, m_lock); + + if (r == Completed && m_refresh) { + m_core.afterRun(m_sp.binary, m_exitCode); + } + + return r; } } -SpawnedProcess ProcessRunner::spawn(spawn::SpawnParameters sp) +DWORD ProcessRunner::exitCode() { - QWidget* parent = nullptr; - if (m_ui) { - parent = m_ui->qtWidget(); - } + return m_exitCode; +} - if (!checkBinary(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } - const auto* game = m_core.managedGame(); - auto& settings = m_core.settings(); +bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) +{ + setFromFile(parent, targetInfo); + setWaitForCompletion(LockWidget::LockUI, true); - if (!checkSteam(parent, sp, game->gameDirectory(), sp.steamAppID, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } + const auto r = run(); + return (r != Error); +} - if (!checkEnvironment(parent, sp)) { - return {INVALID_HANDLE_VALUE, sp}; - } +bool ProcessRunner::runExecutableFile( + const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, const QString &steamAppID, + const QString &customOverwrite, + const QList &forcedLibraries, + bool refresh) +{ + setBinary(binary); + setArguments(arguments); + setCurrentDirectory(currentDirectory); + setSteamID(steamAppID); + setCustomOverwrite(customOverwrite); + setForcedLibraries(forcedLibraries); + setWaitForCompletion(LockWidget::LockUI, refresh); + + const auto r = run(); + return (r != Error); +} - if (!checkBlacklist(parent, sp, settings)) { - return {INVALID_HANDLE_VALUE, sp}; - } +bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) +{ + setFromExecutable(exe); + setWaitForCompletion(LockWidget::LockUI, refresh); + + const auto r = run(); + return (r != Error); +} - adjustForVirtualized(game, sp, settings); +bool ProcessRunner::runShortcut(const MOShortcut& shortcut) +{ + setFromShortcut(shortcut); + setWaitForCompletion(LockWidget::LockUI, false); - return {startBinary(parent, sp), sp}; + const auto r = run(); + return (r != Error); } -void ProcessRunner::withLock(std::function f) +HANDLE ProcessRunner::runExecutableOrExecutableFile( + const QString& executable, const QStringList &args, const QString &cwd, + const QString& profileOverride, const QString &forcedCustomOverwrite, + bool ignoreCustomOverwrite) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; + setFromFileOrExecutable( + executable, args, cwd, profileOverride, forcedCustomOverwrite, + ignoreCustomOverwrite); - if (m_ui != nullptr) { - uilock = m_ui->lock(); - } else { - // i.e. when running command line shortcuts there is no user interface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } + setWaitForCompletion(LockWidget::LockUI, true); - Guard g([&]() { - if (m_ui != nullptr) { - m_ui->unlock(); - } - }); + run(); + return m_handle; +} - f(uilock); +void ProcessRunner::withLock( + LockWidget::Reasons reason, std::function f) +{ + auto lock = std::make_unique( + m_ui ? m_ui->qtWidget() : nullptr, reason); + + f(*lock); } -bool ProcessRunner::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode) +ProcessRunner::Results ProcessRunner::waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) { if (!Settings::instance().interface().lockGUI()) { log::debug("not waiting for process because user has disabled locking"); - return true; + return ForceUnlocked; } - auto r = WaitResults::Error; - - withLock([&](auto* uilock) { - r = waitForProcess(handle, exitCode, uilock); - }); - - // completed/unlocked is fine - return (r != WaitResults::Error); + return waitForApplication(handle, exitCode, reason); } -bool ProcessRunner::waitForApplication(HANDLE handle, LPDWORD exitCode) +ProcessRunner::Results ProcessRunner::waitForApplication( + HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) { // don't check for lockGUI() setting; this _always_ locks the ui and waits // for completion // - // this is typically called only from OrganizerProxy, which allows plugins - // to wait on applications until they're finished + // this is typically called only from: + // 1) OrganizerProxy, which allows plugins to wait on applications until + // they're finished + // + // the check_fnis plugin for example will start FNIS, wait for it to + // complete, and then check the exit code; this has to work regardless of + // the locking setting; // - // the check_fnis plugin for example will start FNIS, wait for it to complete, - // and then check the exit code; this has to work regardless of the locking - // setting + // 2) waitForProcessCompletionWithLock() above, which has already checked the + // lock setting - auto r = WaitResults::Error; + auto r = Error; - withLock([&](auto* uilock) { - r = waitForProcess(handle, exitCode, uilock); + withLock(reason, [&](auto& lock) { + r = waitForProcess(handle, exitCode, lock); }); - // treat unlocked as an error since this should always wait for completion - return (r == WaitResults::Completed); + return r; } -bool ProcessRunner::waitForAllUSVFSProcessesWithLock() +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( + LockWidget::Reasons reason) { if (!Settings::instance().interface().lockGUI()) { log::debug("not waiting for usvfs processes because user has disabled locking"); - return true; + return ForceUnlocked; } - bool r = false; + auto r = Error; - withLock([&](auto* uilock) { - r = waitForAllUSVFSProcesses(uilock); + withLock(reason, [&](auto& lock) { + r = waitForAllUSVFSProcesses(lock); }); return r; } -bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcesses(LockWidget& lock) { for (;;) { const auto processes = getRunningUSVFSProcesses(); @@ -681,26 +801,15 @@ bool ProcessRunner::waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock) break; } - const auto r = waitForProcesses(processes, nullptr, uilock); + const auto r = waitForProcesses(processes, lock); - switch (r) - { - case WaitResults::Completed: - // this process is completed, check for others - break; - - case WaitResults::Cancelled: - // force unlocked - log::debug("waiting for process completion aborted by UI"); - return true; - - case WaitResults::Error: // fall-through - default: - log::debug("waiting for process completion not successful"); - return false; + if (r != Completed) { + // error, cancelled, or unlocked + return r; } + + // this process is completed, check for others } - log::debug("waiting for process completion successful"); - return true; + return Completed; } diff --git a/src/processrunner.h b/src/processrunner.h index 28f4da75..b7895903 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -2,10 +2,10 @@ #define PROCESSRUNNER_H #include "spawn.h" +#include "lockwidget.h" #include class OrganizerCore; -class ILockedWaitingForProcess; class IUserInterface; class Executable; class MOShortcut; @@ -35,9 +35,43 @@ private: class ProcessRunner { public: - ProcessRunner(OrganizerCore& core); + enum Results + { + Running = 1, + Completed, + Error, + Cancelled, + ForceUnlocked + }; + + using ForcedLibraries = QList; + + ProcessRunner(OrganizerCore& core, IUserInterface* ui); + + ProcessRunner& setBinary(const QFileInfo &binary); + ProcessRunner& setArguments(const QString& arguments); + ProcessRunner& setCurrentDirectory(const QDir& directory); + ProcessRunner& setSteamID(const QString& steamID); + ProcessRunner& setCustomOverwrite(const QString& customOverwrite); + ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); + ProcessRunner& setProfileName(const QString& profileName); + ProcessRunner& setWaitForCompletion(LockWidget::Reasons reason, bool refresh); + + ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); + ProcessRunner& setFromExecutable(const Executable& exe); + ProcessRunner& setFromShortcut(const MOShortcut& shortcut); + + ProcessRunner& setFromFileOrExecutable( + const QString &executable, + const QStringList &args, + const QString &cwd, + const QString &profile, + const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false); + + Results run(); + DWORD exitCode(); - void setUserInterface(IUserInterface* ui); bool runFile(QWidget* parent, const QFileInfo& targetInfo); @@ -53,17 +87,31 @@ public: bool runShortcut(const MOShortcut& shortcut); HANDLE runExecutableOrExecutableFile( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite = "", + const QString &executable, + const QStringList &args, + const QString &cwd, + const QString &profile, + const QString &forcedCustomOverwrite = "", bool ignoreCustomOverwrite = false); - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - bool waitForAllUSVFSProcessesWithLock(); + Results waitForApplication( + HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); + + Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); private: OrganizerCore& m_core; IUserInterface* m_ui; + spawn::SpawnParameters m_sp; + QString m_customOverwrite; + ForcedLibraries m_forcedLibraries; + QString m_profileName; + LockWidget::Reasons m_lock; + bool m_refresh; + QString m_shellOpen; + HANDLE m_handle; + DWORD m_exitCode; HANDLE spawnAndWait( const QFileInfo &binary, const QString &arguments, @@ -76,11 +124,13 @@ private: SpawnedProcess spawn(spawn::SpawnParameters sp); - void withLock(std::function f); + void withLock( + LockWidget::Reasons reason, std::function f); - bool waitForProcessCompletionWithLock(HANDLE handle, LPDWORD exitCode); + Results waitForProcessCompletionWithLock( + HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); - bool waitForAllUSVFSProcesses(ILockedWaitingForProcess* uilock); + Results waitForAllUSVFSProcesses(LockWidget& lock); }; #endif // PROCESSRUNNER_H -- cgit v1.3.1 From 0cea4833eb48400feb652e883c70d8a2907701c3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 01:47:08 -0400 Subject: explicit refresh parameter for setWaitForCompletion(), some parts of the ui will crash if things refresh unexpectedly removed runFile() fixed crash when unlocking if some widgets were destroyed in the meantime lock widget will now pick the active window and disable all top levels --- src/lockwidget.cpp | 73 +++++++++++++++++++++++++++--------------- src/lockwidget.h | 2 +- src/mainwindow.cpp | 6 +++- src/modinfodialogconflicts.cpp | 6 +++- src/processrunner.cpp | 25 +++++---------- src/processrunner.h | 13 +++++--- 6 files changed, 75 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 720cac36..ad9aefe3 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -59,17 +59,22 @@ LockWidget::Results LockWidget::result() const void LockWidget::createUi(Reasons reason) { - if (m_parent) { - m_overlay.reset(createTransparentWidget(m_parent)); + QWidget* overlayTarget = m_parent; + if (auto* w = qApp->activeWindow()) { + overlayTarget = w; + } + + if (overlayTarget) { + m_overlay.reset(createTransparentWidget(overlayTarget)); m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint); - m_overlay->setGeometry(m_parent->rect()); + m_overlay->setGeometry(overlayTarget->rect()); } else { m_overlay.reset(new QDialog); } auto* center = new QFrame; - if (m_parent) { + if (overlayTarget) { center->setFrameStyle(QFrame::StyledPanel); center->setLineWidth(1); center->setAutoFillBackground(true); @@ -86,7 +91,7 @@ void LockWidget::createUi(Reasons reason) auto* ly = new QVBoxLayout(center); - if (!m_parent) { + if (!overlayTarget) { ly->setContentsMargins(0, 0, 0, 0); } @@ -115,7 +120,7 @@ void LockWidget::createUi(Reasons reason) case OutputRequired: { message->setText(QObject::tr( - "The executable must run to completion because a its output is " + "The executable must run to completion because its output is " "required.")); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); @@ -149,7 +154,7 @@ void LockWidget::createUi(Reasons reason) grid->addWidget(createTransparentWidget(), 1, 2); grid->addWidget(center, 1, 1); - if (!m_parent) { + if (!overlayTarget) { grid->setContentsMargins(0, 0, 0, 0); } @@ -160,10 +165,10 @@ void LockWidget::createUi(Reasons reason) disableAll(); - if (m_parent) { + if (overlayTarget) { m_filter.reset(new Filter); - m_filter->resized = [=]{ m_overlay->setGeometry(m_parent->rect()); }; - m_parent->installEventFilter(m_filter.get()); + m_filter->resized = [=]{ m_overlay->setGeometry(overlayTarget->rect()); }; + overlayTarget->installEventFilter(m_filter.get()); } m_overlay->setFocusPolicy(Qt::TabFocus); @@ -184,32 +189,48 @@ void LockWidget::onCancel() unlock(); } +template +QList findChildrenImmediate(QWidget* parent) +{ + return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); +} + void LockWidget::disableAll() { - if (!m_parent) { - // nothing to disable without a main window - return; - } + const auto topLevels = QApplication::topLevelWidgets(); - if (auto* mw=dynamic_cast(m_parent)) { - disable(mw->centralWidget()); - disable(mw->menuBar()); - disable(mw->statusBar()); - } + for (auto* w : topLevels) { + if (auto* mw=dynamic_cast(w)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); - for (auto* tb : m_parent->findChildren()) { - disable(tb); - } + for (auto* tb : findChildrenImmediate(w)) { + disable(tb); + } + + for (auto* d : findChildrenImmediate(w)) { + disable(d); + } + } - for (auto* d : m_parent->findChildren()) { - disable(d); + if (auto* d=dynamic_cast(w)) { + // no central widget, just disable the children, except for the overlay + for (auto* child : findChildrenImmediate(d)) { + if (child != m_overlay.get()) { + disable(child); + } + } + } } } void LockWidget::enableAll() { - for (auto* w : m_disabled) { - w->setEnabled(true); + for (auto w : m_disabled) { + if (w) { + w->setEnabled(true); + } } m_disabled.clear(); diff --git a/src/lockwidget.h b/src/lockwidget.h index bfb0b30f..9c555ac9 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -55,7 +55,7 @@ private: QLabel* m_info; Results m_result; std::unique_ptr m_filter; - std::vector m_disabled; + std::vector> m_disabled; void createUi(Reasons reason); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7d3d20b3..63f6c680 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5422,7 +5422,11 @@ void MainWindow::openDataFile() const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); - m_OrganizerCore.processRunner().runFile(this, targetInfo); + + m_OrganizerCore.processRunner() + .setFromFile(this, targetInfo) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); } void MainWindow::openDataOriginExplorer_clicked() diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 758112cc..8aefd4c6 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -527,7 +527,11 @@ void ConflictsTab::openItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner().runFile(parentWidget(), item->fileName()); + core().processRunner() + .setFromFile(parentWidget(), item->fileName()) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + return true; }); } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 62c77efc..1d9da96b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -368,7 +368,7 @@ void SpawnedProcess::destroy() ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(false), + m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(NoRefresh), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { m_sp.hooked = true; @@ -417,10 +417,10 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - LockWidget::Reasons reason, bool refresh) + RefreshModes refresh, LockWidget::Reasons reason) { - m_lock = reason; m_refresh = refresh; + m_lock = reason; return *this; } @@ -655,7 +655,7 @@ ProcessRunner::Results ProcessRunner::run() const auto r = waitForProcessCompletionWithLock( m_handle, &m_exitCode, m_lock); - if (r == Completed && m_refresh) { + if (r == Completed && m_refresh == Refresh) { m_core.afterRun(m_sp.binary, m_exitCode); } @@ -669,15 +669,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runFile(QWidget* parent, const QFileInfo& targetInfo) -{ - setFromFile(parent, targetInfo); - setWaitForCompletion(LockWidget::LockUI, true); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, @@ -691,7 +682,7 @@ bool ProcessRunner::runExecutableFile( setSteamID(steamAppID); setCustomOverwrite(customOverwrite); setForcedLibraries(forcedLibraries); - setWaitForCompletion(LockWidget::LockUI, refresh); + setWaitForCompletion(refresh ? Refresh : NoRefresh); const auto r = run(); return (r != Error); @@ -700,7 +691,7 @@ bool ProcessRunner::runExecutableFile( bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) { setFromExecutable(exe); - setWaitForCompletion(LockWidget::LockUI, refresh); + setWaitForCompletion(refresh ? Refresh : NoRefresh); const auto r = run(); return (r != Error); @@ -709,7 +700,7 @@ bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) bool ProcessRunner::runShortcut(const MOShortcut& shortcut) { setFromShortcut(shortcut); - setWaitForCompletion(LockWidget::LockUI, false); + setWaitForCompletion(NoRefresh); const auto r = run(); return (r != Error); @@ -724,7 +715,7 @@ HANDLE ProcessRunner::runExecutableOrExecutableFile( executable, args, cwd, profileOverride, forcedCustomOverwrite, ignoreCustomOverwrite); - setWaitForCompletion(LockWidget::LockUI, true); + setWaitForCompletion(Refresh); run(); return m_handle; diff --git a/src/processrunner.h b/src/processrunner.h index b7895903..21840bfe 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -44,6 +44,12 @@ public: ForceUnlocked }; + enum RefreshModes + { + NoRefresh = 1, + Refresh + }; + using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); @@ -55,7 +61,8 @@ public: ProcessRunner& setCustomOverwrite(const QString& customOverwrite); ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); - ProcessRunner& setWaitForCompletion(LockWidget::Reasons reason, bool refresh); + ProcessRunner& setWaitForCompletion( + RefreshModes refresh, LockWidget::Reasons reason=LockWidget::LockUI); ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); @@ -73,8 +80,6 @@ public: DWORD exitCode(); - bool runFile(QWidget* parent, const QFileInfo& targetInfo); - bool runExecutableFile( const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID={}, @@ -108,7 +113,7 @@ private: ForcedLibraries m_forcedLibraries; QString m_profileName; LockWidget::Reasons m_lock; - bool m_refresh; + RefreshModes m_refresh; QString m_shellOpen; HANDLE m_handle; DWORD m_exitCode; -- cgit v1.3.1 From c8e101e19eed4417d42bef678cd60d3efb414eb7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 01:54:49 -0400 Subject: removed runExecutableFile() turns out on_startButton_clicked() had redundant code --- src/mainwindow.cpp | 43 ++++++++++--------------------------------- src/processrunner.cpp | 26 -------------------------- src/processrunner.h | 7 ------- 3 files changed, 10 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 63f6c680..c1789b0a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2312,41 +2312,18 @@ void MainWindow::installMod(QString fileName) void MainWindow::on_startButton_clicked() { - try { - const Executable* selectedExecutable = getSelectedExecutable(); - if (!selectedExecutable) { - return; - } - - ui->startButton->setEnabled(false); - - auto* profile = m_OrganizerCore.currentProfile(); - - const QString customOverwrite = profile->setting( - "custom_overwrites", selectedExecutable->title()).toString(); - - auto forcedLibraries = profile->determineForcedLibraries( - selectedExecutable->title()); - - if (!profile->forcedLibrariesEnabled(selectedExecutable->title())) { - forcedLibraries.clear(); - } - - m_OrganizerCore.processRunner().runExecutableFile( - selectedExecutable->binaryInfo(), - selectedExecutable->arguments(), - selectedExecutable->workingDirectory().length() != 0 ? - selectedExecutable->workingDirectory() : - selectedExecutable->binaryInfo().absolutePath(), - selectedExecutable->steamAppID(), - customOverwrite, - forcedLibraries); - } catch (...) { - ui->startButton->setEnabled(true); - throw; + const Executable* selectedExecutable = getSelectedExecutable(); + if (!selectedExecutable) { + return; } - ui->startButton->setEnabled(true); + ui->startButton->setEnabled(false); + Guard g([&]{ ui->startButton->setEnabled(true); }); + + m_OrganizerCore.processRunner() + .setFromExecutable(*selectedExecutable) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); } bool MainWindow::modifyExecutablesDialog(int selection) diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 1d9da96b..dba29bd2 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -515,13 +515,6 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( setProfileName(profileOverride); - //QFileInfo binary; - //QString arguments = args.join(" "); - //QString currentDirectory = cwd; - //QString steamAppID; - //QString customOverwrite; - //QList forcedLibraries; - if (executable.contains('\\') || executable.contains('/')) { // file path @@ -669,25 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries, - bool refresh) -{ - setBinary(binary); - setArguments(arguments); - setCurrentDirectory(currentDirectory); - setSteamID(steamAppID); - setCustomOverwrite(customOverwrite); - setForcedLibraries(forcedLibraries); - setWaitForCompletion(refresh ? Refresh : NoRefresh); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) { setFromExecutable(exe); diff --git a/src/processrunner.h b/src/processrunner.h index 21840bfe..2e9550e0 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,13 +80,6 @@ public: DWORD exitCode(); - bool runExecutableFile( - const QFileInfo &binary, const QString &arguments, - const QDir ¤tDirectory, const QString &steamAppID={}, - const QString &customOverwrite={}, - const QList &forcedLibraries={}, - bool refresh=true); - bool runExecutable(const Executable& exe, bool refresh=true); bool runShortcut(const MOShortcut& shortcut); -- cgit v1.3.1 From 2c3079c1dc2aaeb84cbe7e4a021f6b3f22c785a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:00:58 -0400 Subject: removed runExecutable() --- src/mainwindow.cpp | 8 ++++++-- src/processrunner.cpp | 9 --------- src/processrunner.h | 2 -- 3 files changed, 6 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c1789b0a..3d32aa16 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1536,8 +1536,12 @@ void MainWindow::startExeAction() } action->setEnabled(false); - m_OrganizerCore.processRunner().runExecutable(*itor); - action->setEnabled(true); + Guard g([&]{ action->setEnabled(true); }); + + m_OrganizerCore.processRunner() + .setFromExecutable(*itor) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); } void MainWindow::activateSelectedProfile() diff --git a/src/processrunner.cpp b/src/processrunner.cpp index dba29bd2..1c8c923b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -662,15 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runExecutable(const Executable& exe, bool refresh) -{ - setFromExecutable(exe); - setWaitForCompletion(refresh ? Refresh : NoRefresh); - - const auto r = run(); - return (r != Error); -} - bool ProcessRunner::runShortcut(const MOShortcut& shortcut) { setFromShortcut(shortcut); diff --git a/src/processrunner.h b/src/processrunner.h index 2e9550e0..d3437412 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,8 +80,6 @@ public: DWORD exitCode(); - bool runExecutable(const Executable& exe, bool refresh=true); - bool runShortcut(const MOShortcut& shortcut); HANDLE runExecutableOrExecutableFile( -- cgit v1.3.1 From b855754c9708c825e6bc1e995cb0262c065c5d20 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:18:21 -0400 Subject: removed runShortcut() changed lock widget text when running without a ui --- src/lockwidget.cpp | 16 ++++++++++++---- src/main.cpp | 6 +++++- src/organizercore.cpp | 8 ++++++-- src/processrunner.cpp | 9 --------- src/processrunner.h | 2 -- 5 files changed, 23 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index ad9aefe3..cc66112e 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -107,8 +107,16 @@ void LockWidget::createUi(Reasons reason) { case LockUI: { - message->setText(QObject::tr( - "Mod Organizer is locked while the executable is running.")); + QString s; + + if (!m_parent) { + s = QObject::tr("Mod Organizer is currently running an application."); + } else { + s = QObject::tr( + "Mod Organizer is locked while the application is running."); + } + + message->setText(s); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); @@ -120,7 +128,7 @@ void LockWidget::createUi(Reasons reason) case OutputRequired: { message->setText(QObject::tr( - "The executable must run to completion because its output is " + "The application must run to completion because its output is " "required.")); auto* unlockButton = new QPushButton(QObject::tr("Unlock")); @@ -133,7 +141,7 @@ void LockWidget::createUi(Reasons reason) case PreventExit: { message->setText(QObject::tr( - "Mod Organizer is waiting on processes to finish before exiting.")); + "Mod Organizer is waiting on application to close before exiting.")); auto* exit = new QPushButton(QObject::tr("Exit Now")); QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); }); diff --git a/src/main.cpp b/src/main.cpp index 5ed7da5d..9decb94e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -632,7 +632,11 @@ int runApplication(MOApplication &application, SingleInstance &instance, if (MOShortcut shortcut{ arguments.at(1) }) { if (shortcut.hasExecutable()) { try { - organizer.processRunner().runShortcut(shortcut); + organizer.processRunner() + .setFromShortcut(shortcut) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + return 0; } catch (const std::exception &e) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 89e8bd9e..57e24f3b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -354,8 +354,12 @@ void OrganizerCore::downloadRequestedNXM(const QString &url) void OrganizerCore::externalMessage(const QString &message) { if (MOShortcut moshortcut{ message } ) { - if(moshortcut.hasExecutable()) - processRunner().runShortcut(moshortcut); + if(moshortcut.hasExecutable()) { + processRunner() + .setFromShortcut(moshortcut) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); + } } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 1c8c923b..6ca05147 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -662,15 +662,6 @@ DWORD ProcessRunner::exitCode() } -bool ProcessRunner::runShortcut(const MOShortcut& shortcut) -{ - setFromShortcut(shortcut); - setWaitForCompletion(NoRefresh); - - const auto r = run(); - return (r != Error); -} - HANDLE ProcessRunner::runExecutableOrExecutableFile( const QString& executable, const QStringList &args, const QString &cwd, const QString& profileOverride, const QString &forcedCustomOverwrite, diff --git a/src/processrunner.h b/src/processrunner.h index d3437412..4860be7c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -80,8 +80,6 @@ public: DWORD exitCode(); - bool runShortcut(const MOShortcut& shortcut); - HANDLE runExecutableOrExecutableFile( const QString &executable, const QStringList &args, -- cgit v1.3.1 From a88f9dd9a703c23263b5561d3cae126e90e36f3f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 02:52:14 -0400 Subject: removed runExecutableOrExecutableFile() log the actual spawning and requests to start as well as wait from plugins raise the lock widget when it's a dialog --- src/lockwidget.cpp | 5 +++++ src/main.cpp | 16 +++++++++++----- src/organizerproxy.cpp | 32 ++++++++++++++++++++++++++------ src/processrunner.cpp | 14 ++------------ src/processrunner.h | 19 +++++-------------- src/spawn.cpp | 44 ++++++++++++++++++++++++++++++++++---------- 6 files changed, 83 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index cc66112e..7b6e8430 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -183,6 +183,11 @@ void LockWidget::createUi(Reasons reason) m_overlay->setFocus(); m_overlay->show(); m_overlay->setEnabled(true); + + if (!overlayTarget) { + m_overlay->raise(); + m_overlay->activateWindow(); + } } void LockWidget::onForceUnlock() diff --git a/src/main.cpp b/src/main.cpp index 9decb94e..2b9a2f4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -653,15 +653,21 @@ int runApplication(MOApplication &application, SingleInstance &instance, else { QString exeName = arguments.at(1); log::debug("starting {} from command line", exeName); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.processRunner().runExecutableOrExecutableFile( - exeName, arguments, QString(), QString()); + + try + { + // pass the remaining parameters to the binary + organizer.processRunner() + .setFromFileOrExecutable(exeName, arguments) + .setWaitForCompletion(ProcessRunner::NoRefresh) + .run(); return 0; } - catch (const std::exception &e) { + catch (const std::exception &e) + { reportError( QObject::tr("failed to start application: %1").arg(e.what())); return 1; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 3ee35fe2..9de5b4ba 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -108,17 +108,37 @@ QString OrganizerProxy::pluginDataPath() const } HANDLE OrganizerProxy::startApplication( - const QString &executable, const QStringList &args, const QString &cwd, - const QString &profile, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) + const QString& exe, const QStringList& args, const QString &cwd, + const QString& profile, const QString &overwrite, bool ignoreOverwrite) { - return m_Proxied->processRunner().runExecutableOrExecutableFile( - executable, args, cwd, profile, - forcedCustomOverwrite, ignoreCustomOverwrite); + log::debug( + "a plugin has requested to start an application:\n" + " . executable: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . profile: '{}'\n" + " . overwrite: '{}'\n" + " . ignore overwrite: {}", + exe, args.join(" "), cwd, profile, overwrite, ignoreOverwrite); + + auto runner = m_Proxied->processRunner(); + + runner + .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); + + return runner.processHandle(); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { + const auto pid = ::GetProcessId(handle); + + log::debug( + "a plugin wants to wait for an application to complete, pid {}{}", + pid, (pid == 0 ? "unknown (probably already completed)" : "")); + const auto r = m_Proxied->processRunner().waitForApplication( handle, exitCode, LockWidget::OutputRequired); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 6ca05147..e6f916c5 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -661,22 +661,12 @@ DWORD ProcessRunner::exitCode() return m_exitCode; } - -HANDLE ProcessRunner::runExecutableOrExecutableFile( - const QString& executable, const QStringList &args, const QString &cwd, - const QString& profileOverride, const QString &forcedCustomOverwrite, - bool ignoreCustomOverwrite) +HANDLE ProcessRunner::processHandle() { - setFromFileOrExecutable( - executable, args, cwd, profileOverride, forcedCustomOverwrite, - ignoreCustomOverwrite); - - setWaitForCompletion(Refresh); - - run(); return m_handle; } + void ProcessRunner::withLock( LockWidget::Reasons reason, std::function f) { diff --git a/src/processrunner.h b/src/processrunner.h index 4860be7c..bdd0b260 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -71,23 +71,14 @@ public: ProcessRunner& setFromFileOrExecutable( const QString &executable, const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); + const QString &cwd={}, + const QString &profile={}, + const QString &forcedCustomOverwrite={}, + bool ignoreCustomOverwrite=false); Results run(); DWORD exitCode(); - - - HANDLE runExecutableOrExecutableFile( - const QString &executable, - const QStringList &args, - const QString &cwd, - const QString &profile, - const QString &forcedCustomOverwrite = "", - bool ignoreCustomOverwrite = false); - + HANDLE processHandle(); Results waitForApplication( HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); diff --git a/src/spawn.cpp b/src/spawn.cpp index c8d7c76a..b331db01 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -442,6 +442,25 @@ QMessageBox::StandardButton confirmBlacklisted( namespace spawn { +void logSpawning(const SpawnParameters& sp, const QString& realCmd) +{ + log::debug( + "spawning binary:\n" + " . exe: '{}'\n" + " . args: '{}'\n" + " . cwd: '{}'\n" + " . steam id: '{}'\n" + " . hooked: {}\n" + " . stdout: {}\n" + " . stderr: {}\n" + " . real cmd: '{}'", + sp.binary.absoluteFilePath(), sp.arguments, + sp.currentDirectory.absolutePath(), sp.steamAppID, sp.hooked, + (sp.stdOut == INVALID_HANDLE_VALUE ? "no" : "yes"), + (sp.stdErr == INVALID_HANDLE_VALUE ? "no" : "yes"), + realCmd); +} + DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) { BOOL inheritHandles = FALSE; @@ -462,30 +481,35 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) si.dwFlags |= STARTF_USESTDHANDLES; } - const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()).toStdWString(); - const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()).toStdWString(); + const auto bin = QDir::toNativeSeparators(sp.binary.absoluteFilePath()); + const auto cwd = QDir::toNativeSeparators(sp.currentDirectory.absolutePath()); - std::wstring commandLine = L"\"" + bin + L"\""; - if (sp.arguments[0] != L'\0') { - commandLine += L" " + sp.arguments.toStdWString(); + QString commandLine = "\"" + bin + "\""; + if (!sp.arguments.isEmpty()) { + commandLine += " " + sp.arguments; } - QString moPath = QCoreApplication::applicationDirPath(); + const QString moPath = QCoreApplication::applicationDirPath(); const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); PROCESS_INFORMATION pi; BOOL success = FALSE; + logSpawning(sp, commandLine); + + const auto wcommandLine = commandLine.toStdWString(); + const auto wcwd = cwd.toStdWString(); + if (sp.hooked) { success = ::CreateProcessHooked( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); + wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( - nullptr, const_cast(commandLine.c_str()), nullptr, nullptr, + nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - cwd.c_str(), &si, &pi); + wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); -- cgit v1.3.1 From d72e94a92f31bcc720d12ed0cb2cc75b590e6770 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 03:54:18 -0400 Subject: added attachToProcess(), made waitForApplication() private changed OrganizerProxy::startApplication() so it _doesn't_ wait for completion, which is its original behaviour --- src/organizerproxy.cpp | 13 ++++++++++--- src/processrunner.cpp | 37 +++++++++++++++++++++++++++---------- src/processrunner.h | 19 ++++++------------- 3 files changed, 43 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 9de5b4ba..0b6f8df1 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -123,9 +123,9 @@ HANDLE OrganizerProxy::startApplication( auto runner = m_Proxied->processRunner(); + // don't wait for completion runner .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) - .setWaitForCompletion(ProcessRunner::Refresh) .run(); return runner.processHandle(); @@ -139,8 +139,15 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const "a plugin wants to wait for an application to complete, pid {}{}", pid, (pid == 0 ? "unknown (probably already completed)" : "")); - const auto r = m_Proxied->processRunner().waitForApplication( - handle, exitCode, LockWidget::OutputRequired); + auto runner = m_Proxied->processRunner(); + + const auto r = runner + .setWaitForCompletion(ProcessRunner::NoRefresh, LockWidget::OutputRequired) + .attachToProcess(handle); + + if (exitCode) { + *exitCode = runner.exitCode(); + } switch (r) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index e6f916c5..b732204c 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -593,9 +593,15 @@ ProcessRunner::Results ProcessRunner::run() return Error; } - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer) m_handle = r.stealProcessHandle(); + + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer); in this + // case it's impossible to determine the status, so just say it's still + // running + if (m_handle == INVALID_HANDLE_VALUE) { + return Running; + } } else { if (m_profileName.isEmpty()) { const auto* profile = m_core.currentProfile(); @@ -642,18 +648,29 @@ ProcessRunner::Results ProcessRunner::run() } } - if (m_handle == INVALID_HANDLE_VALUE || m_lock == LockWidget::NoReason) { + return postRun(); +} + +ProcessRunner::Results ProcessRunner::postRun() +{ + if (m_lock == LockWidget::NoReason) { return Running; - } else { - const auto r = waitForProcessCompletionWithLock( - m_handle, &m_exitCode, m_lock); + } - if (r == Completed && m_refresh == Refresh) { - m_core.afterRun(m_sp.binary, m_exitCode); - } + const auto r = waitForProcessCompletionWithLock( + m_handle, &m_exitCode, m_lock); - return r; + if (r == Completed && m_refresh == Refresh) { + m_core.afterRun(m_sp.binary, m_exitCode); } + + return r; +} + +ProcessRunner::Results ProcessRunner::attachToProcess(HANDLE h) +{ + m_handle = h; + return postRun(); } DWORD ProcessRunner::exitCode() diff --git a/src/processrunner.h b/src/processrunner.h index bdd0b260..62ec7a9e 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -77,12 +77,11 @@ public: bool ignoreCustomOverwrite=false); Results run(); + Results attachToProcess(HANDLE h); + DWORD exitCode(); HANDLE processHandle(); - Results waitForApplication( - HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); - Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); private: @@ -98,16 +97,7 @@ private: HANDLE m_handle; DWORD m_exitCode; - HANDLE spawnAndWait( - const QFileInfo &binary, const QString &arguments, - const QString &profileName, - const QDir ¤tDirectory, - const QString &steamAppID, - const QString &customOverwrite, - const QList &forcedLibraries={}, - LPDWORD exitCode = nullptr); - - SpawnedProcess spawn(spawn::SpawnParameters sp); + Results postRun(); void withLock( LockWidget::Reasons reason, std::function f); @@ -115,6 +105,9 @@ private: Results waitForProcessCompletionWithLock( HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); + Results waitForApplication( + HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); + Results waitForAllUSVFSProcesses(LockWidget& lock); }; -- cgit v1.3.1 From 9ff8a471aaf9a696610a055c091de39c41f985b7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 04:03:16 -0400 Subject: added waitForAllUSVFSProcesses() wrapper in OrganizerCore, the fact that it's in ProcessRunner is a bit of a hack --- src/mainwindow.cpp | 4 +--- src/organizercore.cpp | 6 ++++++ src/organizercore.h | 3 +++ 3 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3d32aa16..d304f8b7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1310,9 +1310,7 @@ bool MainWindow::canExit() } } - const auto r = m_OrganizerCore.processRunner() - .waitForAllUSVFSProcessesWithLock(LockWidget::PreventExit); - + const auto r = m_OrganizerCore.waitForAllUSVFSProcesses(); if (r == ProcessRunner::Cancelled) { return false; } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 57e24f3b..943750ed 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1796,6 +1796,12 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) m_FinishedRun(binary.absoluteFilePath(), exitCode); } +ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses( + LockWidget::Reasons reason) +{ + return processRunner().waitForAllUSVFSProcessesWithLock(reason); +} + std::vector OrganizerCore::fileMapping(const QString &profileName, const QString &customOverwrite) { diff --git a/src/organizercore.h b/src/organizercore.h index d4882b92..0ba52284 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -143,6 +143,9 @@ public: void afterRun(const QFileInfo& binary, DWORD exitCode); + ProcessRunner::Results waitForAllUSVFSProcesses( + LockWidget::Reasons reason=LockWidget::PreventExit); + void refreshESPList(bool force = false); void refreshBSAList(); -- cgit v1.3.1 From 7ebd4debeef2cfdf268679e7b680021d3dc20687 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:19:14 -0400 Subject: added a ForceWait flag to bypass disabled locking merged a bunch of unnecessary functions in ProcessRunner --- src/main.cpp | 4 +- src/mainwindow.cpp | 2 +- src/modinfodialogconflicts.cpp | 2 +- src/organizercore.cpp | 2 +- src/organizerproxy.cpp | 6 +- src/processrunner.cpp | 155 ++++++++++++++++++++--------------------- src/processrunner.h | 43 ++++++------ 7 files changed, 107 insertions(+), 107 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 2b9a2f4d..02347ee3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -634,7 +634,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, try { organizer.processRunner() .setFromShortcut(shortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; @@ -662,7 +662,7 @@ int runApplication(MOApplication &application, SingleInstance &instance, // pass the remaining parameters to the binary organizer.processRunner() .setFromFileOrExecutable(exeName, arguments) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return 0; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d304f8b7..c2d91bcc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5404,7 +5404,7 @@ void MainWindow::openDataFile() m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 8aefd4c6..d37f068c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -529,7 +529,7 @@ void ConflictsTab::openItems(QTreeView* tree) for_each_in_selection(tree, [&](const ConflictItem* item) { core().processRunner() .setFromFile(parentWidget(), item->fileName()) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); return true; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 943750ed..96ae84a8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -357,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion(ProcessRunner::NoRefresh) + .setWaitForCompletion() .run(); } } diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 0b6f8df1..420e2d82 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -128,7 +128,9 @@ HANDLE OrganizerProxy::startApplication( .setFromFileOrExecutable(exe, args, cwd, profile, overwrite, ignoreOverwrite) .run(); - return runner.processHandle(); + // the plugin is in charge of closing the handle, unless waitForApplication() + // is called on it + return runner.stealProcessHandle().release(); } bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const @@ -142,7 +144,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const auto runner = m_Proxied->processRunner(); const auto r = runner - .setWaitForCompletion(ProcessRunner::NoRefresh, LockWidget::OutputRequired) + .setWaitForCompletion(ProcessRunner::ForceWait, LockWidget::OutputRequired) .attachToProcess(handle); if (exitCode) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index b732204c..9cc2ce52 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -368,8 +368,8 @@ void SpawnedProcess::destroy() ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lock(LockWidget::NoReason), m_refresh(NoRefresh), - m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) + m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), + m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { m_sp.hooked = true; } @@ -417,10 +417,10 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason) + WaitFlags flags, LockWidget::Reasons reason) { - m_refresh = refresh; - m_lock = reason; + m_waitFlags = flags; + m_lockReason = reason; return *this; } @@ -593,13 +593,13 @@ ProcessRunner::Results ProcessRunner::run() return Error; } - m_handle = r.stealProcessHandle(); + m_handle.reset(r.stealProcessHandle()); // not all files will return a valid handle even if opening them was // successful, such as inproc handlers (like the photo viewer); in this // case it's impossible to determine the status, so just say it's still // running - if (m_handle == INVALID_HANDLE_VALUE) { + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Running; } } else { @@ -642,8 +642,8 @@ ProcessRunner::Results ProcessRunner::run() adjustForVirtualized(game, m_sp, settings); - m_handle = startBinary(parent, m_sp); - if (m_handle == INVALID_HANDLE_VALUE) { + m_handle.reset(startBinary(parent, m_sp)); + if (m_handle.get() == INVALID_HANDLE_VALUE) { return Error; } } @@ -653,14 +653,46 @@ ProcessRunner::Results ProcessRunner::run() ProcessRunner::Results ProcessRunner::postRun() { - if (m_lock == LockWidget::NoReason) { - return Running; + const bool mustWait = (m_waitFlags & ForceWait); + + if (mustWait && m_lockReason == LockWidget::NoReason) { + // never lock the ui without an escape hatch for the user + log::debug( + "the ForceWait flag is set but the lock reason wasn't, " + "defaulting to LockUI"); + + m_lockReason = LockWidget::LockUI; + } + + if (mustWait) { + if (!Settings::instance().interface().lockGUI()) { + // at least tell the user what's going on + log::debug( + "locking is disabled, but the output of the application is required; " + "overriding this setting and locking the ui"); + } + } else { + // no force wait + + if (m_lockReason == LockWidget::NoReason) { + // no locking requested + return Running; + } + + if (!Settings::instance().interface().lockGUI()) { + // disabling locking is like clicking on unlock immediately + log::debug("not waiting for process because locking is disabled"); + return ForceUnlocked; + } } - const auto r = waitForProcessCompletionWithLock( - m_handle, &m_exitCode, m_lock); + auto r = Error; + + withLock([&](auto& lock) { + r = waitForProcess(m_handle.get(), &m_exitCode, lock); + }); - if (r == Completed && m_refresh == Refresh) { + if (r == Completed && (m_waitFlags & Refresh)) { m_core.afterRun(m_sp.binary, m_exitCode); } @@ -669,101 +701,64 @@ ProcessRunner::Results ProcessRunner::postRun() ProcessRunner::Results ProcessRunner::attachToProcess(HANDLE h) { - m_handle = h; + m_handle.reset(h); return postRun(); } -DWORD ProcessRunner::exitCode() +DWORD ProcessRunner::exitCode() const { return m_exitCode; } -HANDLE ProcessRunner::processHandle() +HANDLE ProcessRunner::getProcessHandle() const { - return m_handle; + return m_handle.get(); } - -void ProcessRunner::withLock( - LockWidget::Reasons reason, std::function f) +env::HandlePtr ProcessRunner::stealProcessHandle() { - auto lock = std::make_unique( - m_ui ? m_ui->qtWidget() : nullptr, reason); - - f(*lock); + return std::move(m_handle); } -ProcessRunner::Results ProcessRunner::waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) +ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( + LockWidget::Reasons reason) { + m_lockReason = reason; + if (!Settings::instance().interface().lockGUI()) { - log::debug("not waiting for process because user has disabled locking"); + // disabling locking is like clicking on unlock immediately return ForceUnlocked; } - return waitForApplication(handle, exitCode, reason); -} - -ProcessRunner::Results ProcessRunner::waitForApplication( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason) -{ - // don't check for lockGUI() setting; this _always_ locks the ui and waits - // for completion - // - // this is typically called only from: - // 1) OrganizerProxy, which allows plugins to wait on applications until - // they're finished - // - // the check_fnis plugin for example will start FNIS, wait for it to - // complete, and then check the exit code; this has to work regardless of - // the locking setting; - // - // 2) waitForProcessCompletionWithLock() above, which has already checked the - // lock setting - auto r = Error; - withLock(reason, [&](auto& lock) { - r = waitForProcess(handle, exitCode, lock); - }); + withLock([&](auto& lock) { + for (;;) { + const auto processes = getRunningUSVFSProcesses(); + if (processes.empty()) { + break; + } - return r; -} + r = waitForProcesses(processes, lock); -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( - LockWidget::Reasons reason) -{ - if (!Settings::instance().interface().lockGUI()) { - log::debug("not waiting for usvfs processes because user has disabled locking"); - return ForceUnlocked; - } + if (r != Completed) { + // error, cancelled, or unlocked + return; + } - auto r = Error; + // this process is completed, check for others + } - withLock(reason, [&](auto& lock) { - r = waitForAllUSVFSProcesses(lock); + r = Completed; }); return r; } -ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcesses(LockWidget& lock) +void ProcessRunner::withLock(std::function f) { - for (;;) { - const auto processes = getRunningUSVFSProcesses(); - if (processes.empty()) { - break; - } - - const auto r = waitForProcesses(processes, lock); - - if (r != Completed) { - // error, cancelled, or unlocked - return r; - } - - // this process is completed, check for others - } + auto lk = std::make_unique( + m_ui ? m_ui->qtWidget() : nullptr, m_lockReason); - return Completed; + f(*lk); } diff --git a/src/processrunner.h b/src/processrunner.h index 62ec7a9e..41c0f12c 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -3,6 +3,7 @@ #include "spawn.h" #include "lockwidget.h" +#include "envmodule.h" #include class OrganizerCore; @@ -44,16 +45,25 @@ public: ForceUnlocked }; - enum RefreshModes + enum WaitFlag { - NoRefresh = 1, - Refresh + NoFlags = 0x00, + Refresh = 0x01, + ForceWait = 0x02 }; + using WaitFlags = QFlags; + using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); + // move only + ProcessRunner(ProcessRunner&&) = default; + ProcessRunner& operator=(const ProcessRunner&) = delete; + ProcessRunner(const ProcessRunner&) = delete; + ProcessRunner& operator=(ProcessRunner&&) = delete; + ProcessRunner& setBinary(const QFileInfo &binary); ProcessRunner& setArguments(const QString& arguments); ProcessRunner& setCurrentDirectory(const QDir& directory); @@ -62,7 +72,7 @@ public: ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( - RefreshModes refresh, LockWidget::Reasons reason=LockWidget::LockUI); + WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); @@ -79,8 +89,9 @@ public: Results run(); Results attachToProcess(HANDLE h); - DWORD exitCode(); - HANDLE processHandle(); + DWORD exitCode() const; + HANDLE getProcessHandle() const; + env::HandlePtr stealProcessHandle(); Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); @@ -91,24 +102,16 @@ private: QString m_customOverwrite; ForcedLibraries m_forcedLibraries; QString m_profileName; - LockWidget::Reasons m_lock; - RefreshModes m_refresh; + LockWidget::Reasons m_lockReason; + WaitFlags m_waitFlags; QString m_shellOpen; - HANDLE m_handle; + env::HandlePtr m_handle; DWORD m_exitCode; Results postRun(); - - void withLock( - LockWidget::Reasons reason, std::function f); - - Results waitForProcessCompletionWithLock( - HANDLE handle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForApplication( - HANDLE processHandle, LPDWORD exitCode, LockWidget::Reasons reason); - - Results waitForAllUSVFSProcesses(LockWidget& lock); + void withLock(std::function f); }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); + #endif // PROCESSRUNNER_H -- cgit v1.3.1 From 7db5f938841933fb4846441448dafefc414ed5e7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:33:53 -0400 Subject: removed unused SpawnedProcess lock widget now interprets closing the dialog as a forced unlock --- src/lockwidget.cpp | 1 + src/lockwidget.h | 5 +++++ src/processrunner.cpp | 47 ----------------------------------------------- src/processrunner.h | 25 +++---------------------- 4 files changed, 9 insertions(+), 69 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 7b6e8430..bbffd390 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -176,6 +176,7 @@ void LockWidget::createUi(Reasons reason) if (overlayTarget) { m_filter.reset(new Filter); m_filter->resized = [=]{ m_overlay->setGeometry(overlayTarget->rect()); }; + m_filter->closed = [=]{ onForceUnlock(); }; overlayTarget->installEventFilter(m_filter.get()); } diff --git a/src/lockwidget.h b/src/lockwidget.h index 9c555ac9..18ff76dd 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -35,6 +35,7 @@ private: { public: std::function resized; + std::function closed; protected: bool eventFilter(QObject* o, QEvent* e) override @@ -43,6 +44,10 @@ private: if (resized) { resized(); } + } else if (e->type() == QEvent::Close) { + if (closed) { + closed(); + } } return QObject::eventFilter(o, e); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 9cc2ce52..58b2a771 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -320,53 +320,6 @@ ProcessRunner::Results waitForProcess( } - -SpawnedProcess::SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp) - : m_handle(handle), m_parameters(std::move(sp)) -{ -} - -SpawnedProcess::SpawnedProcess(SpawnedProcess&& other) - : m_handle(other.m_handle), m_parameters(std::move(other.m_parameters)) -{ - other.m_handle = INVALID_HANDLE_VALUE; -} - -SpawnedProcess& SpawnedProcess::operator=(SpawnedProcess&& other) -{ - if (this != &other) { - destroy(); - - m_handle = other.m_handle; - other.m_handle = INVALID_HANDLE_VALUE; - - m_parameters = std::move(other.m_parameters); - } - - return *this; -} - -SpawnedProcess::~SpawnedProcess() -{ - destroy(); -} - -HANDLE SpawnedProcess::releaseHandle() -{ - const auto h = m_handle; - m_handle = INVALID_HANDLE_VALUE; - return h; -} - -void SpawnedProcess::destroy() -{ - if (m_handle != INVALID_HANDLE_VALUE) { - ::CloseHandle(m_handle); - m_handle = INVALID_HANDLE_VALUE; - } -} - - ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) diff --git a/src/processrunner.h b/src/processrunner.h index 41c0f12c..0fb3f59a 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -11,28 +11,9 @@ class IUserInterface; class Executable; class MOShortcut; -class SpawnedProcess -{ -public: - SpawnedProcess(HANDLE handle, spawn::SpawnParameters sp); - - SpawnedProcess(const SpawnedProcess&) = delete; - SpawnedProcess& operator=(const SpawnedProcess&) = delete; - SpawnedProcess(SpawnedProcess&& other); - SpawnedProcess& operator=(SpawnedProcess&& other); - ~SpawnedProcess(); - - HANDLE releaseHandle(); - void wait(); - -private: - HANDLE m_handle; - spawn::SpawnParameters m_parameters; - - void destroy(); -}; - - +// handles spawning a process and waiting for it, including setting up the lock +// widget if required +// class ProcessRunner { public: -- cgit v1.3.1 From 719a2f20b4bb6b722097b63df8468d073d67e65f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:40:46 -0400 Subject: refresh ui after running an exe from 1) an external message, and 2) the data tab --- src/mainwindow.cpp | 2 +- src/organizercore.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c2d91bcc..631a31d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5404,7 +5404,7 @@ void MainWindow::openDataFile() m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) - .setWaitForCompletion() + .setWaitForCompletion(ProcessRunner::Refresh) .run(); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 96ae84a8..3c535868 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -357,7 +357,7 @@ void OrganizerCore::externalMessage(const QString &message) if(moshortcut.hasExecutable()) { processRunner() .setFromShortcut(moshortcut) - .setWaitForCompletion() + .setWaitForCompletion(ProcessRunner::Refresh) .run(); } } -- cgit v1.3.1 From 72dd230cdc60e74446caceb5cfb4c6d32e4f6f68 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 05:58:41 -0400 Subject: removed unused files fixed handle leak when starting steam --- src/CMakeLists.txt | 17 +----- src/ilockedwaitingforprocess.h | 14 ----- src/iuserinterface.h | 1 - src/lockeddialog.cpp | 71 ------------------------ src/lockeddialog.h | 57 -------------------- src/lockeddialog.ui | 98 --------------------------------- src/lockeddialogbase.cpp | 78 --------------------------- src/lockeddialogbase.h | 64 ---------------------- src/mainwindow.cpp | 1 - src/organizercore.cpp | 1 - src/spawn.cpp | 3 +- src/spawn.h | 2 - src/waitingonclosedialog.cpp | 71 ------------------------ src/waitingonclosedialog.h | 56 ------------------- src/waitingonclosedialog.ui | 119 ----------------------------------------- 15 files changed, 2 insertions(+), 651 deletions(-) delete mode 100644 src/ilockedwaitingforprocess.h delete mode 100644 src/lockeddialog.cpp delete mode 100644 src/lockeddialog.h delete mode 100644 src/lockeddialog.ui delete mode 100644 src/lockeddialogbase.cpp delete mode 100644 src/lockeddialogbase.h delete mode 100644 src/waitingonclosedialog.cpp delete mode 100644 src/waitingonclosedialog.h delete mode 100644 src/waitingonclosedialog.ui (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 168b79dc..06f2cd1e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,9 +82,6 @@ SET(organizer_SRCS main.cpp loghighlighter.cpp loglist.cpp - lockeddialogbase.cpp - lockeddialog.cpp - waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp filedialogmemory.cpp @@ -206,9 +203,6 @@ SET(organizer_HDRS mainwindow.h loghighlighter.h loglist.h - lockeddialogbase.h - lockeddialog.h - waitingonclosedialog.h loadmechanism.h installationmanager.h filedialogmemory.h @@ -246,7 +240,6 @@ SET(organizer_HDRS viewmarkingscrollbar.h plugincontainer.h organizercore.h - ilockedwaitingforprocess.h iuserinterface.h instancemanager.h usvfsconnector.h @@ -293,8 +286,6 @@ SET(organizer_UIS modinfodialog.ui messagedialog.ui mainwindow.ui - lockeddialog.ui - waitingonclosedialog.ui editexecutablesdialog.ui credentialsdialog.ui categoriesdialog.ui @@ -397,12 +388,6 @@ set(executables editexecutablesdialog ) -set(locking - ilockedwaitingforprocess - lockeddialog - lockeddialogbase -) - set(modinfo modinfo modinfobackup @@ -500,7 +485,7 @@ set(widgets ) set(src_filters - application core browser dialogs downloads env executables locking modinfo + application core browser dialogs downloads env executables modinfo modinfo\\dialog modlist plugins previews profiles settings settingsdialog utilities widgets ) diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h deleted file mode 100644 index 4d1e786f..00000000 --- a/src/ilockedwaitingforprocess.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef ILOCKEDWAITINGFORPROCESS_H -#define ILOCKEDWAITINGFORPROCESS_H - -class QString; - -class ILockedWaitingForProcess -{ -public: - virtual bool unlockForced() const = 0; - virtual void setProcessName(QString const &) = 0; - virtual void setProcessInformation(DWORD pid, const QString& name) = 0; -}; - -#endif // ILOCKEDWAITINGFORPROCESS_H diff --git a/src/iuserinterface.h b/src/iuserinterface.h index e5755f03..99caceb1 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -3,7 +3,6 @@ #include "modinfodialogfwd.h" -#include "ilockedwaitingforprocess.h" #include "lockwidget.h" #include #include diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp deleted file mode 100644 index 143d5838..00000000 --- a/src/lockeddialog.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "lockeddialog.h" -#include "ui_lockeddialog.h" - -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) - : LockedDialogBase(parent, !unlockByButton) - , ui(new Ui::LockedDialog) -{ - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); - - if (!unlockByButton) - { - ui->unlockButton->hide(); - ui->verticalLayout->addItem( - new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); - } -} - -LockedDialog::~LockedDialog() -{ - delete ui; -} - - -void LockedDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - -void LockedDialog::on_unlockButton_clicked() -{ - unlock(); -} - -void LockedDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->unlockButton->setEnabled(false); -} diff --git a/src/lockeddialog.h b/src/lockeddialog.h deleted file mode 100644 index 36c16429..00000000 --- a/src/lockeddialog.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "lockeddialogbase.h" - -namespace Ui { - class LockedDialog; -} - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialog : public LockedDialogBase -{ - Q_OBJECT - -public: - explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false); - ~LockedDialog(); - - void setProcessName(const QString &name) override; - -protected: - - void unlock() override; - -private slots: - - void on_unlockButton_clicked(); - -private: - - Ui::LockedDialog *ui; -}; diff --git a/src/lockeddialog.ui b/src/lockeddialog.ui deleted file mode 100644 index 0ec2e467..00000000 --- a/src/lockeddialog.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - LockedDialog - - - - 0 - 0 - 317 - 151 - - - - Running virtualized processes - - - - - - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - - - MO is locked while the executable is running. - - - Qt::AlignCenter - - - true - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - true - - - - color: grey; - - - - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - Unlock - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp deleted file mode 100644 index 0876a511..00000000 --- a/src/lockeddialogbase.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "lockeddialogbase.h" -#include "envmodule.h" -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose) - : QDialog(parent) - , m_Unlocked(false) - , m_Canceled(false) - , m_allowClose(allowClose) -{ - if (parent != nullptr) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } -} - -void LockedDialogBase::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != nullptr) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - -void LockedDialogBase::reject() -{ - if (m_allowClose) - unlock(); -} - -bool LockedDialogBase::unlockForced() const { - return m_Unlocked; -} - -bool LockedDialogBase::canceled() const { - return m_Canceled; -} - -void LockedDialogBase::setProcessInformation(DWORD pid, const QString& name) -{ - setProcessName(QString("%1 (%2)").arg(name).arg(pid)); -} - -void LockedDialogBase::unlock() { - m_Unlocked = true; -} - -void LockedDialogBase::cancel() { - m_Canceled = true; -} - diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h deleted file mode 100644 index 4ebad4c7..00000000 --- a/src/lockeddialogbase.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "ilockedwaitingforprocess.h" -#include // for QDialog -#include // for Q_OBJECT, slots -#include // for QString - -class QResizeEvent; -class QWidget; - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialogBase : public QDialog, public ILockedWaitingForProcess -{ - Q_OBJECT - -public: - explicit LockedDialogBase(QWidget *parent, bool allowClose); - - bool unlockForced() const override; - - virtual bool canceled() const; - - void setProcessInformation(DWORD pid, const QString& name) override; - -protected: - - virtual void resizeEvent(QResizeEvent *event); - - virtual void reject(); - - virtual void unlock(); - - virtual void cancel(); - - bool m_Unlocked; - bool m_Canceled; - bool m_allowClose; -}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 631a31d8..9453f07c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -57,7 +57,6 @@ along with Mod Organizer. If not, see . #include "downloadlistwidget.h" #include "messagedialog.h" #include "installationmanager.h" -#include "waitingonclosedialog.h" #include "downloadlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3c535868..dda60f76 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -29,7 +29,6 @@ #include "appconfig.h" #include #include -#include "lockeddialog.h" #include "instancemanager.h" #include #include "previewdialog.h" diff --git a/src/spawn.cpp b/src/spawn.cpp index b331db01..62745542 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -27,8 +27,6 @@ along with Mod Organizer. If not, see . #include "envmodule.h" #include "settings.h" #include "settingsdialogworkarounds.h" -#include -#include #include #include #include @@ -645,6 +643,7 @@ bool startSteam(QWidget* parent) HANDLE ph = INVALID_HANDLE_VALUE; const auto e = spawn(sp, ph); + ::CloseHandle(ph); if (e != ERROR_SUCCESS) { // make sure username and passwords are not shown diff --git a/src/spawn.h b/src/spawn.h index 0464ffd6..9fb346b0 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -28,8 +28,6 @@ along with Mod Organizer. If not, see . class Settings; -namespace MOBase { class IPluginGame; } - namespace spawn { diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp deleted file mode 100644 index 565d0a36..00000000 --- a/src/waitingonclosedialog.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "waitingonclosedialog.h" -#include "ui_waitingonclosedialog.h" - -#include -#include -#include -#include // for Qt::FramelessWindowHint, etc - -WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent) - : LockedDialogBase(parent,true) - , ui(new Ui::WaitingOnCloseDialog) -{ - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); -} - -WaitingOnCloseDialog::~WaitingOnCloseDialog() -{ - delete ui; -} - - -void WaitingOnCloseDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - -void WaitingOnCloseDialog::on_closeButton_clicked() -{ - unlock(); -} - -void WaitingOnCloseDialog::on_cancelButton_clicked() -{ - cancel(); - unlock(); -} - -void WaitingOnCloseDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->closeButton->setEnabled(false); - ui->cancelButton->setEnabled(false); -} diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h deleted file mode 100644 index 6650c390..00000000 --- a/src/waitingonclosedialog.h +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#include "lockeddialogbase.h" - -namespace Ui { - class WaitingOnCloseDialog; -} - -/** - * Similar to the LockedDialog but used for waiting on running process during - * a process close request which requries a slightly different dialog. - **/ -class WaitingOnCloseDialog : public LockedDialogBase -{ - Q_OBJECT - -public: - explicit WaitingOnCloseDialog(QWidget *parent = 0); - ~WaitingOnCloseDialog(); - - bool canceled() const { return m_Canceled; } - - void setProcessName(const QString &name) override; - -protected: - - void unlock() override; - -private slots: - - void on_closeButton_clicked(); - void on_cancelButton_clicked(); - -private: - - Ui::WaitingOnCloseDialog *ui; -}; diff --git a/src/waitingonclosedialog.ui b/src/waitingonclosedialog.ui deleted file mode 100644 index 9c7818e0..00000000 --- a/src/waitingonclosedialog.ui +++ /dev/null @@ -1,119 +0,0 @@ - - - WaitingOnCloseDialog - - - - 0 - 0 - 317 - 151 - - - - Waiting for virtualized processes - - - - - - This dialog should disappear automatically if the application/game is done. - - - Virtualized processes are still running, it is prefered to keep MO running until they are finished. - - - true - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - true - - - - color: grey; - - - - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - - Close Now - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Cancel - - - - - - - - - Qt::Vertical - - - - 10 - - - - - - - - - -- cgit v1.3.1 From ada2ac0cb5d0ef2039ce55794928c4d05255ed65 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 31 Oct 2019 07:37:24 -0400 Subject: removed redundant checkBinary(), which used to check for non existing binaries, that's handled when spawning removed useless checkEnvironment(), which checked for EventLog removed CREATE_BREAKAWAY_FROM_JOB from CreateProcess() calls, didn't do anything fixed setFromFileOrExecutable() when running just a filename that's not an executable name split run() fixed lock widget being disabled when running without a ui --- src/lockwidget.cpp | 12 ++- src/processrunner.cpp | 197 ++++++++++++++++++++++++++++++++------------------ src/processrunner.h | 76 ++++++++++++++++++- src/spawn.cpp | 43 +---------- src/spawn.h | 4 - 5 files changed, 212 insertions(+), 120 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index bbffd390..35d51dab 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -229,10 +229,14 @@ void LockWidget::disableAll() } if (auto* d=dynamic_cast(w)) { - // no central widget, just disable the children, except for the overlay - for (auto* child : findChildrenImmediate(d)) { - if (child != m_overlay.get()) { - disable(child); + // don't disable stuff if this dialog is the overlay, which happens when + // there's no ui + if (d != m_overlay.get()) { + // no central widget, just disable the children, except for the overlay + for (auto* child : findChildrenImmediate(d)) { + if (child != m_overlay.get()) { + disable(child); + } } } } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 58b2a771..220ffe38 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -111,6 +111,9 @@ QString toString(Interest i) } +// returns a process that's in the hidden list, or the top-level process if +// they're all hidden; returns an invalid process if the list is empty +// std::pair findInterestingProcessInTrees( std::vector& processes) { @@ -150,6 +153,8 @@ std::pair findInterestingProcessInTrees( return {processes[0], Interest::Weak}; } +// gets the most interesting process in the list +// std::pair getInterestingProcess( const std::vector& initialProcesses) { @@ -160,7 +165,7 @@ std::pair getInterestingProcess( std::vector processes; - log::debug("getting process tree for {} processes", initialProcesses.size()); + // getting process trees for all processes for (auto&& h : initialProcesses) { auto tree = env::getProcessTree(h); if (tree.isValid()) { @@ -169,12 +174,15 @@ std::pair getInterestingProcess( } if (processes.empty()) { + // if the initial list wasn't empty but this one is, it means all the + // processes were already completed log::debug("processes are already completed"); return {{}, Interest::None}; } const auto interest = findInterestingProcessInTrees(processes); if (!interest.first.isValid()) { + // this shouldn't happen log::debug("no interesting process to wait for"); return {{}, Interest::None}; } @@ -184,6 +192,8 @@ std::pair getInterestingProcess( const std::chrono::milliseconds Infinite(-1); +// waits for completion, times out after `wait` if not Infinite +// std::optional timedWait( HANDLE handle, DWORD pid, LockWidget& lock, std::chrono::milliseconds wait) { @@ -195,18 +205,21 @@ std::optional timedWait( } for (;;) { + // wait for a very short while, allows for processing events below const auto r = singleWait(handle, pid); if (r) { + // the process has either completed or an error was returned return *r; } - // still running + // the process is still running // keep processing events so the app doesn't appear dead QCoreApplication::sendPostedEvents(); QCoreApplication::processEvents(); + // check the lock widget switch (lock.result()) { case LockWidget::StillLocked: @@ -239,8 +252,10 @@ std::optional timedWait( } if (wait != Infinite) { + // check if enough time has elapsed const auto now = high_resolution_clock::now(); if (duration_cast(now - start) >= wait) { + // if so, return an empty result return {}; } } @@ -258,6 +273,10 @@ ProcessRunner::Results waitForProcesses( } DWORD currentPID = 0; + + // if the interesting process that was found is weak (such as ModOrganizer.exe + // when starting a program from within the Data directory), start with a short + // wait and check for more interesting children milliseconds wait(50); for (;;) { @@ -267,14 +286,17 @@ ProcessRunner::Results waitForProcesses( return ProcessRunner::Completed; } + // update the lock widget lock.setInfo(p.pid(), p.name()); + // open the process auto interestingHandle = p.openHandleForWait(); if (!interestingHandle) { return ProcessRunner::Error; } if (p.pid() != currentPID) { + // log any change in the process being waited for currentPID = p.pid(); log::debug( @@ -283,19 +305,19 @@ ProcessRunner::Results waitForProcesses( } if (interest == Interest::Strong) { + // don't bother with short wait, this is a good process to wait for wait = Infinite; } const auto r = timedWait(interestingHandle.get(), p.pid(), lock, wait); if (r) { + // the process has completed or returned an error return *r; } + // exponentially increase the wait time between checks for interesting + // processes wait = std::min(wait * 2, milliseconds(2000)); - - log::debug( - "looking for a more interesting process (next check in {}ms)", - wait.count()); } } @@ -324,6 +346,7 @@ ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { + // all processes started in ProcessRunner are hooked m_sp.hooked = true; } @@ -383,6 +406,9 @@ ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targ parent = m_ui->qtWidget(); } + // if the file is a .exe, start it directory; if it's anything else, ask the + // shell to start it + const auto fec = spawn::getFileExecutionContext(parent, targetInfo); switch (fec.type) @@ -466,28 +492,25 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( throw MyException(QObject::tr("No profile set")); } + setBinary(executable); + setArguments(args.join(" ")); + setCurrentDirectory(cwd); setProfileName(profileOverride); if (executable.contains('\\') || executable.contains('/')) { // file path - auto binary = QFileInfo(executable); - - if (binary.isRelative()) { + if (m_sp.binary.isRelative()) { // relative path, should be relative to game directory - binary = m_core.managedGame()->gameDirectory().absoluteFilePath(executable); + setBinary(m_core.managedGame()->gameDirectory().absoluteFilePath(executable)); } - setBinary(binary); - if (cwd == "") { - setCurrentDirectory(binary.absolutePath()); - } else { - setCurrentDirectory(cwd); + setCurrentDirectory(m_sp.binary.absolutePath()); } try { - const Executable& exe = m_core.executablesList()->getByBinary(binary); + const Executable& exe = m_core.executablesList()->getByBinary(m_sp.binary); setSteamID(exe.steamAppID()); setCustomOverwrite(profile->setting("custom_overwrites", exe.title()).toString()); @@ -512,20 +535,15 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( if (args.isEmpty()) { setArguments(exe.arguments()); - } else { - setArguments(args.join(" ")); } setBinary(exe.binaryInfo()); if (cwd == "") { setCurrentDirectory(exe.workingDirectory()); - } else { - setCurrentDirectory(cwd); } } catch (const std::runtime_error &) { log::warn("\"{}\" not set up as executable", executable); - setBinary(QFileInfo(executable)); } } @@ -540,68 +558,91 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( ProcessRunner::Results ProcessRunner::run() { + std::optional r; + if (!m_shellOpen.isEmpty()) { - auto r = shell::Open(m_shellOpen); - if (!r.success()) { - return Error; - } + r = runShell(); + } else { + r = runBinary(); + } - m_handle.reset(r.stealProcessHandle()); + if (r) { + // early result: something went wrong and the process cannot be waited for + return *r; + } - // not all files will return a valid handle even if opening them was - // successful, such as inproc handlers (like the photo viewer); in this - // case it's impossible to determine the status, so just say it's still - // running - if (m_handle.get() == INVALID_HANDLE_VALUE) { - return Running; - } - } else { - if (m_profileName.isEmpty()) { - const auto* profile = m_core.currentProfile(); - if (!profile) { - throw MyException(QObject::tr("No profile set")); - } + return postRun(); +} - m_profileName = profile->name(); - } +std::optional ProcessRunner::runShell() +{ + log::debug("executing from shell: '{}'", m_shellOpen); - if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) { - return Error; - } + auto r = shell::Open(m_shellOpen); + if (!r.success()) { + return Error; + } - QWidget* parent = nullptr; - if (m_ui) { - parent = m_ui->qtWidget(); - } + m_handle.reset(r.stealProcessHandle()); - if (!checkBinary(parent, m_sp)) { - return Error; - } + // not all files will return a valid handle even if opening them was + // successful, such as inproc handlers (like the photo viewer); in this + // case it's impossible to determine the status, so just say it's still + // running + if (m_handle.get() == INVALID_HANDLE_VALUE) { + log::debug("shell didn't report an error, but no handle is available"); + return Running; + } - const auto* game = m_core.managedGame(); - auto& settings = m_core.settings(); + return {}; +} - if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { - return Error; +std::optional ProcessRunner::runBinary() +{ + if (m_profileName.isEmpty()) { + // get the current profile name if it wasn't overridden + const auto* profile = m_core.currentProfile(); + if (!profile) { + throw MyException(QObject::tr("No profile set")); } - if (!checkEnvironment(parent, m_sp)) { - return Error; - } + m_profileName = profile->name(); + } - if (!checkBlacklist(parent, m_sp, settings)) { - return Error; - } + // saves profile, sets up usvfs, notifies plugins, etc.; can return false if + // a plugin doesn't want the program to run (such as when checkFNIS fails to + // run FNIS and the user clicks cancel) + if (!m_core.beforeRun(m_sp.binary, m_profileName, m_customOverwrite, m_forcedLibraries)) { + return Error; + } - adjustForVirtualized(game, m_sp, settings); + // parent widget used for any dialog popped up while checking for things + QWidget* parent = (m_ui ? m_ui->qtWidget() : nullptr); - m_handle.reset(startBinary(parent, m_sp)); - if (m_handle.get() == INVALID_HANDLE_VALUE) { - return Error; - } + const auto* game = m_core.managedGame(); + auto& settings = m_core.settings(); + + // start steam if needed + if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { + return Error; } - return postRun(); + // warn if the executable is on the blacklist + if (!checkBlacklist(parent, m_sp, settings)) { + return Error; + } + + // if the executable is inside the mods folder another instance of + // ModOrganizer.exe is spawned instead to launch it + adjustForVirtualized(game, m_sp, settings); + + // run the binary + m_handle.reset(startBinary(parent, m_sp)); + if (m_handle.get() == INVALID_HANDLE_VALUE) { + return Error; + } + + return {}; } ProcessRunner::Results ProcessRunner::postRun() @@ -618,7 +659,7 @@ ProcessRunner::Results ProcessRunner::postRun() } if (mustWait) { - if (!Settings::instance().interface().lockGUI()) { + if (!m_core.settings().interface().lockGUI()) { // at least tell the user what's going on log::debug( "locking is disabled, but the output of the application is required; " @@ -632,7 +673,7 @@ ProcessRunner::Results ProcessRunner::postRun() return Running; } - if (!Settings::instance().interface().lockGUI()) { + if (!m_core.settings().interface().lockGUI()) { // disabling locking is like clicking on unlock immediately log::debug("not waiting for process because locking is disabled"); return ForceUnlocked; @@ -646,6 +687,18 @@ ProcessRunner::Results ProcessRunner::postRun() }); if (r == Completed && (m_waitFlags & Refresh)) { + // afterRun() is only called with the Refresh flag; it refreshes the + // directory structure and notifies plugins + // + // refreshing is not always required and can actually cause problems: + // + // 1) running shortcuts doesn't need refreshing because MO closes right + // after + // + // 2) the mod info dialog is not set up to deal with refreshes, so that + // it will crash because the old DirectoryEntry's are still being used + // in the list + // m_core.afterRun(m_sp.binary, m_exitCode); } @@ -670,7 +723,9 @@ HANDLE ProcessRunner::getProcessHandle() const env::HandlePtr ProcessRunner::stealProcessHandle() { - return std::move(m_handle); + auto h = m_handle.release(); + m_handle.reset(INVALID_HANDLE_VALUE); + return env::HandlePtr(h); } ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( @@ -678,7 +733,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( { m_lockReason = reason; - if (!Settings::instance().interface().lockGUI()) { + if (!m_core.settings().interface().lockGUI()) { // disabling locking is like clicking on unlock immediately return ForceUnlocked; } diff --git a/src/processrunner.h b/src/processrunner.h index 0fb3f59a..fd451e38 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -19,22 +19,34 @@ class ProcessRunner public: enum Results { + // the process is still running Running = 1, + + // the process has run to completion Completed, + + // the process couldn't be started or waited for Error, + + // the user has clicked the cancel button in the lock widget Cancelled, + + // the user has clicked the unlock button in the lock widget ForceUnlocked }; enum WaitFlag { NoFlags = 0x00, + + // the ui will be refreshed once the process has completed Refresh = 0x01, + + // the process will be waited for even if locking is disabled ForceWait = 0x02 }; using WaitFlags = QFlags; - using ForcedLibraries = QList; ProcessRunner(OrganizerCore& core, IUserInterface* ui); @@ -55,10 +67,29 @@ public: ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); + // if the target is an executable file, runs that; for anything else, calls + // ShellExecute() on it + // ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); + ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); + // this is a messy one that's used for running an arbitrary file from the + // command line, or by plugins (see OrganizerProxy::startApplication()) + // + // 1) if `executable` contains a path separator, it's treated as a binary on + // disk and will be launched with given settings; it's also looked up in + // the list of configured executables, which sets the steam ID and forced + // libraries + // + // 2) if `executable` has no path separators, it's treated purely as an + // executable, but its arguments, current directory and custom overwrite + // can also be overridden + // + // if the executable is not found in the list, the binary is run solely + // based on the parameters given + // ProcessRunner& setFromFileOrExecutable( const QString &executable, const QStringList &args, @@ -67,13 +98,42 @@ public: const QString &forcedCustomOverwrite={}, bool ignoreCustomOverwrite=false); + // spawns the process and waits for it if required + // Results run(); + + // takes ownership of the given handle and waits for it if required + // Results attachToProcess(HANDLE h); + // exit code of the process, will return -1 if the process wasn't waited for + // DWORD exitCode() const; + + // this may be INVALID_HANDLE_VALUE if: + // + // 1) no process was started, or + // 2) the process was started successfully, but the system didn't return a + // handle for it; this can happen for inproc handlers, for example, such + // the photo viewer + // + // note that the handle is still owned by this ProcessRunner and will be + // closed when destroyed; see stealProcessHandle() + // HANDLE getProcessHandle() const; + + // releases ownership of the process handle; if this is called after the + // process is completed, exitCode() will still return the correct value + // env::HandlePtr stealProcessHandle(); + // waits for all usvfs processes spawned by this instance of MO; returns + // immediately with ForceUnlocked if locking is disabled + // + // strictly speaking, this shouldn't be here, as it has nothing to do with + // running a process, but it uses the same internal stuff as when running a + // process + // Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); private: @@ -89,7 +149,21 @@ private: env::HandlePtr m_handle; DWORD m_exitCode; + + // runs the command in m_shellOpen; returns empty if it can be waited for + // + std::optional runShell(); + + // runs the binary; returns empty if it can be waited for + // + std::optional runBinary(); + + // waits for process completion if required + // Results postRun(); + + // creates the lock widget and calls f() + // void withLock(std::function f); }; diff --git a/src/spawn.cpp b/src/spawn.cpp index 62745542..f95846c8 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -490,7 +490,7 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) const QString moPath = QCoreApplication::applicationDirPath(); const auto oldPath = env::addPath(QDir::toNativeSeparators(moPath)); - PROCESS_INFORMATION pi; + PROCESS_INFORMATION pi = {}; BOOL success = FALSE; logSpawning(sp, commandLine); @@ -501,13 +501,11 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) if (sp.hooked) { success = ::CreateProcessHooked( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - wcwd.c_str(), &si, &pi); + inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, CREATE_BREAKAWAY_FROM_JOB, nullptr, - wcwd.c_str(), &si, &pi); + inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); @@ -557,16 +555,6 @@ void startBinaryAdmin(QWidget* parent, const SpawnParameters& sp) restartAsAdmin(parent); } -bool checkBinary(QWidget* parent, const SpawnParameters& sp) -{ - if (!sp.binary.exists()) { - dialogs::spawnFailed(parent, sp, ERROR_FILE_NOT_FOUND); - return false; - } - - return true; -} - struct SteamStatus { bool running=false; @@ -754,31 +742,6 @@ bool checkSteam( return true; } -bool checkEnvironment(QWidget* parent, const SpawnParameters& sp) -{ - // check if the Windows Event Logging service is running; for some reason, - // this seems to be critical to the successful running of usvfs. - const auto serviceName = "EventLog"; - - const auto s = env::getService(serviceName); - - if (!s.isValid()) { - log::error( - "cannot determine the status of the {} service, continuing", - serviceName); - - return true; - } - - if (s.status() == env::Service::Status::Running) { - log::debug("{}", s.toString()); - return true; - } - - log::error("{}", s.toString()); - return dialogs::eventLogNotRunning(parent, s, sp); -} - bool checkBlacklist( QWidget* parent, const SpawnParameters& sp, Settings& settings) { diff --git a/src/spawn.h b/src/spawn.h index 9fb346b0..a615b5ff 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -53,14 +53,10 @@ struct SpawnParameters }; -bool checkBinary(QWidget* parent, const SpawnParameters& sp); - bool checkSteam( QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString &steamAppID, const Settings& settings); -bool checkEnvironment(QWidget* parent, const SpawnParameters& sp); - bool checkBlacklist( QWidget* parent, const SpawnParameters& sp, Settings& settings); -- cgit v1.3.1 From 3d0197fa5e6c4209d021398415f779994c21bd24 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 1 Nov 2019 06:47:53 -0400 Subject: comments --- src/lockwidget.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src') diff --git a/src/lockwidget.h b/src/lockwidget.h index 18ff76dd..d3ff2f7d 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -5,22 +5,40 @@ class LockWidget { public: + // reason to show the widget + // enum Reasons { NoReason = 0, + + // lock the ui LockUI, + + // because the output is required OutputRequired, + + // to prevent exiting until all processes are completed PreventExit }; + // returned by result() + // enum Results { NoResult = 0, + + // the widget is still up StillLocked, + + // force unlock was clicked ForceUnlocked, + + // cancel was clicked Cancelled }; + // if `reason` is not NoReason, lock() is called with it + // LockWidget(QWidget* parent, Reasons reason=NoReason); ~LockWidget(); -- cgit v1.3.1 From 642ddacab802df75d21a1ccf7508268d3efa141a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 05:23:22 -0500 Subject: moved LockWidget back to OrganizerCore to support having multiple locks active moved the ui out of LockWidget into LockInterface added LockWidget::Session to manage multi locks --- src/lockwidget.cpp | 490 ++++++++++++++++++++++++++++++++++++-------------- src/lockwidget.h | 65 +++---- src/organizercore.cpp | 1 + src/organizercore.h | 3 + src/processrunner.cpp | 33 ++-- src/processrunner.h | 2 +- 6 files changed, 414 insertions(+), 180 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 35d51dab..68eb9c2d 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -4,203 +4,431 @@ #include #include -QWidget* createTransparentWidget(QWidget* parent=nullptr) +class LockInterface { - auto* w = new QWidget(parent); +public: + LockInterface() : + m_hasMainUI(false), m_message(nullptr), m_info(nullptr), m_buttons(nullptr) + { + } - w->setWindowOpacity(0); - w->setAttribute(Qt::WA_NoSystemBackground); - w->setAttribute(Qt::WA_TranslucentBackground); + ~LockInterface() + { + } - return w; -} + void set(QWidget* target) + { + QFrame* center = nullptr; + if (target) { + center = createOverlay(target); + } else { + center = createDialog(); + } -LockWidget::LockWidget(QWidget* parent, Reasons reason) : - m_parent(parent), m_overlay(nullptr), m_info(nullptr), m_result(NoResult), - m_filter(nullptr) -{ - if (reason != NoReason) { - lock(reason); + createMessageLabel(); + createInfoLabel(); + createButtonsPanel(); + + center->layout()->addWidget(m_message); + center->layout()->addWidget(m_info); + center->layout()->addWidget(m_buttons); + + m_topLevel->setFocusPolicy(Qt::TabFocus); + m_topLevel->setFocus(); + m_topLevel->show(); + m_topLevel->setEnabled(true); + + m_topLevel->raise(); + m_topLevel->activateWindow(); } -} -LockWidget::~LockWidget() -{ - unlock(); -} + void update(LockWidget::Reasons reason) + { + updateMessage(reason); + updateButtons(reason); + } -void LockWidget::lock(Reasons reason) -{ - m_result = StillLocked; - createUi(reason); -} + void setInfo(const QString& s) + { + m_info->setText(s); + } -void LockWidget::unlock() -{ - m_overlay.reset(); + QWidget* topLevel() + { + return m_topLevel.get(); + } + +private: + class Filter : public QObject + { + public: + std::function resized; + std::function closed; + + protected: + bool eventFilter(QObject* o, QEvent* e) override + { + if (e->type() == QEvent::Resize) { + if (resized) { + resized(); + } + } else if (e->type() == QEvent::Close) { + if (closed) { + closed(); + } + } + + return QObject::eventFilter(o, e); + } + }; + + + bool m_hasMainUI; + std::unique_ptr m_topLevel; + QLabel* m_message; + QLabel* m_info; + QWidget* m_buttons; + std::unique_ptr m_filter; + + QWidget* createTransparentWidget(QWidget* parent=nullptr) + { + auto* w = new QWidget(parent); + + w->setWindowOpacity(0); + w->setAttribute(Qt::WA_NoSystemBackground); + w->setAttribute(Qt::WA_TranslucentBackground); - if (m_filter && m_parent) { - m_parent->removeEventFilter(m_filter.get()); + return w; } - enableAll(); -} + QFrame* createOverlay(QWidget* mainUI) + { + m_hasMainUI = true; -void LockWidget::setInfo(DWORD pid, const QString& name) -{ - m_info->setText(QString("%1 (%2)").arg(name).arg(pid)); -} + m_topLevel.reset(createTransparentWidget(mainUI)); + m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); + m_topLevel->setGeometry(mainUI->rect()); -LockWidget::Results LockWidget::result() const -{ - return m_result; -} + m_filter.reset(new Filter); + m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; + m_filter->closed = [=]{ LockWidget::instance().onForceUnlock(); }; -void LockWidget::createUi(Reasons reason) -{ - QWidget* overlayTarget = m_parent; - if (auto* w = qApp->activeWindow()) { - overlayTarget = w; + mainUI->installEventFilter(m_filter.get()); + + return createFrame(); } - if (overlayTarget) { - m_overlay.reset(createTransparentWidget(overlayTarget)); - m_overlay->setWindowFlags(m_overlay->windowFlags() & Qt::FramelessWindowHint); - m_overlay->setGeometry(overlayTarget->rect()); - } else { - m_overlay.reset(new QDialog); + QFrame* createDialog() + { + m_hasMainUI = false; + m_topLevel.reset(new QDialog); + + return createFrame(); } - auto* center = new QFrame; + QFrame* createFrame() + { + auto* frame = new QFrame; + auto* ly = new QVBoxLayout(frame); + + if (m_hasMainUI) { + frame->setFrameStyle(QFrame::StyledPanel); + frame->setLineWidth(1); + frame->setAutoFillBackground(true); + + auto* shadow = new QGraphicsDropShadowEffect; + shadow->setBlurRadius(50); + shadow->setOffset(0); + shadow->setColor(QColor(0, 0, 0, 100)); + frame->setGraphicsEffect(shadow); + } else { + ly->setContentsMargins(0, 0, 0, 0); + } - if (overlayTarget) { - center->setFrameStyle(QFrame::StyledPanel); - center->setLineWidth(1); - center->setAutoFillBackground(true); + auto* grid = new QGridLayout(m_topLevel.get()); + grid->addWidget(createTransparentWidget(), 0, 1); + grid->addWidget(createTransparentWidget(), 2, 1); + grid->addWidget(createTransparentWidget(), 1, 0); + grid->addWidget(createTransparentWidget(), 1, 2); + grid->addWidget(frame, 1, 1); - auto* shadow = new QGraphicsDropShadowEffect; - shadow->setBlurRadius(50); - shadow->setOffset(0); - shadow->setColor(QColor(0, 0, 0, 100)); - center->setGraphicsEffect(shadow); - } + if (!m_hasMainUI) { + grid->setContentsMargins(0, 0, 0, 0); + } - m_info = new QLabel(" "); - m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + grid->setRowStretch(0, 1); + grid->setRowStretch(2, 1); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(2, 1); - auto* ly = new QVBoxLayout(center); + return frame; + } - if (!overlayTarget) { - ly->setContentsMargins(0, 0, 0, 0); + void createMessageLabel() + { + m_message = new QLabel; + } + + void createInfoLabel() + { + m_info = new QLabel(" "); + m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); } - auto* message = new QLabel; - ly->addWidget(message); - ly->addWidget(m_info); + void createButtonsPanel() + { + m_buttons = new QWidget; + m_buttons->setLayout(new QHBoxLayout); + } - auto* buttons = new QWidget; - auto* buttonsLayout = new QHBoxLayout(buttons); - ly->addWidget(buttons); - switch (reason) + void updateMessage(LockWidget::Reasons reason) { - case LockUI: + switch (reason) { - QString s; + case LockWidget::LockUI: + { + QString s; + + if (m_hasMainUI) { + s = QObject::tr( + "Mod Organizer is locked while the application is running."); + } else { + s = QObject::tr("Mod Organizer is currently running an application."); + } - if (!m_parent) { - s = QObject::tr("Mod Organizer is currently running an application."); - } else { - s = QObject::tr( - "Mod Organizer is locked while the application is running."); + m_message->setText(s); + + break; } - message->setText(s); + case LockWidget::OutputRequired: + { + m_message->setText(QObject::tr( + "The application must run to completion because its output is " + "required.")); - auto* unlockButton = new QPushButton(QObject::tr("Unlock")); - QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); - buttonsLayout->addWidget(unlockButton); + break; + } - break; + case LockWidget::PreventExit: + { + m_message->setText(QObject::tr( + "Mod Organizer is waiting on application to close before exiting.")); + + break; + } } + } - case OutputRequired: + void updateButtons(LockWidget::Reasons reason) + { + MOBase::deleteChildWidgets(m_buttons); + auto* ly = m_buttons->layout(); + + switch (reason) { - message->setText(QObject::tr( - "The application must run to completion because its output is " - "required.")); + case LockWidget::LockUI: // fall-through + case LockWidget::OutputRequired: + { + auto* unlock = new QPushButton(QObject::tr("Unlock")); - auto* unlockButton = new QPushButton(QObject::tr("Unlock")); - QObject::connect(unlockButton, &QPushButton::clicked, [&]{ onForceUnlock(); }); - buttonsLayout->addWidget(unlockButton); + QObject::connect(unlock, &QPushButton::clicked, [&]{ + LockWidget::instance().onForceUnlock(); + }); - break; + ly->addWidget(unlock); + + break; + } + + case LockWidget::PreventExit: + { + auto* exit = new QPushButton(QObject::tr("Exit Now")); + QObject::connect(exit, &QPushButton::clicked, [&]{ + LockWidget::instance().onForceUnlock(); + }); + + ly->addWidget(exit); + + auto* cancel = new QPushButton(QObject::tr("Cancel")); + QObject::connect(cancel, &QPushButton::clicked, [&]{ + LockWidget::instance().onCancel(); + }); + + ly->addWidget(cancel); + + break; + } } + } +}; - case PreventExit: - { - message->setText(QObject::tr( - "Mod Organizer is waiting on application to close before exiting.")); +LockWidget::Session::~Session() +{ + LockWidget::instance().unlock(this); +} + +void LockWidget::Session::setInfo(DWORD pid, const QString& name) +{ + m_pid = pid; + m_name = name; + + LockWidget::instance().updateLabel(); +} + +DWORD LockWidget::Session::pid() const +{ + return m_pid; +} + +const QString& LockWidget::Session::name() const +{ + return m_name; +} - auto* exit = new QPushButton(QObject::tr("Exit Now")); - QObject::connect(exit, &QPushButton::clicked, [&]{ onForceUnlock(); }); - buttonsLayout->addWidget(exit); +LockWidget::Results LockWidget::Session::result() const +{ + return LockWidget::instance().result(); +} + + +static LockWidget* g_instance = nullptr; + + +LockWidget::LockWidget() + : m_parent(nullptr), m_result(NoResult) +{ + Q_ASSERT(!g_instance); + g_instance = this; +} + +LockWidget::~LockWidget() +{ + const auto v = m_sessions; + + for (auto& wp : v) { + if (auto s=wp.lock()) { + unlock(s.get()); + } + } +} - auto* cancel = new QPushButton(QObject::tr("Cancel")); - QObject::connect(cancel, &QPushButton::clicked, [&]{ onCancel(); }); - buttonsLayout->addWidget(cancel); +LockWidget& LockWidget::instance() +{ + Q_ASSERT(g_instance); + return *g_instance; +} + +void LockWidget::setUserInterface(QWidget* parent) +{ + m_parent = parent; +} +std::shared_ptr LockWidget::lock(Reasons reason) +{ + m_result = StillLocked; + createUi(reason); + + auto ls = std::make_shared(); + m_sessions.push_back(ls); + + updateLabel(); + + return ls; +} + +void LockWidget::unlock(Session* s) +{ + auto itor = m_sessions.begin(); + for (;;) { + if (itor == m_sessions.end()) { break; } + + if (auto ss=itor->lock()) { + if (ss.get() == s) { + itor = m_sessions.erase(itor); + continue; + } + } else { + itor = m_sessions.erase(itor); + continue; + } + + ++itor; } - auto* grid = new QGridLayout(m_overlay.get()); - grid->addWidget(createTransparentWidget(), 0, 1); - grid->addWidget(createTransparentWidget(), 2, 1); - grid->addWidget(createTransparentWidget(), 1, 0); - grid->addWidget(createTransparentWidget(), 1, 2); - grid->addWidget(center, 1, 1); + if (m_sessions.empty()) { + m_ui.reset(); + enableAll(); + } else { + updateLabel(); + } +} - if (!overlayTarget) { - grid->setContentsMargins(0, 0, 0, 0); +void LockWidget::unlockCurrent() +{ + if (m_sessions.empty()) { + return; } - grid->setRowStretch(0, 1); - grid->setRowStretch(2, 1); - grid->setColumnStretch(0, 1); - grid->setColumnStretch(2, 1); + auto s = m_sessions.back().lock(); + if (!s) { + m_sessions.pop_back(); + return; + } - disableAll(); + unlock(s.get()); +} - if (overlayTarget) { - m_filter.reset(new Filter); - m_filter->resized = [=]{ m_overlay->setGeometry(overlayTarget->rect()); }; - m_filter->closed = [=]{ onForceUnlock(); }; - overlayTarget->installEventFilter(m_filter.get()); +void LockWidget::updateLabel() +{ + QString label; + + for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { + if (auto ss=itor->lock()) { + label += QString("%1 (%2)").arg(ss->name()).arg(ss->pid()); + break; + } } - m_overlay->setFocusPolicy(Qt::TabFocus); - m_overlay->setFocus(); - m_overlay->show(); - m_overlay->setEnabled(true); + m_ui->setInfo(label); +} - if (!overlayTarget) { - m_overlay->raise(); - m_overlay->activateWindow(); +LockWidget::Results LockWidget::result() const +{ + return m_result; +} + +void LockWidget::createUi(Reasons reason) +{ + QWidget* target = m_parent; + if (auto* w = qApp->activeWindow()) { + target = w; } + + if (!m_ui) { + m_ui.reset(new LockInterface); + } + + m_ui->set(target); + m_ui->update(reason); + + disableAll(); } void LockWidget::onForceUnlock() { m_result = ForceUnlocked; - unlock(); + unlockCurrent(); } void LockWidget::onCancel() { m_result = Cancelled; - unlock(); + unlockCurrent(); } template @@ -231,10 +459,10 @@ void LockWidget::disableAll() if (auto* d=dynamic_cast(w)) { // don't disable stuff if this dialog is the overlay, which happens when // there's no ui - if (d != m_overlay.get()) { + if (d != m_ui->topLevel()) { // no central widget, just disable the children, except for the overlay for (auto* child : findChildrenImmediate(d)) { - if (child != m_overlay.get()) { + if (child != m_ui->topLevel()) { disable(child); } } diff --git a/src/lockwidget.h b/src/lockwidget.h index d3ff2f7d..8062c478 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -2,8 +2,12 @@ #include +class LockInterface; + class LockWidget { + friend class LockInterface; + public: // reason to show the widget // @@ -37,51 +41,50 @@ public: Cancelled }; + + class Session + { + public: + ~Session(); + + void setInfo(DWORD pid, const QString& name); + Results result() const; + + DWORD pid() const; + const QString& name() const; + + private: + DWORD m_pid; + QString m_name; + }; + + // if `reason` is not NoReason, lock() is called with it // - LockWidget(QWidget* parent, Reasons reason=NoReason); + LockWidget(); ~LockWidget(); - void lock(Reasons reason); - void unlock(); + static LockWidget& instance(); - void setInfo(DWORD pid, const QString& name); - Results result() const; + void setUserInterface(QWidget* parent); -private: - class Filter : public QObject - { - public: - std::function resized; - std::function closed; - - protected: - bool eventFilter(QObject* o, QEvent* e) override - { - if (e->type() == QEvent::Resize) { - if (resized) { - resized(); - } - } else if (e->type() == QEvent::Close) { - if (closed) { - closed(); - } - } - - return QObject::eventFilter(o, e); - } - }; + std::shared_ptr lock(Reasons reason); + Results result() const; +private: QWidget* m_parent; - std::unique_ptr m_overlay; - QLabel* m_info; + std::unique_ptr m_ui; + std::vector> m_sessions; Results m_result; - std::unique_ptr m_filter; std::vector> m_disabled; void createUi(Reasons reason); + void unlockCurrent(); + void unlock(Session* s); + void updateLabel(); + void onForceUnlock(); void onCancel(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index dda60f76..3d4d8e4b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -250,6 +250,7 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); + m_LockWidget.setUserInterface(w); checkForUpdates(); } diff --git a/src/organizercore.h b/src/organizercore.h index 0ba52284..7e0e4b7f 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -14,6 +14,7 @@ #include "usvfsconnector.h" #include "moshortcut.h" #include "processrunner.h" +#include "lockwidget.h" #include #include #include @@ -343,6 +344,8 @@ private: MOBase::DelayedFileWriter m_PluginListsWriter; UsvfsConnector m_USVFS; + LockWidget m_LockWidget; + static CrashDumpsType m_globalCrashDumpsType; }; diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 220ffe38..d97c00ef 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -195,7 +195,8 @@ const std::chrono::milliseconds Infinite(-1); // waits for completion, times out after `wait` if not Infinite // std::optional timedWait( - HANDLE handle, DWORD pid, LockWidget& lock, std::chrono::milliseconds wait) + HANDLE handle, DWORD pid, LockWidget::Session& ls, + std::chrono::milliseconds wait) { using namespace std::chrono; @@ -220,7 +221,7 @@ std::optional timedWait( QCoreApplication::processEvents(); // check the lock widget - switch (lock.result()) + switch (ls.result()) { case LockWidget::StillLocked: { @@ -245,7 +246,7 @@ std::optional timedWait( // shouldn't happen log::debug( "unexpected result {} while waiting for {}", - static_cast(lock.result()), pid); + static_cast(ls.result()), pid); return ProcessRunner::Error; } @@ -263,7 +264,7 @@ std::optional timedWait( } ProcessRunner::Results waitForProcesses( - const std::vector& initialProcesses, LockWidget& lock) + const std::vector& initialProcesses, LockWidget::Session& ls) { using namespace std::chrono; @@ -287,7 +288,7 @@ ProcessRunner::Results waitForProcesses( } // update the lock widget - lock.setInfo(p.pid(), p.name()); + ls.setInfo(p.pid(), p.name()); // open the process auto interestingHandle = p.openHandleForWait(); @@ -309,7 +310,7 @@ ProcessRunner::Results waitForProcesses( wait = Infinite; } - const auto r = timedWait(interestingHandle.get(), p.pid(), lock, wait); + const auto r = timedWait(interestingHandle.get(), p.pid(), ls, wait); if (r) { // the process has completed or returned an error return *r; @@ -322,11 +323,11 @@ ProcessRunner::Results waitForProcesses( } ProcessRunner::Results waitForProcess( - HANDLE initialProcess, LPDWORD exitCode, LockWidget& lock) + HANDLE initialProcess, LPDWORD exitCode, LockWidget::Session& ls) { std::vector processes = {initialProcess}; - const auto r = waitForProcesses(processes, lock); + const auto r = waitForProcesses(processes, ls); // as long as it's not running anymore, try to get the exit code if (exitCode && r != ProcessRunner::Running) { @@ -682,8 +683,8 @@ ProcessRunner::Results ProcessRunner::postRun() auto r = Error; - withLock([&](auto& lock) { - r = waitForProcess(m_handle.get(), &m_exitCode, lock); + withLock([&](auto& ls) { + r = waitForProcess(m_handle.get(), &m_exitCode, ls); }); if (r == Completed && (m_waitFlags & Refresh)) { @@ -740,14 +741,14 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; - withLock([&](auto& lock) { + withLock([&](auto& ls) { for (;;) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { break; } - r = waitForProcesses(processes, lock); + r = waitForProcesses(processes, ls); if (r != Completed) { // error, cancelled, or unlocked @@ -763,10 +764,8 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( return r; } -void ProcessRunner::withLock(std::function f) +void ProcessRunner::withLock(std::function f) { - auto lk = std::make_unique( - m_ui ? m_ui->qtWidget() : nullptr, m_lockReason); - - f(*lk); + auto ls = LockWidget::instance().lock(m_lockReason); + f(*ls); } diff --git a/src/processrunner.h b/src/processrunner.h index fd451e38..276c46b1 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -164,7 +164,7 @@ private: // creates the lock widget and calls f() // - void withLock(std::function f); + void withLock(std::function f); }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); -- cgit v1.3.1 From 96cc662b187f465713d3963f7c8cf9a1b4856aa5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 06:09:12 -0500 Subject: lock widget now moves between dialogs and the main window when closing dialogs --- src/lockwidget.cpp | 72 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 68eb9c2d..aae0316b 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -7,21 +7,47 @@ class LockInterface { public: - LockInterface() : - m_hasMainUI(false), m_message(nullptr), m_info(nullptr), m_buttons(nullptr) + LockInterface(QWidget* mainUI) : + m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), + m_buttons(nullptr), m_reason(LockWidget::NoReason) { + m_timer.reset(new QTimer); + QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); + m_timer->start(200); + + set(); } ~LockInterface() { } - void set(QWidget* target) + void checkTarget() + { + if (set()) { + update(m_reason); + } + } + + bool set() { + QWidget* newTarget = nullptr; + + newTarget = m_mainUI; + if (auto* w = QApplication::activeModalWidget()) { + newTarget = w; + } + + if (newTarget == m_target) { + return false; + } + + m_target = newTarget; + QFrame* center = nullptr; - if (target) { - center = createOverlay(target); + if (m_target) { + center = createOverlay(m_target); } else { center = createDialog(); } @@ -41,16 +67,21 @@ public: m_topLevel->raise(); m_topLevel->activateWindow(); + + return true; } void update(LockWidget::Reasons reason) { + m_reason = reason; updateMessage(reason); updateButtons(reason); + setInfo(m_infoText); } void setInfo(const QString& s) { + m_infoText = s; m_info->setText(s); } @@ -84,12 +115,22 @@ private: }; - bool m_hasMainUI; + std::unique_ptr m_timer; + QWidget* m_mainUI; + QWidget* m_target; std::unique_ptr m_topLevel; QLabel* m_message; QLabel* m_info; + QString m_infoText; QWidget* m_buttons; std::unique_ptr m_filter; + LockWidget::Reasons m_reason; + + + bool hasMainUI() const + { + return (m_target != nullptr); + } QWidget* createTransparentWidget(QWidget* parent=nullptr) { @@ -104,15 +145,13 @@ private: QFrame* createOverlay(QWidget* mainUI) { - m_hasMainUI = true; - m_topLevel.reset(createTransparentWidget(mainUI)); m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); m_topLevel->setGeometry(mainUI->rect()); m_filter.reset(new Filter); m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; - m_filter->closed = [=]{ LockWidget::instance().onForceUnlock(); }; + m_filter->closed = [=]{ checkTarget(); }; mainUI->installEventFilter(m_filter.get()); @@ -121,7 +160,6 @@ private: QFrame* createDialog() { - m_hasMainUI = false; m_topLevel.reset(new QDialog); return createFrame(); @@ -132,7 +170,7 @@ private: auto* frame = new QFrame; auto* ly = new QVBoxLayout(frame); - if (m_hasMainUI) { + if (hasMainUI()) { frame->setFrameStyle(QFrame::StyledPanel); frame->setLineWidth(1); frame->setAutoFillBackground(true); @@ -153,7 +191,7 @@ private: grid->addWidget(createTransparentWidget(), 1, 2); grid->addWidget(frame, 1, 1); - if (!m_hasMainUI) { + if (!hasMainUI()) { grid->setContentsMargins(0, 0, 0, 0); } @@ -191,7 +229,7 @@ private: { QString s; - if (m_hasMainUI) { + if (hasMainUI()) { s = QObject::tr( "Mod Organizer is locked while the application is running."); } else { @@ -404,16 +442,10 @@ LockWidget::Results LockWidget::result() const void LockWidget::createUi(Reasons reason) { - QWidget* target = m_parent; - if (auto* w = qApp->activeWindow()) { - target = w; - } - if (!m_ui) { - m_ui.reset(new LockInterface); + m_ui.reset(new LockInterface(m_parent)); } - m_ui->set(target); m_ui->update(reason); disableAll(); -- cgit v1.3.1 From 8f40ec0bdcb99cc1a0a2bde3074842391844f5d6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 06:50:05 -0500 Subject: threaded wait for process --- src/lockwidget.cpp | 22 ++++++++++++++++++---- src/lockwidget.h | 5 ++++- src/processrunner.cpp | 42 +++++++++++++++++++++++++++++++----------- 3 files changed, 53 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index aae0316b..8b654674 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -305,24 +305,38 @@ private: LockWidget::Session::~Session() { - LockWidget::instance().unlock(this); + unlock(); +} + +void LockWidget::Session::unlock() +{ + QMetaObject::invokeMethod(qApp, [this]{ + LockWidget::instance().unlock(this); + }); } void LockWidget::Session::setInfo(DWORD pid, const QString& name) { - m_pid = pid; - m_name = name; + { + std::scoped_lock lock(m_mutex); + m_pid = pid; + m_name = name; + } - LockWidget::instance().updateLabel(); + QMetaObject::invokeMethod(qApp, [this]{ + LockWidget::instance().updateLabel(); + }); } DWORD LockWidget::Session::pid() const { + std::scoped_lock lock(m_mutex); return m_pid; } const QString& LockWidget::Session::name() const { + std::scoped_lock lock(m_mutex); return m_name; } diff --git a/src/lockwidget.h b/src/lockwidget.h index 8062c478..640d0076 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -1,6 +1,7 @@ #pragma once #include +#include class LockInterface; @@ -47,6 +48,7 @@ public: public: ~Session(); + void unlock(); void setInfo(DWORD pid, const QString& name); Results result() const; @@ -54,6 +56,7 @@ public: const QString& name() const; private: + mutable std::mutex m_mutex; DWORD m_pid; QString m_name; }; @@ -76,7 +79,7 @@ private: QWidget* m_parent; std::unique_ptr m_ui; std::vector> m_sessions; - Results m_result; + std::atomic m_result; std::vector> m_disabled; void createUi(Reasons reason); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index d97c00ef..68acca92 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -55,11 +55,7 @@ std::optional singleWait(HANDLE handle, DWORD pid) return ProcessRunner::Error; } - const DWORD WAIT_EVENT = WAIT_OBJECT_0 + 1; - - // Wait for a an event on the handle, a key press, mouse click or timeout - const auto res = MsgWaitForMultipleObjects( - 1, &handle, FALSE, 50, QS_KEY | QS_MOUSEBUTTON); + const auto res = WaitForSingleObject(handle, 50); switch (res) { @@ -70,7 +66,6 @@ std::optional singleWait(HANDLE handle, DWORD pid) } case WAIT_TIMEOUT: - case WAIT_EVENT: { // still running return {}; @@ -216,10 +211,6 @@ std::optional timedWait( // the process is still running - // keep processing events so the app doesn't appear dead - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); - // check the lock widget switch (ls.result()) { @@ -263,7 +254,7 @@ std::optional timedWait( } } -ProcessRunner::Results waitForProcesses( +ProcessRunner::Results waitForProcessesThreadImpl( const std::vector& initialProcesses, LockWidget::Session& ls) { using namespace std::chrono; @@ -322,6 +313,35 @@ ProcessRunner::Results waitForProcesses( } } +void waitForProcessesThread( + ProcessRunner::Results& result, + const std::vector& initialProcesses, LockWidget::Session& ls) +{ + result = waitForProcessesThreadImpl(initialProcesses, ls); + ls.unlock(); +} + +ProcessRunner::Results waitForProcesses( + const std::vector& initialProcesses, LockWidget::Session& ls) +{ + auto results = ProcessRunner::Running; + + auto* t = QThread::create( + waitForProcessesThread, std::ref(results), initialProcesses, std::ref(ls)); + + QEventLoop events; + QObject::connect(t, &QThread::finished, [&]{ + events.quit(); + }); + + t->start(); + events.exec(); + + delete t; + + return results; +} + ProcessRunner::Results waitForProcess( HANDLE initialProcess, LPDWORD exitCode, LockWidget::Session& ls) { -- cgit v1.3.1 From 4f84565085e19b6f6939783c64ebf95599f879be Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 07:20:56 -0500 Subject: renamed LockWidget to UILocker lock interface up to two processes --- src/lockwidget.cpp | 113 +++++++++++++++++++++++++++---------------------- src/lockwidget.h | 16 +++---- src/organizercore.cpp | 4 +- src/organizercore.h | 4 +- src/organizerproxy.cpp | 2 +- src/processrunner.cpp | 34 +++++++-------- src/processrunner.h | 8 ++-- 7 files changed, 95 insertions(+), 86 deletions(-) (limited to 'src') diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp index 8b654674..150d2847 100644 --- a/src/lockwidget.cpp +++ b/src/lockwidget.cpp @@ -4,12 +4,12 @@ #include #include -class LockInterface +class UILockerInterface { public: - LockInterface(QWidget* mainUI) : + UILockerInterface(QWidget* mainUI) : m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), - m_buttons(nullptr), m_reason(LockWidget::NoReason) + m_buttons(nullptr), m_reason(UILocker::NoReason) { m_timer.reset(new QTimer); QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); @@ -18,7 +18,7 @@ public: set(); } - ~LockInterface() + ~UILockerInterface() { } @@ -71,17 +71,28 @@ public: return true; } - void update(LockWidget::Reasons reason) + void update(UILocker::Reasons reason) { m_reason = reason; updateMessage(reason); updateButtons(reason); - setInfo(m_infoText); + setInfo(m_labels); } - void setInfo(const QString& s) + void setInfo(const QStringList& labels) { - m_infoText = s; + const int MaxLabels = 2; + + m_labels = labels; + + QString s; + + if (labels.size() > MaxLabels) { + s = labels.mid(0, MaxLabels).join(", ") + "..."; + } else { + s = labels.join(", "); + } + m_info->setText(s); } @@ -121,10 +132,10 @@ private: std::unique_ptr m_topLevel; QLabel* m_message; QLabel* m_info; - QString m_infoText; + QStringList m_labels; QWidget* m_buttons; std::unique_ptr m_filter; - LockWidget::Reasons m_reason; + UILocker::Reasons m_reason; bool hasMainUI() const @@ -206,6 +217,7 @@ private: void createMessageLabel() { m_message = new QLabel; + m_message->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); } void createInfoLabel() @@ -221,11 +233,11 @@ private: } - void updateMessage(LockWidget::Reasons reason) + void updateMessage(UILocker::Reasons reason) { switch (reason) { - case LockWidget::LockUI: + case UILocker::LockUI: { QString s; @@ -241,7 +253,7 @@ private: break; } - case LockWidget::OutputRequired: + case UILocker::OutputRequired: { m_message->setText(QObject::tr( "The application must run to completion because its output is " @@ -250,7 +262,7 @@ private: break; } - case LockWidget::PreventExit: + case UILocker::PreventExit: { m_message->setText(QObject::tr( "Mod Organizer is waiting on application to close before exiting.")); @@ -260,20 +272,20 @@ private: } } - void updateButtons(LockWidget::Reasons reason) + void updateButtons(UILocker::Reasons reason) { MOBase::deleteChildWidgets(m_buttons); auto* ly = m_buttons->layout(); switch (reason) { - case LockWidget::LockUI: // fall-through - case LockWidget::OutputRequired: + case UILocker::LockUI: // fall-through + case UILocker::OutputRequired: { auto* unlock = new QPushButton(QObject::tr("Unlock")); QObject::connect(unlock, &QPushButton::clicked, [&]{ - LockWidget::instance().onForceUnlock(); + UILocker::instance().onForceUnlock(); }); ly->addWidget(unlock); @@ -281,18 +293,18 @@ private: break; } - case LockWidget::PreventExit: + case UILocker::PreventExit: { auto* exit = new QPushButton(QObject::tr("Exit Now")); QObject::connect(exit, &QPushButton::clicked, [&]{ - LockWidget::instance().onForceUnlock(); + UILocker::instance().onForceUnlock(); }); ly->addWidget(exit); auto* cancel = new QPushButton(QObject::tr("Cancel")); QObject::connect(cancel, &QPushButton::clicked, [&]{ - LockWidget::instance().onCancel(); + UILocker::instance().onCancel(); }); ly->addWidget(cancel); @@ -303,19 +315,19 @@ private: } }; -LockWidget::Session::~Session() +UILocker::Session::~Session() { unlock(); } -void LockWidget::Session::unlock() +void UILocker::Session::unlock() { QMetaObject::invokeMethod(qApp, [this]{ - LockWidget::instance().unlock(this); + UILocker::instance().unlock(this); }); } -void LockWidget::Session::setInfo(DWORD pid, const QString& name) +void UILocker::Session::setInfo(DWORD pid, const QString& name) { { std::scoped_lock lock(m_mutex); @@ -324,39 +336,39 @@ void LockWidget::Session::setInfo(DWORD pid, const QString& name) } QMetaObject::invokeMethod(qApp, [this]{ - LockWidget::instance().updateLabel(); + UILocker::instance().updateLabel(); }); } -DWORD LockWidget::Session::pid() const +DWORD UILocker::Session::pid() const { std::scoped_lock lock(m_mutex); return m_pid; } -const QString& LockWidget::Session::name() const +const QString& UILocker::Session::name() const { std::scoped_lock lock(m_mutex); return m_name; } -LockWidget::Results LockWidget::Session::result() const +UILocker::Results UILocker::Session::result() const { - return LockWidget::instance().result(); + return UILocker::instance().result(); } -static LockWidget* g_instance = nullptr; +static UILocker* g_instance = nullptr; -LockWidget::LockWidget() +UILocker::UILocker() : m_parent(nullptr), m_result(NoResult) { Q_ASSERT(!g_instance); g_instance = this; } -LockWidget::~LockWidget() +UILocker::~UILocker() { const auto v = m_sessions; @@ -367,18 +379,18 @@ LockWidget::~LockWidget() } } -LockWidget& LockWidget::instance() +UILocker& UILocker::instance() { Q_ASSERT(g_instance); return *g_instance; } -void LockWidget::setUserInterface(QWidget* parent) +void UILocker::setUserInterface(QWidget* parent) { m_parent = parent; } -std::shared_ptr LockWidget::lock(Reasons reason) +std::shared_ptr UILocker::lock(Reasons reason) { m_result = StillLocked; createUi(reason); @@ -391,7 +403,7 @@ std::shared_ptr LockWidget::lock(Reasons reason) return ls; } -void LockWidget::unlock(Session* s) +void UILocker::unlock(Session* s) { auto itor = m_sessions.begin(); for (;;) { @@ -420,7 +432,7 @@ void LockWidget::unlock(Session* s) } } -void LockWidget::unlockCurrent() +void UILocker::unlockCurrent() { if (m_sessions.empty()) { return; @@ -435,29 +447,28 @@ void LockWidget::unlockCurrent() unlock(s.get()); } -void LockWidget::updateLabel() +void UILocker::updateLabel() { - QString label; + QStringList labels; for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { if (auto ss=itor->lock()) { - label += QString("%1 (%2)").arg(ss->name()).arg(ss->pid()); - break; + labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid())); } } - m_ui->setInfo(label); + m_ui->setInfo(labels); } -LockWidget::Results LockWidget::result() const +UILocker::Results UILocker::result() const { return m_result; } -void LockWidget::createUi(Reasons reason) +void UILocker::createUi(Reasons reason) { if (!m_ui) { - m_ui.reset(new LockInterface(m_parent)); + m_ui.reset(new UILockerInterface(m_parent)); } m_ui->update(reason); @@ -465,13 +476,13 @@ void LockWidget::createUi(Reasons reason) disableAll(); } -void LockWidget::onForceUnlock() +void UILocker::onForceUnlock() { m_result = ForceUnlocked; unlockCurrent(); } -void LockWidget::onCancel() +void UILocker::onCancel() { m_result = Cancelled; unlockCurrent(); @@ -483,7 +494,7 @@ QList findChildrenImmediate(QWidget* parent) return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); } -void LockWidget::disableAll() +void UILocker::disableAll() { const auto topLevels = QApplication::topLevelWidgets(); @@ -517,7 +528,7 @@ void LockWidget::disableAll() } } -void LockWidget::enableAll() +void UILocker::enableAll() { for (auto w : m_disabled) { if (w) { @@ -528,7 +539,7 @@ void LockWidget::enableAll() m_disabled.clear(); } -void LockWidget::disable(QWidget* w) +void UILocker::disable(QWidget* w) { if (w->isEnabled()) { w->setEnabled(false); diff --git a/src/lockwidget.h b/src/lockwidget.h index 640d0076..d8c22999 100644 --- a/src/lockwidget.h +++ b/src/lockwidget.h @@ -3,11 +3,11 @@ #include #include -class LockInterface; +class UILockerInterface; -class LockWidget +class UILocker { - friend class LockInterface; + friend class UILockerInterface; public: // reason to show the widget @@ -62,12 +62,10 @@ public: }; - // if `reason` is not NoReason, lock() is called with it - // - LockWidget(); - ~LockWidget(); + UILocker(); + ~UILocker(); - static LockWidget& instance(); + static UILocker& instance(); void setUserInterface(QWidget* parent); @@ -77,7 +75,7 @@ public: private: QWidget* m_parent; - std::unique_ptr m_ui; + std::unique_ptr m_ui; std::vector> m_sessions; std::atomic m_result; std::vector> m_disabled; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3d4d8e4b..9ceb149e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -250,7 +250,7 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); - m_LockWidget.setUserInterface(w); + m_UILocker.setUserInterface(w); checkForUpdates(); } @@ -1797,7 +1797,7 @@ void OrganizerCore::afterRun(const QFileInfo& binary, DWORD exitCode) } ProcessRunner::Results OrganizerCore::waitForAllUSVFSProcesses( - LockWidget::Reasons reason) + UILocker::Reasons reason) { return processRunner().waitForAllUSVFSProcessesWithLock(reason); } diff --git a/src/organizercore.h b/src/organizercore.h index 7e0e4b7f..47630dc2 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -145,7 +145,7 @@ public: void afterRun(const QFileInfo& binary, DWORD exitCode); ProcessRunner::Results waitForAllUSVFSProcesses( - LockWidget::Reasons reason=LockWidget::PreventExit); + UILocker::Reasons reason=UILocker::PreventExit); void refreshESPList(bool force = false); void refreshBSAList(); @@ -344,7 +344,7 @@ private: MOBase::DelayedFileWriter m_PluginListsWriter; UsvfsConnector m_USVFS; - LockWidget m_LockWidget; + UILocker m_UILocker; static CrashDumpsType m_globalCrashDumpsType; }; diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 420e2d82..613de742 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -144,7 +144,7 @@ bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const auto runner = m_Proxied->processRunner(); const auto r = runner - .setWaitForCompletion(ProcessRunner::ForceWait, LockWidget::OutputRequired) + .setWaitForCompletion(ProcessRunner::ForceWait, UILocker::OutputRequired) .attachToProcess(handle); if (exitCode) { diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 68acca92..cfddbb63 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -190,7 +190,7 @@ const std::chrono::milliseconds Infinite(-1); // waits for completion, times out after `wait` if not Infinite // std::optional timedWait( - HANDLE handle, DWORD pid, LockWidget::Session& ls, + HANDLE handle, DWORD pid, UILocker::Session& ls, std::chrono::milliseconds wait) { using namespace std::chrono; @@ -214,24 +214,24 @@ std::optional timedWait( // check the lock widget switch (ls.result()) { - case LockWidget::StillLocked: + case UILocker::StillLocked: { break; } - case LockWidget::ForceUnlocked: + case UILocker::ForceUnlocked: { log::debug("waiting for {} force unlocked by user", pid); return ProcessRunner::ForceUnlocked; } - case LockWidget::Cancelled: + case UILocker::Cancelled: { log::debug("waiting for {} cancelled by user", pid); return ProcessRunner::Cancelled; } - case LockWidget::NoResult: // fall-through + case UILocker::NoResult: // fall-through default: { // shouldn't happen @@ -255,7 +255,7 @@ std::optional timedWait( } ProcessRunner::Results waitForProcessesThreadImpl( - const std::vector& initialProcesses, LockWidget::Session& ls) + const std::vector& initialProcesses, UILocker::Session& ls) { using namespace std::chrono; @@ -315,14 +315,14 @@ ProcessRunner::Results waitForProcessesThreadImpl( void waitForProcessesThread( ProcessRunner::Results& result, - const std::vector& initialProcesses, LockWidget::Session& ls) + const std::vector& initialProcesses, UILocker::Session& ls) { result = waitForProcessesThreadImpl(initialProcesses, ls); ls.unlock(); } ProcessRunner::Results waitForProcesses( - const std::vector& initialProcesses, LockWidget::Session& ls) + const std::vector& initialProcesses, UILocker::Session& ls) { auto results = ProcessRunner::Running; @@ -343,7 +343,7 @@ ProcessRunner::Results waitForProcesses( } ProcessRunner::Results waitForProcess( - HANDLE initialProcess, LPDWORD exitCode, LockWidget::Session& ls) + HANDLE initialProcess, LPDWORD exitCode, UILocker::Session& ls) { std::vector processes = {initialProcess}; @@ -364,7 +364,7 @@ ProcessRunner::Results waitForProcess( ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : - m_core(core), m_ui(ui), m_lockReason(LockWidget::NoReason), + m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { // all processes started in ProcessRunner are hooked @@ -414,7 +414,7 @@ ProcessRunner& ProcessRunner::setProfileName(const QString& profileName) } ProcessRunner& ProcessRunner::setWaitForCompletion( - WaitFlags flags, LockWidget::Reasons reason) + WaitFlags flags, UILocker::Reasons reason) { m_waitFlags = flags; m_lockReason = reason; @@ -670,13 +670,13 @@ ProcessRunner::Results ProcessRunner::postRun() { const bool mustWait = (m_waitFlags & ForceWait); - if (mustWait && m_lockReason == LockWidget::NoReason) { + if (mustWait && m_lockReason == UILocker::NoReason) { // never lock the ui without an escape hatch for the user log::debug( "the ForceWait flag is set but the lock reason wasn't, " "defaulting to LockUI"); - m_lockReason = LockWidget::LockUI; + m_lockReason = UILocker::LockUI; } if (mustWait) { @@ -689,7 +689,7 @@ ProcessRunner::Results ProcessRunner::postRun() } else { // no force wait - if (m_lockReason == LockWidget::NoReason) { + if (m_lockReason == UILocker::NoReason) { // no locking requested return Running; } @@ -750,7 +750,7 @@ env::HandlePtr ProcessRunner::stealProcessHandle() } ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( - LockWidget::Reasons reason) + UILocker::Reasons reason) { m_lockReason = reason; @@ -784,8 +784,8 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( return r; } -void ProcessRunner::withLock(std::function f) +void ProcessRunner::withLock(std::function f) { - auto ls = LockWidget::instance().lock(m_lockReason); + auto ls = UILocker::instance().lock(m_lockReason); f(*ls); } diff --git a/src/processrunner.h b/src/processrunner.h index 276c46b1..af5cd416 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -65,7 +65,7 @@ public: ProcessRunner& setForcedLibraries(const ForcedLibraries& forcedLibraries); ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( - WaitFlags flags=NoFlags, LockWidget::Reasons reason=LockWidget::LockUI); + WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); // if the target is an executable file, runs that; for anything else, calls // ShellExecute() on it @@ -134,7 +134,7 @@ public: // running a process, but it uses the same internal stuff as when running a // process // - Results waitForAllUSVFSProcessesWithLock(LockWidget::Reasons reason); + Results waitForAllUSVFSProcessesWithLock(UILocker::Reasons reason); private: OrganizerCore& m_core; @@ -143,7 +143,7 @@ private: QString m_customOverwrite; ForcedLibraries m_forcedLibraries; QString m_profileName; - LockWidget::Reasons m_lockReason; + UILocker::Reasons m_lockReason; WaitFlags m_waitFlags; QString m_shellOpen; env::HandlePtr m_handle; @@ -164,7 +164,7 @@ private: // creates the lock widget and calls f() // - void withLock(std::function f); + void withLock(std::function f); }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessRunner::WaitFlags); -- cgit v1.3.1 From decd5c1828f495be4e230c9fc6fb79dd9bfdfb81 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 6 Nov 2019 07:28:11 -0500 Subject: renamed lockwidget files to uilocker --- src/CMakeLists.txt | 6 +- src/iuserinterface.h | 1 - src/lockwidget.cpp | 548 --------------------------------------------------- src/lockwidget.h | 95 --------- src/organizercore.h | 2 +- src/processrunner.h | 2 +- src/uilocker.cpp | 548 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/uilocker.h | 95 +++++++++ 8 files changed, 648 insertions(+), 649 deletions(-) delete mode 100644 src/lockwidget.cpp delete mode 100644 src/lockwidget.h create mode 100644 src/uilocker.cpp create mode 100644 src/uilocker.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 06f2cd1e..d935981d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,7 +141,7 @@ SET(organizer_SRCS colortable.cpp sanitychecks.cpp processrunner.cpp - lockwidget.cpp + uilocker.cpp shared/windows_error.cpp shared/error_report.cpp @@ -262,7 +262,7 @@ SET(organizer_HDRS envwindows.h colortable.h processrunner.h - lockwidget.h + uilocker.h shared/windows_error.h shared/error_report.h @@ -344,6 +344,7 @@ set(core organizerproxy apiuseraccount processrunner + uilocker ) set(dialogs @@ -473,7 +474,6 @@ set(widgets filterwidget icondelegate lcdnumber - lockwidget loglist loghighlighter modflagicondelegate diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 99caceb1..aa48194f 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -3,7 +3,6 @@ #include "modinfodialogfwd.h" -#include "lockwidget.h" #include #include #include diff --git a/src/lockwidget.cpp b/src/lockwidget.cpp deleted file mode 100644 index 150d2847..00000000 --- a/src/lockwidget.cpp +++ /dev/null @@ -1,548 +0,0 @@ -#include "lockwidget.h" -#include "mainwindow.h" -#include -#include -#include - -class UILockerInterface -{ -public: - UILockerInterface(QWidget* mainUI) : - m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), - m_buttons(nullptr), m_reason(UILocker::NoReason) - { - m_timer.reset(new QTimer); - QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); - m_timer->start(200); - - set(); - } - - ~UILockerInterface() - { - } - - void checkTarget() - { - if (set()) { - update(m_reason); - } - } - - bool set() - { - QWidget* newTarget = nullptr; - - newTarget = m_mainUI; - if (auto* w = QApplication::activeModalWidget()) { - newTarget = w; - } - - if (newTarget == m_target) { - return false; - } - - m_target = newTarget; - - QFrame* center = nullptr; - - if (m_target) { - center = createOverlay(m_target); - } else { - center = createDialog(); - } - - createMessageLabel(); - createInfoLabel(); - createButtonsPanel(); - - center->layout()->addWidget(m_message); - center->layout()->addWidget(m_info); - center->layout()->addWidget(m_buttons); - - m_topLevel->setFocusPolicy(Qt::TabFocus); - m_topLevel->setFocus(); - m_topLevel->show(); - m_topLevel->setEnabled(true); - - m_topLevel->raise(); - m_topLevel->activateWindow(); - - return true; - } - - void update(UILocker::Reasons reason) - { - m_reason = reason; - updateMessage(reason); - updateButtons(reason); - setInfo(m_labels); - } - - void setInfo(const QStringList& labels) - { - const int MaxLabels = 2; - - m_labels = labels; - - QString s; - - if (labels.size() > MaxLabels) { - s = labels.mid(0, MaxLabels).join(", ") + "..."; - } else { - s = labels.join(", "); - } - - m_info->setText(s); - } - - QWidget* topLevel() - { - return m_topLevel.get(); - } - -private: - class Filter : public QObject - { - public: - std::function resized; - std::function closed; - - protected: - bool eventFilter(QObject* o, QEvent* e) override - { - if (e->type() == QEvent::Resize) { - if (resized) { - resized(); - } - } else if (e->type() == QEvent::Close) { - if (closed) { - closed(); - } - } - - return QObject::eventFilter(o, e); - } - }; - - - std::unique_ptr m_timer; - QWidget* m_mainUI; - QWidget* m_target; - std::unique_ptr m_topLevel; - QLabel* m_message; - QLabel* m_info; - QStringList m_labels; - QWidget* m_buttons; - std::unique_ptr m_filter; - UILocker::Reasons m_reason; - - - bool hasMainUI() const - { - return (m_target != nullptr); - } - - QWidget* createTransparentWidget(QWidget* parent=nullptr) - { - auto* w = new QWidget(parent); - - w->setWindowOpacity(0); - w->setAttribute(Qt::WA_NoSystemBackground); - w->setAttribute(Qt::WA_TranslucentBackground); - - return w; - } - - QFrame* createOverlay(QWidget* mainUI) - { - m_topLevel.reset(createTransparentWidget(mainUI)); - m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); - m_topLevel->setGeometry(mainUI->rect()); - - m_filter.reset(new Filter); - m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; - m_filter->closed = [=]{ checkTarget(); }; - - mainUI->installEventFilter(m_filter.get()); - - return createFrame(); - } - - QFrame* createDialog() - { - m_topLevel.reset(new QDialog); - - return createFrame(); - } - - QFrame* createFrame() - { - auto* frame = new QFrame; - auto* ly = new QVBoxLayout(frame); - - if (hasMainUI()) { - frame->setFrameStyle(QFrame::StyledPanel); - frame->setLineWidth(1); - frame->setAutoFillBackground(true); - - auto* shadow = new QGraphicsDropShadowEffect; - shadow->setBlurRadius(50); - shadow->setOffset(0); - shadow->setColor(QColor(0, 0, 0, 100)); - frame->setGraphicsEffect(shadow); - } else { - ly->setContentsMargins(0, 0, 0, 0); - } - - auto* grid = new QGridLayout(m_topLevel.get()); - grid->addWidget(createTransparentWidget(), 0, 1); - grid->addWidget(createTransparentWidget(), 2, 1); - grid->addWidget(createTransparentWidget(), 1, 0); - grid->addWidget(createTransparentWidget(), 1, 2); - grid->addWidget(frame, 1, 1); - - if (!hasMainUI()) { - grid->setContentsMargins(0, 0, 0, 0); - } - - grid->setRowStretch(0, 1); - grid->setRowStretch(2, 1); - grid->setColumnStretch(0, 1); - grid->setColumnStretch(2, 1); - - return frame; - } - - void createMessageLabel() - { - m_message = new QLabel; - m_message->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); - } - - void createInfoLabel() - { - m_info = new QLabel(" "); - m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); - } - - void createButtonsPanel() - { - m_buttons = new QWidget; - m_buttons->setLayout(new QHBoxLayout); - } - - - void updateMessage(UILocker::Reasons reason) - { - switch (reason) - { - case UILocker::LockUI: - { - QString s; - - if (hasMainUI()) { - s = QObject::tr( - "Mod Organizer is locked while the application is running."); - } else { - s = QObject::tr("Mod Organizer is currently running an application."); - } - - m_message->setText(s); - - break; - } - - case UILocker::OutputRequired: - { - m_message->setText(QObject::tr( - "The application must run to completion because its output is " - "required.")); - - break; - } - - case UILocker::PreventExit: - { - m_message->setText(QObject::tr( - "Mod Organizer is waiting on application to close before exiting.")); - - break; - } - } - } - - void updateButtons(UILocker::Reasons reason) - { - MOBase::deleteChildWidgets(m_buttons); - auto* ly = m_buttons->layout(); - - switch (reason) - { - case UILocker::LockUI: // fall-through - case UILocker::OutputRequired: - { - auto* unlock = new QPushButton(QObject::tr("Unlock")); - - QObject::connect(unlock, &QPushButton::clicked, [&]{ - UILocker::instance().onForceUnlock(); - }); - - ly->addWidget(unlock); - - break; - } - - case UILocker::PreventExit: - { - auto* exit = new QPushButton(QObject::tr("Exit Now")); - QObject::connect(exit, &QPushButton::clicked, [&]{ - UILocker::instance().onForceUnlock(); - }); - - ly->addWidget(exit); - - auto* cancel = new QPushButton(QObject::tr("Cancel")); - QObject::connect(cancel, &QPushButton::clicked, [&]{ - UILocker::instance().onCancel(); - }); - - ly->addWidget(cancel); - - break; - } - } - } -}; - -UILocker::Session::~Session() -{ - unlock(); -} - -void UILocker::Session::unlock() -{ - QMetaObject::invokeMethod(qApp, [this]{ - UILocker::instance().unlock(this); - }); -} - -void UILocker::Session::setInfo(DWORD pid, const QString& name) -{ - { - std::scoped_lock lock(m_mutex); - m_pid = pid; - m_name = name; - } - - QMetaObject::invokeMethod(qApp, [this]{ - UILocker::instance().updateLabel(); - }); -} - -DWORD UILocker::Session::pid() const -{ - std::scoped_lock lock(m_mutex); - return m_pid; -} - -const QString& UILocker::Session::name() const -{ - std::scoped_lock lock(m_mutex); - return m_name; -} - -UILocker::Results UILocker::Session::result() const -{ - return UILocker::instance().result(); -} - - -static UILocker* g_instance = nullptr; - - -UILocker::UILocker() - : m_parent(nullptr), m_result(NoResult) -{ - Q_ASSERT(!g_instance); - g_instance = this; -} - -UILocker::~UILocker() -{ - const auto v = m_sessions; - - for (auto& wp : v) { - if (auto s=wp.lock()) { - unlock(s.get()); - } - } -} - -UILocker& UILocker::instance() -{ - Q_ASSERT(g_instance); - return *g_instance; -} - -void UILocker::setUserInterface(QWidget* parent) -{ - m_parent = parent; -} - -std::shared_ptr UILocker::lock(Reasons reason) -{ - m_result = StillLocked; - createUi(reason); - - auto ls = std::make_shared(); - m_sessions.push_back(ls); - - updateLabel(); - - return ls; -} - -void UILocker::unlock(Session* s) -{ - auto itor = m_sessions.begin(); - for (;;) { - if (itor == m_sessions.end()) { - break; - } - - if (auto ss=itor->lock()) { - if (ss.get() == s) { - itor = m_sessions.erase(itor); - continue; - } - } else { - itor = m_sessions.erase(itor); - continue; - } - - ++itor; - } - - if (m_sessions.empty()) { - m_ui.reset(); - enableAll(); - } else { - updateLabel(); - } -} - -void UILocker::unlockCurrent() -{ - if (m_sessions.empty()) { - return; - } - - auto s = m_sessions.back().lock(); - if (!s) { - m_sessions.pop_back(); - return; - } - - unlock(s.get()); -} - -void UILocker::updateLabel() -{ - QStringList labels; - - for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { - if (auto ss=itor->lock()) { - labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid())); - } - } - - m_ui->setInfo(labels); -} - -UILocker::Results UILocker::result() const -{ - return m_result; -} - -void UILocker::createUi(Reasons reason) -{ - if (!m_ui) { - m_ui.reset(new UILockerInterface(m_parent)); - } - - m_ui->update(reason); - - disableAll(); -} - -void UILocker::onForceUnlock() -{ - m_result = ForceUnlocked; - unlockCurrent(); -} - -void UILocker::onCancel() -{ - m_result = Cancelled; - unlockCurrent(); -} - -template -QList findChildrenImmediate(QWidget* parent) -{ - return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); -} - -void UILocker::disableAll() -{ - const auto topLevels = QApplication::topLevelWidgets(); - - for (auto* w : topLevels) { - if (auto* mw=dynamic_cast(w)) { - disable(mw->centralWidget()); - disable(mw->menuBar()); - disable(mw->statusBar()); - - for (auto* tb : findChildrenImmediate(w)) { - disable(tb); - } - - for (auto* d : findChildrenImmediate(w)) { - disable(d); - } - } - - if (auto* d=dynamic_cast(w)) { - // don't disable stuff if this dialog is the overlay, which happens when - // there's no ui - if (d != m_ui->topLevel()) { - // no central widget, just disable the children, except for the overlay - for (auto* child : findChildrenImmediate(d)) { - if (child != m_ui->topLevel()) { - disable(child); - } - } - } - } - } -} - -void UILocker::enableAll() -{ - for (auto w : m_disabled) { - if (w) { - w->setEnabled(true); - } - } - - m_disabled.clear(); -} - -void UILocker::disable(QWidget* w) -{ - if (w->isEnabled()) { - w->setEnabled(false); - m_disabled.push_back(w); - } -} diff --git a/src/lockwidget.h b/src/lockwidget.h deleted file mode 100644 index d8c22999..00000000 --- a/src/lockwidget.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include - -class UILockerInterface; - -class UILocker -{ - friend class UILockerInterface; - -public: - // reason to show the widget - // - enum Reasons - { - NoReason = 0, - - // lock the ui - LockUI, - - // because the output is required - OutputRequired, - - // to prevent exiting until all processes are completed - PreventExit - }; - - // returned by result() - // - enum Results - { - NoResult = 0, - - // the widget is still up - StillLocked, - - // force unlock was clicked - ForceUnlocked, - - // cancel was clicked - Cancelled - }; - - - class Session - { - public: - ~Session(); - - void unlock(); - void setInfo(DWORD pid, const QString& name); - Results result() const; - - DWORD pid() const; - const QString& name() const; - - private: - mutable std::mutex m_mutex; - DWORD m_pid; - QString m_name; - }; - - - UILocker(); - ~UILocker(); - - static UILocker& instance(); - - void setUserInterface(QWidget* parent); - - std::shared_ptr lock(Reasons reason); - - Results result() const; - -private: - QWidget* m_parent; - std::unique_ptr m_ui; - std::vector> m_sessions; - std::atomic m_result; - std::vector> m_disabled; - - void createUi(Reasons reason); - - void unlockCurrent(); - void unlock(Session* s); - void updateLabel(); - - void onForceUnlock(); - void onCancel(); - - void disableAll(); - void enableAll(); - void disable(QWidget* w); -}; diff --git a/src/organizercore.h b/src/organizercore.h index 47630dc2..6c9edb9f 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -14,7 +14,7 @@ #include "usvfsconnector.h" #include "moshortcut.h" #include "processrunner.h" -#include "lockwidget.h" +#include "uilocker.h" #include #include #include diff --git a/src/processrunner.h b/src/processrunner.h index af5cd416..c61d6b70 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -2,7 +2,7 @@ #define PROCESSRUNNER_H #include "spawn.h" -#include "lockwidget.h" +#include "uilocker.h" #include "envmodule.h" #include diff --git a/src/uilocker.cpp b/src/uilocker.cpp new file mode 100644 index 00000000..01794d6b --- /dev/null +++ b/src/uilocker.cpp @@ -0,0 +1,548 @@ +#include "uilocker.h" +#include "mainwindow.h" +#include +#include +#include + +class UILockerInterface +{ +public: + UILockerInterface(QWidget* mainUI) : + m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), + m_buttons(nullptr), m_reason(UILocker::NoReason) + { + m_timer.reset(new QTimer); + QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); + m_timer->start(200); + + set(); + } + + ~UILockerInterface() + { + } + + void checkTarget() + { + if (set()) { + update(m_reason); + } + } + + bool set() + { + QWidget* newTarget = nullptr; + + newTarget = m_mainUI; + if (auto* w = QApplication::activeModalWidget()) { + newTarget = w; + } + + if (newTarget == m_target) { + return false; + } + + m_target = newTarget; + + QFrame* center = nullptr; + + if (m_target) { + center = createOverlay(m_target); + } else { + center = createDialog(); + } + + createMessageLabel(); + createInfoLabel(); + createButtonsPanel(); + + center->layout()->addWidget(m_message); + center->layout()->addWidget(m_info); + center->layout()->addWidget(m_buttons); + + m_topLevel->setFocusPolicy(Qt::TabFocus); + m_topLevel->setFocus(); + m_topLevel->show(); + m_topLevel->setEnabled(true); + + m_topLevel->raise(); + m_topLevel->activateWindow(); + + return true; + } + + void update(UILocker::Reasons reason) + { + m_reason = reason; + updateMessage(reason); + updateButtons(reason); + setInfo(m_labels); + } + + void setInfo(const QStringList& labels) + { + const int MaxLabels = 2; + + m_labels = labels; + + QString s; + + if (labels.size() > MaxLabels) { + s = labels.mid(0, MaxLabels).join(", ") + "..."; + } else { + s = labels.join(", "); + } + + m_info->setText(s); + } + + QWidget* topLevel() + { + return m_topLevel.get(); + } + +private: + class Filter : public QObject + { + public: + std::function resized; + std::function closed; + + protected: + bool eventFilter(QObject* o, QEvent* e) override + { + if (e->type() == QEvent::Resize) { + if (resized) { + resized(); + } + } else if (e->type() == QEvent::Close) { + if (closed) { + closed(); + } + } + + return QObject::eventFilter(o, e); + } + }; + + + std::unique_ptr m_timer; + QWidget* m_mainUI; + QWidget* m_target; + std::unique_ptr m_topLevel; + QLabel* m_message; + QLabel* m_info; + QStringList m_labels; + QWidget* m_buttons; + std::unique_ptr m_filter; + UILocker::Reasons m_reason; + + + bool hasMainUI() const + { + return (m_target != nullptr); + } + + QWidget* createTransparentWidget(QWidget* parent=nullptr) + { + auto* w = new QWidget(parent); + + w->setWindowOpacity(0); + w->setAttribute(Qt::WA_NoSystemBackground); + w->setAttribute(Qt::WA_TranslucentBackground); + + return w; + } + + QFrame* createOverlay(QWidget* mainUI) + { + m_topLevel.reset(createTransparentWidget(mainUI)); + m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); + m_topLevel->setGeometry(mainUI->rect()); + + m_filter.reset(new Filter); + m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; + m_filter->closed = [=]{ checkTarget(); }; + + mainUI->installEventFilter(m_filter.get()); + + return createFrame(); + } + + QFrame* createDialog() + { + m_topLevel.reset(new QDialog); + + return createFrame(); + } + + QFrame* createFrame() + { + auto* frame = new QFrame; + auto* ly = new QVBoxLayout(frame); + + if (hasMainUI()) { + frame->setFrameStyle(QFrame::StyledPanel); + frame->setLineWidth(1); + frame->setAutoFillBackground(true); + + auto* shadow = new QGraphicsDropShadowEffect; + shadow->setBlurRadius(50); + shadow->setOffset(0); + shadow->setColor(QColor(0, 0, 0, 100)); + frame->setGraphicsEffect(shadow); + } else { + ly->setContentsMargins(0, 0, 0, 0); + } + + auto* grid = new QGridLayout(m_topLevel.get()); + grid->addWidget(createTransparentWidget(), 0, 1); + grid->addWidget(createTransparentWidget(), 2, 1); + grid->addWidget(createTransparentWidget(), 1, 0); + grid->addWidget(createTransparentWidget(), 1, 2); + grid->addWidget(frame, 1, 1); + + if (!hasMainUI()) { + grid->setContentsMargins(0, 0, 0, 0); + } + + grid->setRowStretch(0, 1); + grid->setRowStretch(2, 1); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(2, 1); + + return frame; + } + + void createMessageLabel() + { + m_message = new QLabel; + m_message->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + } + + void createInfoLabel() + { + m_info = new QLabel(" "); + m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); + } + + void createButtonsPanel() + { + m_buttons = new QWidget; + m_buttons->setLayout(new QHBoxLayout); + } + + + void updateMessage(UILocker::Reasons reason) + { + switch (reason) + { + case UILocker::LockUI: + { + QString s; + + if (hasMainUI()) { + s = QObject::tr( + "Mod Organizer is locked while the application is running."); + } else { + s = QObject::tr("Mod Organizer is currently running an application."); + } + + m_message->setText(s); + + break; + } + + case UILocker::OutputRequired: + { + m_message->setText(QObject::tr( + "The application must run to completion because its output is " + "required.")); + + break; + } + + case UILocker::PreventExit: + { + m_message->setText(QObject::tr( + "Mod Organizer is waiting on application to close before exiting.")); + + break; + } + } + } + + void updateButtons(UILocker::Reasons reason) + { + MOBase::deleteChildWidgets(m_buttons); + auto* ly = m_buttons->layout(); + + switch (reason) + { + case UILocker::LockUI: // fall-through + case UILocker::OutputRequired: + { + auto* unlock = new QPushButton(QObject::tr("Unlock")); + + QObject::connect(unlock, &QPushButton::clicked, [&]{ + UILocker::instance().onForceUnlock(); + }); + + ly->addWidget(unlock); + + break; + } + + case UILocker::PreventExit: + { + auto* exit = new QPushButton(QObject::tr("Exit Now")); + QObject::connect(exit, &QPushButton::clicked, [&]{ + UILocker::instance().onForceUnlock(); + }); + + ly->addWidget(exit); + + auto* cancel = new QPushButton(QObject::tr("Cancel")); + QObject::connect(cancel, &QPushButton::clicked, [&]{ + UILocker::instance().onCancel(); + }); + + ly->addWidget(cancel); + + break; + } + } + } +}; + +UILocker::Session::~Session() +{ + unlock(); +} + +void UILocker::Session::unlock() +{ + QMetaObject::invokeMethod(qApp, [this]{ + UILocker::instance().unlock(this); + }); +} + +void UILocker::Session::setInfo(DWORD pid, const QString& name) +{ + { + std::scoped_lock lock(m_mutex); + m_pid = pid; + m_name = name; + } + + QMetaObject::invokeMethod(qApp, [this]{ + UILocker::instance().updateLabel(); + }); +} + +DWORD UILocker::Session::pid() const +{ + std::scoped_lock lock(m_mutex); + return m_pid; +} + +const QString& UILocker::Session::name() const +{ + std::scoped_lock lock(m_mutex); + return m_name; +} + +UILocker::Results UILocker::Session::result() const +{ + return UILocker::instance().result(); +} + + +static UILocker* g_instance = nullptr; + + +UILocker::UILocker() + : m_parent(nullptr), m_result(NoResult) +{ + Q_ASSERT(!g_instance); + g_instance = this; +} + +UILocker::~UILocker() +{ + const auto v = m_sessions; + + for (auto& wp : v) { + if (auto s=wp.lock()) { + unlock(s.get()); + } + } +} + +UILocker& UILocker::instance() +{ + Q_ASSERT(g_instance); + return *g_instance; +} + +void UILocker::setUserInterface(QWidget* parent) +{ + m_parent = parent; +} + +std::shared_ptr UILocker::lock(Reasons reason) +{ + m_result = StillLocked; + createUi(reason); + + auto ls = std::make_shared(); + m_sessions.push_back(ls); + + updateLabel(); + + return ls; +} + +void UILocker::unlock(Session* s) +{ + auto itor = m_sessions.begin(); + for (;;) { + if (itor == m_sessions.end()) { + break; + } + + if (auto ss=itor->lock()) { + if (ss.get() == s) { + itor = m_sessions.erase(itor); + continue; + } + } else { + itor = m_sessions.erase(itor); + continue; + } + + ++itor; + } + + if (m_sessions.empty()) { + m_ui.reset(); + enableAll(); + } else { + updateLabel(); + } +} + +void UILocker::unlockCurrent() +{ + if (m_sessions.empty()) { + return; + } + + auto s = m_sessions.back().lock(); + if (!s) { + m_sessions.pop_back(); + return; + } + + unlock(s.get()); +} + +void UILocker::updateLabel() +{ + QStringList labels; + + for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { + if (auto ss=itor->lock()) { + labels.push_back(QString("%1 (%2)").arg(ss->name()).arg(ss->pid())); + } + } + + m_ui->setInfo(labels); +} + +UILocker::Results UILocker::result() const +{ + return m_result; +} + +void UILocker::createUi(Reasons reason) +{ + if (!m_ui) { + m_ui.reset(new UILockerInterface(m_parent)); + } + + m_ui->update(reason); + + disableAll(); +} + +void UILocker::onForceUnlock() +{ + m_result = ForceUnlocked; + unlockCurrent(); +} + +void UILocker::onCancel() +{ + m_result = Cancelled; + unlockCurrent(); +} + +template +QList findChildrenImmediate(QWidget* parent) +{ + return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); +} + +void UILocker::disableAll() +{ + const auto topLevels = QApplication::topLevelWidgets(); + + for (auto* w : topLevels) { + if (auto* mw=dynamic_cast(w)) { + disable(mw->centralWidget()); + disable(mw->menuBar()); + disable(mw->statusBar()); + + for (auto* tb : findChildrenImmediate(w)) { + disable(tb); + } + + for (auto* d : findChildrenImmediate(w)) { + disable(d); + } + } + + if (auto* d=dynamic_cast(w)) { + // don't disable stuff if this dialog is the overlay, which happens when + // there's no ui + if (d != m_ui->topLevel()) { + // no central widget, just disable the children, except for the overlay + for (auto* child : findChildrenImmediate(d)) { + if (child != m_ui->topLevel()) { + disable(child); + } + } + } + } + } +} + +void UILocker::enableAll() +{ + for (auto w : m_disabled) { + if (w) { + w->setEnabled(true); + } + } + + m_disabled.clear(); +} + +void UILocker::disable(QWidget* w) +{ + if (w->isEnabled()) { + w->setEnabled(false); + m_disabled.push_back(w); + } +} diff --git a/src/uilocker.h b/src/uilocker.h new file mode 100644 index 00000000..d8c22999 --- /dev/null +++ b/src/uilocker.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include + +class UILockerInterface; + +class UILocker +{ + friend class UILockerInterface; + +public: + // reason to show the widget + // + enum Reasons + { + NoReason = 0, + + // lock the ui + LockUI, + + // because the output is required + OutputRequired, + + // to prevent exiting until all processes are completed + PreventExit + }; + + // returned by result() + // + enum Results + { + NoResult = 0, + + // the widget is still up + StillLocked, + + // force unlock was clicked + ForceUnlocked, + + // cancel was clicked + Cancelled + }; + + + class Session + { + public: + ~Session(); + + void unlock(); + void setInfo(DWORD pid, const QString& name); + Results result() const; + + DWORD pid() const; + const QString& name() const; + + private: + mutable std::mutex m_mutex; + DWORD m_pid; + QString m_name; + }; + + + UILocker(); + ~UILocker(); + + static UILocker& instance(); + + void setUserInterface(QWidget* parent); + + std::shared_ptr lock(Reasons reason); + + Results result() const; + +private: + QWidget* m_parent; + std::unique_ptr m_ui; + std::vector> m_sessions; + std::atomic m_result; + std::vector> m_disabled; + + void createUi(Reasons reason); + + void unlockCurrent(); + void unlock(Session* s); + void updateLabel(); + + void onForceUnlock(); + void onCancel(); + + void disableAll(); + void enableAll(); + void disable(QWidget* w); +}; -- cgit v1.3.1 From 838446e262ac628c748b2e2b2f2ec0cfaff01508 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 19:41:17 -0500 Subject: split loot stuff to loot.h/cpp, no changes in functionality --- src/CMakeLists.txt | 3 + src/loot.cpp | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/loot.h | 10 +++ src/mainwindow.cpp | 227 +------------------------------------------------ src/mainwindow.h | 4 - 5 files changed, 261 insertions(+), 227 deletions(-) create mode 100644 src/loot.cpp create mode 100644 src/loot.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d935981d..70c501ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ SET(organizer_SRCS sanitychecks.cpp processrunner.cpp uilocker.cpp + loot.cpp shared/windows_error.cpp shared/error_report.cpp @@ -263,6 +264,7 @@ SET(organizer_HDRS colortable.h processrunner.h uilocker.h + loot.h shared/windows_error.h shared/error_report.h @@ -465,6 +467,7 @@ set(utilities shared/util usvfsconnector shared/windows_error + loot ) set(widgets diff --git a/src/loot.cpp b/src/loot.cpp new file mode 100644 index 00000000..b19c59ed --- /dev/null +++ b/src/loot.cpp @@ -0,0 +1,244 @@ +#include "spawn.h" +#include "organizercore.h" +#include +#include + +using namespace MOBase; + +void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +{ + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = nullptr; + + if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { + log::error("failed to create stdout reroute"); + } + + if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { + log::error("failed to correctly set up the stdout reroute"); + *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; + } +} + +std::string readFromPipe(HANDLE stdOutRead) +{ + static const int chunkSize = 128; + std::string result; + + char buffer[chunkSize + 1]; + buffer[chunkSize] = '\0'; + + DWORD read = 1; + while (read > 0) { + if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { + break; + } + if (read > 0) { + result.append(buffer, read); + if (read < chunkSize) { + break; + } + } + } + return result; +} + +void processLOOTOut( + OrganizerCore& core, + const std::string &lootOut, std::string &errorMessages, + QProgressDialog &dialog) +{ + std::vector lines; + boost::split(lines, lootOut, boost::is_any_of("\r\n")); + + std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); + + for (const std::string &line : lines) { + if (line.length() > 0) { + size_t progidx = line.find("[progress]"); + size_t erroridx = line.find("[error]"); + if (progidx != std::string::npos) { + dialog.setLabelText(line.substr(progidx + 11).c_str()); + } else if (erroridx != std::string::npos) { + log::warn("{}", line); + errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); + } else { + std::smatch match; + if (std::regex_match(line, match, exRequires)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + core.pluginList()->addInformation(modName.c_str(), QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); + } else if (std::regex_match(line, match, exIncompatible)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + core.pluginList()->addInformation(modName.c_str(), QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); + } else { + log::debug("[loot] {}", line); + } + } + } + } +} + +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +{ + std::string errorMessages; + + //m_OrganizerCore.currentProfile()->writeModlistNow(); + core.savePluginList(); + + //Create a backup of the load orders w/ LOOT in name + //to make sure that any sorting is easily undo-able. + //Need to figure out how I want to do that. + + bool success = false; + + try { + QProgressDialog dialog(parent); + + dialog.setLabelText(QObject::tr("Please wait while LOOT is running")); + dialog.setMaximum(0); + dialog.show(); + + QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); + + QStringList parameters; + parameters + << "--game" << core.managedGame()->gameShortName() + << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) + << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) + << "--out" << QString("\"%1\"").arg(outPath); + + if (didUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + HANDLE stdOutRead = INVALID_HANDLE_VALUE; + createStdoutPipe(&stdOutRead, &stdOutWrite); + + try { + core.prepareVFS(); + } catch (const UsvfsConnectorException &e) { + log::debug("{}", e.what()); + return false; + } catch (const std::exception &e) { + QMessageBox::warning(parent, QObject::tr("Error"), e.what()); + return false; + } + + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.hooked = true; + sp.stdOut = stdOutWrite; + + HANDLE loot = spawn::startBinary(parent, sp); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + core.pluginList()->clearAdditionalInformation(); + + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + HANDLE processHandle = loot; + + if (loot != INVALID_HANDLE_VALUE) { + bool isJobHandle = true; + ULONG lastProcessID; + DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { + if (isJobHandle) { + if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + log::debug("no more processes in job"); + break; + } else { + if (lastProcessID != info.ProcessIdList[0]) { + lastProcessID = info.ProcessIdList[0]; + if (processHandle != loot) { + ::CloseHandle(processHandle); + } + processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); + } + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + + if (dialog.wasCanceled()) { + if (isJobHandle) { + ::TerminateJobObject(loot, 1); + } else { + ::TerminateProcess(loot, 1); + } + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + std::string lootOut = readFromPipe(stdOutRead); + processLOOTOut(core, lootOut, errorMessages, dialog); + + res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + } + + std::string remainder = readFromPipe(stdOutRead).c_str(); + if (remainder.length() > 0) { + processLOOTOut(core, remainder, errorMessages, dialog); + } + + DWORD exitCode = 0UL; + ::GetExitCodeProcess(processHandle, &exitCode); + ::CloseHandle(processHandle); + if (exitCode != 0UL) { + reportError(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); + return false; + } else { + success = true; + QFile outFile(outPath); + outFile.open(QIODevice::ReadOnly); + QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); + QJsonArray array = doc.array(); + for (auto iter = array.begin(); iter != array.end(); ++iter) { + QJsonObject pluginObj = (*iter).toObject(); + QJsonArray pluginMessages = pluginObj["messages"].toArray(); + for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { + QJsonObject msg = (*msgIter).toObject(); + core.pluginList()->addInformation(pluginObj["name"].toString(), + QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); + } + if (pluginObj["dirty"].toString() == "yes") + core.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); + } + + } + } else { + reportError(QObject::tr("failed to start loot")); + } + } catch (const std::exception &e) { + reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + } + + if (errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox( + QMessageBox::Warning, QObject::tr("Errors occurred"), + errorMessages.c_str(), QMessageBox::Ok, parent); + + warn->setModal(false); + warn->show(); + } + + return success; +} diff --git a/src/loot.h b/src/loot.h new file mode 100644 index 00000000..2314e5b3 --- /dev/null +++ b/src/loot.h @@ -0,0 +1,10 @@ +#ifndef MODORGANIZER_LOOT_H +#define MODORGANIZER_LOOT_H + +#include + +class OrganizerCore; + +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + +#endif // MODORGANIZER_LOOT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9453f07c..11edfc81 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,7 +39,7 @@ along with Mod Organizer. If not, see . #include "spawn.h" #include "versioninfo.h" #include "instancemanager.h" - +#include "loot.h" #include "report.h" #include "modlist.h" #include "modlistsortproxy.h" @@ -6369,233 +6369,14 @@ void MainWindow::on_showHiddenBox_toggled(bool checked) m_OrganizerCore.downloadManager()->setShowHidden(checked); } - -void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) -{ - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = nullptr; - - if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - log::error("failed to create stdout reroute"); - } - - if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - log::error("failed to correctly set up the stdout reroute"); - *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; - } -} - -std::string MainWindow::readFromPipe(HANDLE stdOutRead) -{ - static const int chunkSize = 128; - std::string result; - - char buffer[chunkSize + 1]; - buffer[chunkSize] = '\0'; - - DWORD read = 1; - while (read > 0) { - if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { - break; - } - if (read > 0) { - result.append(buffer, read); - if (read < chunkSize) { - break; - } - } - } - return result; -} - -void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog) -{ - std::vector lines; - boost::split(lines, lootOut, boost::is_any_of("\r\n")); - - std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); - - for (const std::string &line : lines) { - if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t erroridx = line.find("[error]"); - if (progidx != std::string::npos) { - dialog.setLabelText(line.substr(progidx + 11).c_str()); - } else if (erroridx != std::string::npos) { - log::warn("{}", line); - errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); - } else { - std::smatch match; - if (std::regex_match(line, match, exRequires)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else if (std::regex_match(line, match, exIncompatible)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); - } else { - log::debug("[loot] {}", line); - } - } - } - } -} - void MainWindow::on_bossButton_clicked() { - std::string errorMessages; - - //m_OrganizerCore.currentProfile()->writeModlistNow(); m_OrganizerCore.savePluginList(); - //Create a backup of the load orders w/ LOOT in name - //to make sure that any sorting is easily undo-able. - //Need to figure out how I want to do that. - bool success = false; - - try { - setEnabled(false); - ON_BLOCK_EXIT([&] () { setEnabled(true); }); - QProgressDialog dialog(this); - dialog.setLabelText(tr("Please wait while LOOT is running")); - dialog.setMaximum(0); - dialog.show(); - - QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - - QStringList parameters; - parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); - - if (m_DidUpdateMasterList) { - parameters << "--skipUpdateMasterlist"; - } - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - try { - m_OrganizerCore.prepareVFS(); - } catch (const UsvfsConnectorException &e) { - log::debug("{}", e.what()); - return; - } catch (const std::exception &e) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); - return; - } - - spawn::SpawnParameters sp; - sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); - sp.arguments = parameters.join(" "); - sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); - sp.hooked = true; - sp.stdOut = stdOutWrite; - - HANDLE loot = spawn::startBinary(this, sp); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - m_OrganizerCore.pluginList()->clearAdditionalInformation(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = loot; - - if (loot != INVALID_HANDLE_VALUE) { - bool isJobHandle = true; - ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - log::debug("no more processes in job"); - break; - } else { - if (lastProcessID != info.ProcessIdList[0]) { - lastProcessID = info.ProcessIdList[0]; - if (processHandle != loot) { - ::CloseHandle(processHandle); - } - processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); - } - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } - - if (dialog.wasCanceled()) { - if (isJobHandle) { - ::TerminateJobObject(loot, 1); - } else { - ::TerminateProcess(loot, 1); - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(lootOut, errorMessages, dialog); - - res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - } - - std::string remainder = readFromPipe(stdOutRead).c_str(); - if (remainder.length() > 0) { - processLOOTOut(remainder, errorMessages, dialog); - } - DWORD exitCode = 0UL; - ::GetExitCodeProcess(processHandle, &exitCode); - ::CloseHandle(processHandle); - if (exitCode != 0UL) { - reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); - return; - } else { - success = true; - QFile outFile(outPath); - outFile.open(QIODevice::ReadOnly); - QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); - QJsonArray array = doc.array(); - for (auto iter = array.begin(); iter != array.end(); ++iter) { - QJsonObject pluginObj = (*iter).toObject(); - QJsonArray pluginMessages = pluginObj["messages"].toArray(); - for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { - QJsonObject msg = (*msgIter).toObject(); - m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), - QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); - } - if (pluginObj["dirty"].toString() == "yes") - m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); - } - - } - } else { - reportError(tr("failed to start loot")); - } - } catch (const std::exception &e) { - reportError(tr("failed to run loot: %1").arg(e.what())); - } - - if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occurred"), errorMessages.c_str(), QMessageBox::Ok, this); - warn->setModal(false); - warn->show(); - } + setEnabled(false); + ON_BLOCK_EXIT([&] () { setEnabled(true); }); - if (success) { + if (runLoot(this, m_OrganizerCore, m_DidUpdateMasterList)) { m_DidUpdateMasterList = true; m_OrganizerCore.refreshESPList(false); m_OrganizerCore.savePluginList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index c80287b2..7c2ee1eb 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -136,10 +136,6 @@ public: void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); - std::string readFromPipe(HANDLE stdOutRead); - void processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog); - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); QString getOriginDisplayName(int originID); -- cgit v1.3.1 From 272cb514839ebe931d735677026ee55d08bd949b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 20:34:55 -0500 Subject: added LootDialog, more flexible than QProgressDialog --- src/loot.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index b19c59ed..c0051e61 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -5,6 +5,74 @@ using namespace MOBase; + +class LootDialog : public QDialog +{ +public: + LootDialog(QWidget* parent) : + QDialog(parent), m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), + m_cancelled(false) + { + createUI(); + } + + void setText(const QString& s) + { + m_label->setText(s); + } + + void setIndeterminate() + { + m_progress->setMaximum(0); + } + + bool cancelled() const + { + return m_cancelled; + } + +private: + QLabel* m_label; + QProgressBar* m_progress; + QDialogButtonBox* m_buttons; + bool m_cancelled; + + void createUI() + { + auto* root = new QWidget(this); + auto* ly = new QVBoxLayout(root); + + setLayout(new QVBoxLayout); + layout()->setContentsMargins(0, 0, 0, 0); + layout()->addWidget(root); + + m_label = new QLabel; + ly->addWidget(m_label); + + m_progress = new QProgressBar; + ly->addWidget(m_progress); + + ly->addStretch(1); + + m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); + connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); + ly->addWidget(m_buttons); + } + + void closeEvent(QCloseEvent* e) override + { + m_cancelled = true; + } + + void onButton(QAbstractButton* b) + { + if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { + m_cancelled = true; + } + } +}; + + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) { SECURITY_ATTRIBUTES secAttributes; @@ -47,8 +115,7 @@ std::string readFromPipe(HANDLE stdOutRead) void processLOOTOut( OrganizerCore& core, - const std::string &lootOut, std::string &errorMessages, - QProgressDialog &dialog) + const std::string &lootOut, std::string &errorMessages, LootDialog& dialog) { std::vector lines; boost::split(lines, lootOut, boost::is_any_of("\r\n")); @@ -61,7 +128,7 @@ void processLOOTOut( size_t progidx = line.find("[progress]"); size_t erroridx = line.find("[error]"); if (progidx != std::string::npos) { - dialog.setLabelText(line.substr(progidx + 11).c_str()); + dialog.setText(line.substr(progidx + 11).c_str()); } else if (erroridx != std::string::npos) { log::warn("{}", line); errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); @@ -97,10 +164,10 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) bool success = false; try { - QProgressDialog dialog(parent); + LootDialog dialog(parent); - dialog.setLabelText(QObject::tr("Please wait while LOOT is running")); - dialog.setMaximum(0); + dialog.setText(QObject::tr("Please wait while LOOT is running")); + dialog.setIndeterminate(); dialog.show(); QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); @@ -178,7 +245,7 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) } } - if (dialog.wasCanceled()) { + if (dialog.cancelled()) { if (isJobHandle) { ::TerminateJobObject(loot, 1); } else { -- cgit v1.3.1 From 9688f37bbe54568aba09e2dd755e0ea35eef2bb1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 20:47:30 -0500 Subject: added loot output --- src/loot.cpp | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index c0051e61..a9e716ae 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -26,6 +26,19 @@ public: m_progress->setMaximum(0); } + void addOutput(const QString& s) + { + const auto lines = s.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts); + + for (auto&& line : lines) { + if (line.isEmpty()) { + continue; + } + + addLineOutput(line); + } + } + bool cancelled() const { return m_cancelled; @@ -35,6 +48,8 @@ private: QLabel* m_label; QProgressBar* m_progress; QDialogButtonBox* m_buttons; + QPlainTextEdit* m_output; + QString m_lastLine; bool m_cancelled; void createUI() @@ -52,7 +67,8 @@ private: m_progress = new QProgressBar; ly->addWidget(m_progress); - ly->addStretch(1); + m_output = new QPlainTextEdit; + ly->addWidget(m_output); m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); @@ -70,6 +86,16 @@ private: m_cancelled = true; } } + + void addLineOutput(const QString& line) + { + if (line == m_lastLine) { + return; + } + + m_output->appendPlainText(line); + m_lastLine = line; + } }; @@ -117,6 +143,8 @@ void processLOOTOut( OrganizerCore& core, const std::string &lootOut, std::string &errorMessages, LootDialog& dialog) { + dialog.addOutput(QString::fromStdString(lootOut)); + std::vector lines; boost::split(lines, lootOut, boost::is_any_of("\r\n")); -- cgit v1.3.1 From b8eab5d248272cab886bc800f2d619a49d8af54a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 22:25:55 -0500 Subject: threaded loot --- src/loot.cpp | 317 +++++++++++++++++++++++++++++++++++------------------ src/loot.h | 37 +++++++ src/mainwindow.cpp | 3 +- 3 files changed, 249 insertions(+), 108 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index a9e716ae..ab152b80 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -1,3 +1,4 @@ +#include "loot.h" #include "spawn.h" #include "organizercore.h" #include @@ -9,11 +10,35 @@ using namespace MOBase; class LootDialog : public QDialog { public: - LootDialog(QWidget* parent) : - QDialog(parent), m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), - m_cancelled(false) + LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : + QDialog(parent), m_core(core), m_loot(loot), + m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), m_finished(false) { createUI(); + + QObject::connect( + &m_loot, &Loot::output, this, + [&](auto&& s){ addOutput(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::progress, + this, [&](auto&& s){ setText(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::information, this, + [&](auto&& mod, auto&& i){ setInfo(mod, i); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::errorMessage, this, + [&](auto&& s){ onErrorMessage(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::error, this, + [&](auto&& s){ onError(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::finished, this, + [&]{ onFinished(); }, Qt::QueuedConnection); } void setText(const QString& s) @@ -39,18 +64,52 @@ public: } } - bool cancelled() const + void setInfo(const QString& mod, const QString& info) { - return m_cancelled; + m_core.pluginList()->addInformation(mod.toStdString().c_str(), info); + } + + bool result() const + { + return m_loot.result(); + } + + void cancel() + { + m_loot.cancel(); + } + + int exec() override + { + m_loot.start(); + QDialog::exec(); + + if (m_errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox( + QMessageBox::Warning, QObject::tr("Errors occurred"), + m_errorMessages, QMessageBox::Ok, parentWidget()); + + warn->exec(); + } + + return 0; + } + + void onError(const QString& s) + { + reportError(s); } private: + OrganizerCore& m_core; + Loot& m_loot; QLabel* m_label; QProgressBar* m_progress; QDialogButtonBox* m_buttons; QPlainTextEdit* m_output; QString m_lastLine; - bool m_cancelled; + QString m_errorMessages; + bool m_finished; void createUI() { @@ -77,13 +136,18 @@ private: void closeEvent(QCloseEvent* e) override { - m_cancelled = true; + if (m_finished) { + QDialog::closeEvent(e); + } else { + cancel(); + e->ignore(); + } } void onButton(QAbstractButton* b) { if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - m_cancelled = true; + cancel(); } } @@ -96,10 +160,83 @@ private: m_output->appendPlainText(line); m_lastLine = line; } + + void onFinished() + { + m_finished = true; + close(); + } + + void onErrorMessage(const QString& s) + { + m_errorMessages += s; + } }; -void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +Loot::Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) : + m_thread(nullptr), m_cancel(false), m_result(false), + m_lootProcess(INVALID_HANDLE_VALUE), m_stdOutRead(INVALID_HANDLE_VALUE) +{ + m_outPath = QDir::temp().absoluteFilePath("lootreport.json"); + + QStringList parameters; + parameters + << "--game" << core.managedGame()->gameShortName() + << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) + << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) + << "--out" << QString("\"%1\"").arg(m_outPath); + + if (didUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + createStdoutPipe(&m_stdOutRead, &stdOutWrite); + + core.prepareVFS(); + + spawn::SpawnParameters sp; + sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); + sp.arguments = parameters.join(" "); + sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); + sp.hooked = true; + sp.stdOut = stdOutWrite; + + m_lootProcess = spawn::startBinary(parent, sp); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + core.pluginList()->clearAdditionalInformation(); + + m_thread.reset(QThread::create([&]{ + lootThread(); + emit finished(); + })); +} + +Loot::~Loot() +{ + m_thread->wait(); +} + +void Loot::start() +{ + m_thread->start(); +} + +void Loot::cancel() +{ + m_cancel = true; +} + +bool Loot::result() const +{ + return m_result; +} + +void Loot::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) { SECURITY_ATTRIBUTES secAttributes; secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); @@ -116,7 +253,7 @@ void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) } } -std::string readFromPipe(HANDLE stdOutRead) +std::string Loot::readFromPipe(HANDLE stdOutRead) { static const int chunkSize = 128; std::string result; @@ -139,11 +276,9 @@ std::string readFromPipe(HANDLE stdOutRead) return result; } -void processLOOTOut( - OrganizerCore& core, - const std::string &lootOut, std::string &errorMessages, LootDialog& dialog) +void Loot::processLOOTOut(const std::string &lootOut) { - dialog.addOutput(QString::fromStdString(lootOut)); + emit output(QString::fromStdString(lootOut)); std::vector lines; boost::split(lines, lootOut, boost::is_any_of("\r\n")); @@ -156,20 +291,25 @@ void processLOOTOut( size_t progidx = line.find("[progress]"); size_t erroridx = line.find("[error]"); if (progidx != std::string::npos) { - dialog.setText(line.substr(progidx + 11).c_str()); + emit progress(line.substr(progidx + 11).c_str()); } else if (erroridx != std::string::npos) { log::warn("{}", line); - errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); + emit errorMessage(QString::fromStdString( + boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n")); } else { std::smatch match; if (std::regex_match(line, match, exRequires)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - core.pluginList()->addInformation(modName.c_str(), QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); + emit information( + QString::fromStdString(modName), + QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); } else if (std::regex_match(line, match, exIncompatible)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - core.pluginList()->addInformation(modName.c_str(), QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); + emit information( + QString::fromStdString(modName), + QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { log::debug("[loot] {}", line); } @@ -178,85 +318,29 @@ void processLOOTOut( } } -bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +void Loot::lootThread() { - std::string errorMessages; - - //m_OrganizerCore.currentProfile()->writeModlistNow(); - core.savePluginList(); - - //Create a backup of the load orders w/ LOOT in name - //to make sure that any sorting is easily undo-able. - //Need to figure out how I want to do that. - - bool success = false; - try { - LootDialog dialog(parent); - - dialog.setText(QObject::tr("Please wait while LOOT is running")); - dialog.setIndeterminate(); - dialog.show(); - - QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - - QStringList parameters; - parameters - << "--game" << core.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); - - if (didUpdateMasterList) { - parameters << "--skipUpdateMasterlist"; - } - - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - - try { - core.prepareVFS(); - } catch (const UsvfsConnectorException &e) { - log::debug("{}", e.what()); - return false; - } catch (const std::exception &e) { - QMessageBox::warning(parent, QObject::tr("Error"), e.what()); - return false; - } - - spawn::SpawnParameters sp; - sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); - sp.arguments = parameters.join(" "); - sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); - sp.hooked = true; - sp.stdOut = stdOutWrite; - - HANDLE loot = spawn::startBinary(parent, sp); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - core.pluginList()->clearAdditionalInformation(); + m_result = false; DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = loot; + HANDLE processHandle = m_lootProcess; - if (loot != INVALID_HANDLE_VALUE) { + if (m_lootProcess != INVALID_HANDLE_VALUE) { bool isJobHandle = true; ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + DWORD res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (::QueryInformationJobObject(m_lootProcess, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { log::debug("no more processes in job"); break; } else { if (lastProcessID != info.ProcessIdList[0]) { lastProcessID = info.ProcessIdList[0]; - if (processHandle != loot) { + if (processHandle != m_lootProcess) { ::CloseHandle(processHandle); } processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); @@ -273,36 +357,37 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) } } - if (dialog.cancelled()) { + if (m_cancel) { if (isJobHandle) { - ::TerminateJobObject(loot, 1); + ::TerminateJobObject(m_lootProcess, 1); } else { - ::TerminateProcess(loot, 1); + ::TerminateProcess(m_lootProcess, 1); } } // keep processing events so the app doesn't appear dead QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(core, lootOut, errorMessages, dialog); + std::string lootOut = readFromPipe(m_stdOutRead); + processLOOTOut(lootOut); - res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); } - std::string remainder = readFromPipe(stdOutRead).c_str(); + std::string remainder = readFromPipe(m_stdOutRead).c_str(); if (remainder.length() > 0) { - processLOOTOut(core, remainder, errorMessages, dialog); + processLOOTOut(remainder); } DWORD exitCode = 0UL; ::GetExitCodeProcess(processHandle, &exitCode); ::CloseHandle(processHandle); if (exitCode != 0UL) { - reportError(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); - return false; + emit error(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); + m_result = false; + return; } else { - success = true; - QFile outFile(outPath); + m_result = true; + QFile outFile(m_outPath); outFile.open(QIODevice::ReadOnly); QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); QJsonArray array = doc.array(); @@ -311,29 +396,47 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) QJsonArray pluginMessages = pluginObj["messages"].toArray(); for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { QJsonObject msg = (*msgIter).toObject(); - core.pluginList()->addInformation(pluginObj["name"].toString(), + emit information( + pluginObj["name"].toString(), QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); } - if (pluginObj["dirty"].toString() == "yes") - core.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); + if (pluginObj["dirty"].toString() == "yes") { + emit information(pluginObj["name"].toString(), "dirty"); + } } - } } else { - reportError(QObject::tr("failed to start loot")); + emit error(QObject::tr("failed to start loot")); } } catch (const std::exception &e) { - reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + emit error(QObject::tr("failed to run loot: %1").arg(e.what())); } +} - if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox( - QMessageBox::Warning, QObject::tr("Errors occurred"), - errorMessages.c_str(), QMessageBox::Ok, parent); - warn->setModal(false); - warn->show(); - } +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +{ + //m_OrganizerCore.currentProfile()->writeModlistNow(); + core.savePluginList(); - return success; + //Create a backup of the load orders w/ LOOT in name + //to make sure that any sorting is easily undo-able. + //Need to figure out how I want to do that. + + try { + Loot loot(parent, core, didUpdateMasterList); + LootDialog dialog(parent, core, loot); + + dialog.setText(QObject::tr("Please wait while LOOT is running")); + dialog.setIndeterminate(); + dialog.exec(); + + return dialog.result(); + } catch (const UsvfsConnectorException &e) { + log::debug("{}", e.what()); + return false; + } catch (const std::exception &e) { + reportError(QObject::tr("failed to run loot: %1").arg(e.what())); + return false; + } } diff --git a/src/loot.h b/src/loot.h index 2314e5b3..11f1dbc8 100644 --- a/src/loot.h +++ b/src/loot.h @@ -1,10 +1,47 @@ #ifndef MODORGANIZER_LOOT_H #define MODORGANIZER_LOOT_H +#include #include class OrganizerCore; +class Loot : public QObject +{ + Q_OBJECT; + +public: + Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + ~Loot(); + + void start(); + void cancel(); + bool result() const; + +signals: + void output(const QString& s); + void progress(const QString& s); + void information(const QString& mod, const QString& info); + void errorMessage(const QString& s); + void error(const QString& s); + void finished(); + +private: + std::unique_ptr m_thread; + std::atomic m_cancel; + std::atomic m_result; + QString m_outPath; + HANDLE m_lootProcess; + HANDLE m_stdOutRead; + + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); + + void processLOOTOut(const std::string &lootOut); + void lootThread(); +}; + + bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); #endif // MODORGANIZER_LOOT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 11edfc81..9ea554a2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,7 +39,6 @@ along with Mod Organizer. If not, see . #include "spawn.h" #include "versioninfo.h" #include "instancemanager.h" -#include "loot.h" #include "report.h" #include "modlist.h" #include "modlistsortproxy.h" @@ -191,6 +190,8 @@ const QSize SmallToolbarSize(24, 24); const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore -- cgit v1.3.1 From d686a61a7a72a8d4f6e7df7a4136757397cae3ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 16 Nov 2019 23:05:24 -0500 Subject: removed unused job stuff don't emit error when the process terminates because it's cancelled --- src/loot.cpp | 206 ++++++++++++++++++++++++++--------------------------------- src/loot.h | 12 ++-- 2 files changed, 97 insertions(+), 121 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index ab152b80..36d8c8f9 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -76,12 +76,12 @@ public: void cancel() { + addOutput(QObject::tr("Stopping LOOT...")); m_loot.cancel(); } int exec() override { - m_loot.start(); QDialog::exec(); if (m_errorMessages.length() > 0) { @@ -174,9 +174,17 @@ private: }; -Loot::Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) : - m_thread(nullptr), m_cancel(false), m_result(false), - m_lootProcess(INVALID_HANDLE_VALUE), m_stdOutRead(INVALID_HANDLE_VALUE) +Loot::Loot() + : m_thread(nullptr), m_cancel(false), m_result(false) +{ +} + +Loot::~Loot() +{ + m_thread->wait(); +} + +bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) { m_outPath = QDir::temp().absoluteFilePath("lootreport.json"); @@ -191,8 +199,28 @@ Loot::Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) : parameters << "--skipUpdateMasterlist"; } - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - createStdoutPipe(&m_stdOutRead, &stdOutWrite); + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = nullptr; + + env::HandlePtr readPipe, writePipe; + + { + HANDLE read = INVALID_HANDLE_VALUE; + HANDLE write = INVALID_HANDLE_VALUE; + + if (!::CreatePipe(&read, &write, &secAttributes, 0)) { + log::error("failed to create stdout reroute"); + } + + readPipe.reset(read); + writePipe.reset(write); + + if (!::SetHandleInformation(read, HANDLE_FLAG_INHERIT, 0)) { + log::error("failed to correctly set up the stdout reroute"); + } + } core.prepareVFS(); @@ -201,12 +229,17 @@ Loot::Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) : sp.arguments = parameters.join(" "); sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); sp.hooked = true; - sp.stdOut = stdOutWrite; + sp.stdOut = writePipe.get(); + + m_stdout = std::move(readPipe); - m_lootProcess = spawn::startBinary(parent, sp); + HANDLE lootHandle = spawn::startBinary(parent, sp); + if (lootHandle == INVALID_HANDLE_VALUE) { + emit error(QObject::tr("failed to start loot")); + return false; + } - // we don't use the write end - ::CloseHandle(stdOutWrite); + m_lootProcess.reset(lootHandle); core.pluginList()->clearAdditionalInformation(); @@ -214,16 +247,10 @@ Loot::Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) : lootThread(); emit finished(); })); -} -Loot::~Loot() -{ - m_thread->wait(); -} - -void Loot::start() -{ m_thread->start(); + + return true; } void Loot::cancel() @@ -236,24 +263,7 @@ bool Loot::result() const return m_result; } -void Loot::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) -{ - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = nullptr; - - if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - log::error("failed to create stdout reroute"); - } - - if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - log::error("failed to correctly set up the stdout reroute"); - *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; - } -} - -std::string Loot::readFromPipe(HANDLE stdOutRead) +std::string Loot::readFromPipe() { static const int chunkSize = 128; std::string result; @@ -263,7 +273,7 @@ std::string Loot::readFromPipe(HANDLE stdOutRead) DWORD read = 1; while (read > 0) { - if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { + if (!::ReadFile(m_stdout.get(), buffer, chunkSize, &read, nullptr)) { break; } if (read > 0) { @@ -323,90 +333,54 @@ void Loot::lootThread() try { m_result = false; - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = m_lootProcess; - - if (m_lootProcess != INVALID_HANDLE_VALUE) { - bool isJobHandle = true; - ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (isJobHandle) { - if (::QueryInformationJobObject(m_lootProcess, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - log::debug("no more processes in job"); - break; - } else { - if (lastProcessID != info.ProcessIdList[0]) { - lastProcessID = info.ProcessIdList[0]; - if (processHandle != m_lootProcess) { - ::CloseHandle(processHandle); - } - processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); - } - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } + HANDLE waitHandle = m_lootProcess.get(); + DWORD res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); - if (m_cancel) { - if (isJobHandle) { - ::TerminateJobObject(m_lootProcess, 1); - } else { - ::TerminateProcess(m_lootProcess, 1); - } - } + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { + if (m_cancel) { + ::TerminateProcess(m_lootProcess.get(), 1); + } - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(m_stdOutRead); - processLOOTOut(lootOut); + std::string lootOut = readFromPipe(); + processLOOTOut(lootOut); - res = ::MsgWaitForMultipleObjects(1, &m_lootProcess, false, 100, QS_KEY | QS_MOUSE); - } + res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); + } - std::string remainder = readFromPipe(m_stdOutRead).c_str(); - if (remainder.length() > 0) { - processLOOTOut(remainder); - } + std::string remainder = readFromPipe(); + if (remainder.length() > 0) { + processLOOTOut(remainder); + } - DWORD exitCode = 0UL; - ::GetExitCodeProcess(processHandle, &exitCode); - ::CloseHandle(processHandle); - if (exitCode != 0UL) { + DWORD exitCode = 0UL; + ::GetExitCodeProcess(m_lootProcess.get(), &exitCode); + + if (exitCode != 0UL) { + if (!m_cancel) { emit error(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); - m_result = false; - return; - } else { - m_result = true; - QFile outFile(m_outPath); - outFile.open(QIODevice::ReadOnly); - QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); - QJsonArray array = doc.array(); - for (auto iter = array.begin(); iter != array.end(); ++iter) { - QJsonObject pluginObj = (*iter).toObject(); - QJsonArray pluginMessages = pluginObj["messages"].toArray(); - for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { - QJsonObject msg = (*msgIter).toObject(); - emit information( - pluginObj["name"].toString(), - QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); - } - if (pluginObj["dirty"].toString() == "yes") { - emit information(pluginObj["name"].toString(), "dirty"); - } - } } - } else { - emit error(QObject::tr("failed to start loot")); + + return; + } + + m_result = true; + QFile outFile(m_outPath); + outFile.open(QIODevice::ReadOnly); + QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); + QJsonArray array = doc.array(); + + for (auto iter = array.begin(); iter != array.end(); ++iter) { + QJsonObject pluginObj = (*iter).toObject(); + QJsonArray pluginMessages = pluginObj["messages"].toArray(); + for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { + QJsonObject msg = (*msgIter).toObject(); + emit information( + pluginObj["name"].toString(), + QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); + } + if (pluginObj["dirty"].toString() == "yes") { + emit information(pluginObj["name"].toString(), "dirty"); + } } } catch (const std::exception &e) { emit error(QObject::tr("failed to run loot: %1").arg(e.what())); @@ -424,9 +398,11 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) //Need to figure out how I want to do that. try { - Loot loot(parent, core, didUpdateMasterList); + Loot loot; LootDialog dialog(parent, core, loot); + loot.start(parent, core, didUpdateMasterList); + dialog.setText(QObject::tr("Please wait while LOOT is running")); dialog.setIndeterminate(); dialog.exec(); diff --git a/src/loot.h b/src/loot.h index 11f1dbc8..d0c22236 100644 --- a/src/loot.h +++ b/src/loot.h @@ -3,6 +3,7 @@ #include #include +#include "envmodule.h" class OrganizerCore; @@ -11,10 +12,10 @@ class Loot : public QObject Q_OBJECT; public: - Loot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + Loot(); ~Loot(); - void start(); + bool start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); void cancel(); bool result() const; @@ -31,11 +32,10 @@ private: std::atomic m_cancel; std::atomic m_result; QString m_outPath; - HANDLE m_lootProcess; - HANDLE m_stdOutRead; + env::HandlePtr m_lootProcess; + env::HandlePtr m_stdout; - void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); - std::string readFromPipe(HANDLE stdOutRead); + std::string readFromPipe(); void processLOOTOut(const std::string &lootOut); void lootThread(); -- cgit v1.3.1 From 1b6dc5e0a0ef365453abeaf52015d5eeeb6b7ae2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 18 Nov 2019 11:15:09 -0500 Subject: moved things around --- src/loot.cpp | 142 ++++++++++++++++++++++++++++++++++------------------------- src/loot.h | 4 +- 2 files changed, 86 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 36d8c8f9..e589b54c 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -76,7 +76,7 @@ public: void cancel() { - addOutput(QObject::tr("Stopping LOOT...")); + addOutput(tr("Stopping LOOT...")); m_loot.cancel(); } @@ -86,7 +86,7 @@ public: if (m_errorMessages.length() > 0) { QMessageBox *warn = new QMessageBox( - QMessageBox::Warning, QObject::tr("Errors occurred"), + QMessageBox::Warning, tr("Errors occurred"), m_errorMessages, QMessageBox::Ok, parentWidget()); warn->exec(); @@ -235,7 +235,7 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) HANDLE lootHandle = spawn::startBinary(parent, sp); if (lootHandle == INVALID_HANDLE_VALUE) { - emit error(QObject::tr("failed to start loot")); + emit error(tr("failed to start loot")); return false; } @@ -263,6 +263,66 @@ bool Loot::result() const return m_result; } +void Loot::lootThread() +{ + try { + m_result = false; + + if (!waitForCompletion()) { + return; + } + + m_result = true; + processOutputFile(); + } catch (const std::exception &e) { + emit error(tr("failed to run loot: %1").arg(e.what())); + } +} + +bool Loot::waitForCompletion() +{ + HANDLE waitHandle = m_lootProcess.get(); + DWORD res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); + + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { + if (m_cancel) { + ::TerminateProcess(m_lootProcess.get(), 1); + } + + std::string lootOut = readFromPipe(); + processStdout(lootOut); + + res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); + } + + const std::string remainder = readFromPipe(); + if (!remainder.empty()) { + processStdout(remainder); + } + + if (m_cancel) { + return false; + } + + + // checking exit code + + DWORD exitCode = 0; + + if (!::GetExitCodeProcess(m_lootProcess.get(), &exitCode)) { + const auto e = GetLastError(); + log::error("failed to get exit code for loot, {}", formatSystemMessage(e)); + return false; + } + + if (exitCode != 0UL) { + emit error(tr("Loot failed. Exit code was: %1").arg(exitCode)); + return false; + } + + return true; +} + std::string Loot::readFromPipe() { static const int chunkSize = 128; @@ -286,7 +346,7 @@ std::string Loot::readFromPipe() return result; } -void Loot::processLOOTOut(const std::string &lootOut) +void Loot::processStdout(const std::string &lootOut) { emit output(QString::fromStdString(lootOut)); @@ -313,13 +373,13 @@ void Loot::processLOOTOut(const std::string &lootOut) std::string dependency(match[2].first, match[2].second); emit information( QString::fromStdString(modName), - QObject::tr("depends on missing \"%1\"").arg(dependency.c_str())); + tr("depends on missing \"%1\"").arg(dependency.c_str())); } else if (std::regex_match(line, match, exIncompatible)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); emit information( QString::fromStdString(modName), - QObject::tr("incompatible with \"%1\"").arg(dependency.c_str())); + tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { log::debug("[loot] {}", line); } @@ -328,62 +388,26 @@ void Loot::processLOOTOut(const std::string &lootOut) } } -void Loot::lootThread() +void Loot::processOutputFile() { - try { - m_result = false; - - HANDLE waitHandle = m_lootProcess.get(); - DWORD res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); - - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (m_cancel) { - ::TerminateProcess(m_lootProcess.get(), 1); - } - - std::string lootOut = readFromPipe(); - processLOOTOut(lootOut); - - res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); - } - - std::string remainder = readFromPipe(); - if (remainder.length() > 0) { - processLOOTOut(remainder); - } - - DWORD exitCode = 0UL; - ::GetExitCodeProcess(m_lootProcess.get(), &exitCode); - - if (exitCode != 0UL) { - if (!m_cancel) { - emit error(QObject::tr("loot failed. Exit code was: %1").arg(exitCode)); - } - - return; + QFile outFile(m_outPath); + outFile.open(QIODevice::ReadOnly); + + QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); + QJsonArray array = doc.array(); + + for (auto iter = array.begin(); iter != array.end(); ++iter) { + QJsonObject pluginObj = (*iter).toObject(); + QJsonArray pluginMessages = pluginObj["messages"].toArray(); + for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { + QJsonObject msg = (*msgIter).toObject(); + emit information( + pluginObj["name"].toString(), + QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); } - - m_result = true; - QFile outFile(m_outPath); - outFile.open(QIODevice::ReadOnly); - QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); - QJsonArray array = doc.array(); - - for (auto iter = array.begin(); iter != array.end(); ++iter) { - QJsonObject pluginObj = (*iter).toObject(); - QJsonArray pluginMessages = pluginObj["messages"].toArray(); - for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { - QJsonObject msg = (*msgIter).toObject(); - emit information( - pluginObj["name"].toString(), - QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); - } - if (pluginObj["dirty"].toString() == "yes") { - emit information(pluginObj["name"].toString(), "dirty"); - } + if (pluginObj["dirty"].toString() == "yes") { + emit information(pluginObj["name"].toString(), "dirty"); } - } catch (const std::exception &e) { - emit error(QObject::tr("failed to run loot: %1").arg(e.what())); } } diff --git a/src/loot.h b/src/loot.h index d0c22236..1f1c4353 100644 --- a/src/loot.h +++ b/src/loot.h @@ -37,8 +37,10 @@ private: std::string readFromPipe(); - void processLOOTOut(const std::string &lootOut); void lootThread(); + bool waitForCompletion(); + void processOutputFile(); + void processStdout(const std::string &lootOut); }; -- cgit v1.3.1 From 27dadd016422765acb774ed2ed9ddae480eda46d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 18 Nov 2019 13:32:33 -0500 Subject:
  • in tooltip for information messages rewrote json output file handling to check for errors --- src/loot.cpp | 208 +++++++++++++++++++++++++++++++++++++++++++++++++---- src/loot.h | 24 ++++++- src/pluginlist.cpp | 6 +- 3 files changed, 220 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index e589b54c..1fc0e438 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -388,27 +388,205 @@ void Loot::processStdout(const std::string &lootOut) } } +QString jsonType(const QJsonValue& v) +{ + if (v.isUndefined()) { + return "undefined"; + } else if (v.isNull()) { + return "null"; + } else if (v.isArray()) { + return "an array"; + } else if (v.isBool()) { + return "a bool"; + } else if (v.isDouble()) { + return "a double"; + } else if (v.isObject()) { + return "an object"; + } else if (v.isString()) { + return "a string"; + } else { + return "an unknown type"; + } +} + +QString jsonType(const QJsonDocument& doc) +{ + if (doc.isEmpty()) { + return "empty"; + } else if (doc.isNull()) { + return "null"; + } else if (doc.isArray()) { + return "an array"; + } else if (doc.isObject()) { + return "an object"; + } else { + return "an unknown type"; + } +} + void Loot::processOutputFile() { QFile outFile(m_outPath); - outFile.open(QIODevice::ReadOnly); - - QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); - QJsonArray array = doc.array(); - - for (auto iter = array.begin(); iter != array.end(); ++iter) { - QJsonObject pluginObj = (*iter).toObject(); - QJsonArray pluginMessages = pluginObj["messages"].toArray(); - for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { - QJsonObject msg = (*msgIter).toObject(); - emit information( - pluginObj["name"].toString(), - QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); + if (!outFile.open(QIODevice::ReadOnly)) { + logJsonError( + "failed to open file, {} (error {})", + outFile.errorString(), outFile.error()); + + return; + } + + QJsonParseError e; + const QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll(), &e); + if (doc.isNull()) { + logJsonError("invalid json, {} (error {})", e.errorString(), e.error); + return; + } + + if (!doc.isArray()) { + logJsonError("root is {}, not an array", jsonType(doc)); + return; + } + + const QJsonArray array = doc.array(); + + for (auto pluginValue : array) { + processOutputPlugin(pluginValue); + } +} + +bool Loot::processOutputPlugin(const QJsonValue& pluginValue) +{ + if (!pluginValue.isObject()) { + logJsonError( + "value in root array is {}, not an object", jsonType(pluginValue)); + return false; + } + + const auto plugin = pluginValue.toObject(); + + + if (!plugin.contains("name")) { + logJsonError("plugin value doesn't have a 'name' property"); + return false; + } + + const auto pluginNameValue = plugin["name"]; + if (!pluginNameValue.isString()) { + logJsonError( + "plugin property 'name' is {}, not a string", jsonType(pluginNameValue)); + return false; + } + + const auto pluginName = pluginNameValue.toString(); + + processPluginMessages(pluginName, plugin); + processPluginDirty(pluginName, plugin); + + return true; +} + +bool Loot::processPluginMessages( + const QString& pluginName, const QJsonObject& plugin) +{ + if (!plugin.contains("messages")) { + return true; + } + + const auto messagesValue = plugin["messages"]; + + if (!messagesValue.isArray()) { + logJsonError( + "'messages' value for plugin '{}' is {}, not an array", + pluginName, jsonType(messagesValue)); + + return false; + } + + const auto messages = messagesValue.toArray(); + + + for (auto messageValue : messages) { + if (!messageValue.isObject()) { + logJsonError( + "plugin '{}' has a message that's {}, not an object", + pluginName, jsonType(messageValue)); + + continue; } - if (pluginObj["dirty"].toString() == "yes") { - emit information(pluginObj["name"].toString(), "dirty"); + + processPluginMessage(pluginName, messageValue.toObject()); + } + + return true; +} + +bool Loot::processPluginMessage( + const QString& pluginName, const QJsonObject& message) +{ + const auto messageType = message["type"].toString(); + const auto messageString = message["message"].toString(); + + if (messageType.isEmpty()) { + logJsonError( + "plugin '{}' has a message with no 'type' property", pluginName); + return false; + } + + if (messageString.isEmpty()) { + logJsonError( + "plugin '{}' has a message with no 'message' property", pluginName); + return false; + } + + const auto info = QString("%1: %2") + .arg(messageType) + .arg(messageString); + + emit information(pluginName, info); + return true; +} + + +bool Loot::processPluginDirty( + const QString& pluginName, const QJsonObject& plugin) +{ + if (!plugin.contains("dirty")) { + return true; + } + + const auto dirtyValue = plugin["dirty"]; + + if (!dirtyValue.isArray()) { + logJsonError( + "'dirty' value for plugin '{}' is {}, not an array", + pluginName, jsonType(dirtyValue)); + + return false; + } + + const auto dirty = dirtyValue.toArray(); + + + for (auto stringValue : dirty) { + if (!stringValue.isString()) { + logJsonError( + "'dirty' value for plugin '{}' is {}, not a string", + pluginName, jsonType(stringValue)); + + continue; + } + + const auto string = stringValue.toString(); + + if (string.isEmpty()) { + logJsonError("'dirty' string for plugin '{}' is empty", pluginName); + continue; } + + emit information(pluginName, string); } + + return true; } diff --git a/src/loot.h b/src/loot.h index 1f1c4353..11bb4987 100644 --- a/src/loot.h +++ b/src/loot.h @@ -1,9 +1,10 @@ #ifndef MODORGANIZER_LOOT_H #define MODORGANIZER_LOOT_H +#include "envmodule.h" +#include #include #include -#include "envmodule.h" class OrganizerCore; @@ -39,8 +40,27 @@ private: void lootThread(); bool waitForCompletion(); - void processOutputFile(); void processStdout(const std::string &lootOut); + + void processOutputFile(); + bool processOutputPlugin(const QJsonValue& pluginValue); + + bool processPluginMessages( + const QString& pluginName, const QJsonObject& plugin); + + bool processPluginMessage( + const QString& pluginName, const QJsonObject& message); + + bool processPluginDirty( + const QString& pluginName, const QJsonObject& plugin); + + template + void logJsonError(Format&& f, Args&&... args) + { + MOBase::log::error( + std::string("loot output file '{}': ") + f, + m_outPath, std::forward(args)...); + }; }; diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index f35f9409..b50a51d8 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -963,7 +963,11 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const QString toolTip; if (addInfoIter != m_AdditionalInfo.end()) { if (!addInfoIter->second.m_Messages.isEmpty()) { - toolTip += addInfoIter->second.m_Messages.join("
    ") + "

    "; + toolTip += "
      "; + for (auto&& message : addInfoIter->second.m_Messages) { + toolTip += "
    • " + message + "
    • "; + } + toolTip += "

    "; } } if (m_ESPs[index].m_ForceEnabled) { -- cgit v1.3.1 From 2b5747c19d942974295be18042fbdde8ddc4cc78 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 18 Nov 2019 17:04:01 -0500 Subject: handles changes in lootcli for a more formal communication protocol --- src/CMakeLists.txt | 2 + src/loot.cpp | 129 +++++++++++++++++++++++++++++++++++++---------------- src/loot.h | 12 +++-- 3 files changed, 101 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70c501ae..824ffa33 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -569,11 +569,13 @@ INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/bsatk/src ${project_path}/esptk/src ${project_path}/archive/src + ${project_path}/lootcli/include ${dependency_project_path}/usvfs/include ${project_path}/game_gamebryo/src/gamebryo ${project_path}/game_gamebryo/src/creation ${project_path}/game_features/src ${project_path}/githubpp/src + ${SPDLOG_ROOT}/include ${LZ4_ROOT}/lib) INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS}) diff --git a/src/loot.cpp b/src/loot.cpp index 1fc0e438..7184cce8 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -22,19 +22,15 @@ public: QObject::connect( &m_loot, &Loot::progress, - this, [&](auto&& s){ setText(s); }, Qt::QueuedConnection); + this, [&](auto&& p){ setProgress(p); }, Qt::QueuedConnection); QObject::connect( - &m_loot, &Loot::information, this, - [&](auto&& mod, auto&& i){ setInfo(mod, i); }, Qt::QueuedConnection); - - QObject::connect( - &m_loot, &Loot::errorMessage, this, - [&](auto&& s){ onErrorMessage(s); }, Qt::QueuedConnection); + &m_loot, &Loot::log, this, + [&](auto&& lv, auto&& s){ log(lv, s); }, Qt::QueuedConnection); QObject::connect( - &m_loot, &Loot::error, this, - [&](auto&& s){ onError(s); }, Qt::QueuedConnection); + &m_loot, &Loot::information, this, + [&](auto&& mod, auto&& i){ setInfo(mod, i); }, Qt::QueuedConnection); QObject::connect( &m_loot, &Loot::finished, this, @@ -46,6 +42,29 @@ public: m_label->setText(s); } + void setProgress(lootcli::Progress p) + { + setText(progressToString(p)); + } + + QString progressToString(lootcli::Progress p) + { + using P = lootcli::Progress; + + switch (p) + { + case P::CheckingMasterlistExistence: return tr("Checking masterlist existence"); + case P::UpdatingMasterlist: return tr("Updating masterlist"); + case P::LoadingLists: return tr("Loading lists"); + case P::ReadingPlugins: return tr("Reading plugins"); + case P::SortingPlugins: return tr("Sorting plugins"); + case P::WritingLoadorder: return tr("Writing loadorder.txt"); + case P::ParsingLootMessages: return tr("Parsing loot messages"); + case P::Done: return tr("Done"); + default: return QString("unknown progress %1").arg(static_cast(p)); + } + } + void setIndeterminate() { m_progress->setMaximum(0); @@ -153,10 +172,6 @@ private: void addLineOutput(const QString& line) { - if (line == m_lastLine) { - return; - } - m_output->appendPlainText(line); m_lastLine = line; } @@ -164,12 +179,19 @@ private: void onFinished() { m_finished = true; - close(); } - void onErrorMessage(const QString& s) + void log(log::Levels lv, const QString& s) { - m_errorMessages += s; + if (lv == log::Levels::Error) { + MOBase::log::error("{}", s); + + if (!m_errorMessages.isEmpty()) { + m_errorMessages += "\n"; + } + + m_errorMessages += s; + } } }; @@ -235,7 +257,7 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) HANDLE lootHandle = spawn::startBinary(parent, sp); if (lootHandle == INVALID_HANDLE_VALUE) { - emit error(tr("failed to start loot")); + emit log(log::Levels::Error, tr("failed to start loot")); return false; } @@ -275,7 +297,7 @@ void Loot::lootThread() m_result = true; processOutputFile(); } catch (const std::exception &e) { - emit error(tr("failed to run loot: %1").arg(e.what())); + emit log(log::Levels::Error, tr("failed to run loot: %1").arg(e.what())); } } @@ -316,7 +338,7 @@ bool Loot::waitForCompletion() } if (exitCode != 0UL) { - emit error(tr("Loot failed. Exit code was: %1").arg(exitCode)); + emit log(log::Levels::Error, tr("Loot failed. Exit code was: %1").arg(exitCode)); return false; } @@ -350,40 +372,69 @@ void Loot::processStdout(const std::string &lootOut) { emit output(QString::fromStdString(lootOut)); - std::vector lines; - boost::split(lines, lootOut, boost::is_any_of("\r\n")); - - std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); - - for (const std::string &line : lines) { - if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t erroridx = line.find("[error]"); - if (progidx != std::string::npos) { - emit progress(line.substr(progidx + 11).c_str()); - } else if (erroridx != std::string::npos) { - log::warn("{}", line); - emit errorMessage(QString::fromStdString( - boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n")); - } else { + m_outputBuffer += lootOut; + std::size_t start = 0; + + for (;;) { + const auto newline = m_outputBuffer.find("\n", start); + if (newline == std::string::npos) { + break; + } + + const std::string_view line(m_outputBuffer.c_str() + start, newline - start); + const auto m = lootcli::parseMessage(line); + + if (m.type == lootcli::MessageType::None) { + log::error("unrecognised loot output: '{}'", line); + continue; + } + + processMessage(m); + + start = newline + 1; + } + + m_outputBuffer.erase(0, start); +} + +void Loot::processMessage(const lootcli::Message& m) +{ + static const std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + static const std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); + + switch (m.type) + { + case lootcli::MessageType::Log: + { + if (m.logLevel == spdlog::level::err) { std::smatch match; - if (std::regex_match(line, match, exRequires)) { + + if (std::regex_match(m.log, match, exRequires)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); emit information( QString::fromStdString(modName), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else if (std::regex_match(line, match, exIncompatible)) { + } else if (std::regex_match(m.log, match, exIncompatible)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); emit information( QString::fromStdString(modName), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { - log::debug("[loot] {}", line); + emit log(log::levelFromSpdlog(m.logLevel), QString::fromStdString(m.log)); } + } else { + emit log(log::levelFromSpdlog(m.logLevel), QString::fromStdString(m.log)); } + + break; + } + + case lootcli::MessageType::Progress: + { + emit progress(m.progress); + break; } } } diff --git a/src/loot.h b/src/loot.h index 11bb4987..ab959f2e 100644 --- a/src/loot.h +++ b/src/loot.h @@ -3,9 +3,13 @@ #include "envmodule.h" #include +#include #include #include +Q_DECLARE_METATYPE(lootcli::Progress); +Q_DECLARE_METATYPE(MOBase::log::Levels); + class OrganizerCore; class Loot : public QObject @@ -22,10 +26,9 @@ public: signals: void output(const QString& s); - void progress(const QString& s); + void progress(const lootcli::Progress p); + void log(MOBase::log::Levels level, const QString& s); void information(const QString& mod, const QString& info); - void errorMessage(const QString& s); - void error(const QString& s); void finished(); private: @@ -35,12 +38,15 @@ private: QString m_outPath; env::HandlePtr m_lootProcess; env::HandlePtr m_stdout; + std::string m_outputBuffer; std::string readFromPipe(); void lootThread(); bool waitForCompletion(); + void processStdout(const std::string &lootOut); + void processMessage(const lootcli::Message& m); void processOutputFile(); bool processOutputPlugin(const QJsonValue& pluginValue); -- cgit v1.3.1 From 981c1b773966a30374a90bc21d6ebd9c7bf86040 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 19 Nov 2019 14:42:50 -0600 Subject: Move all Qt deployment to main MO project --- src/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d935981d..f168ef36 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -653,6 +653,10 @@ INSTALL( ${qt5bin}/windeployqt.exe --verbose 0 uibase.dll ${windeploy_parameters} WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin ) + EXECUTE_PROCESS(COMMAND + ${qt5bin}/windeployqt.exe --verbose 0 plugins/bsa_packer.dll ${windeploy_parameters} + WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin + ) file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/platforms) file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/styles) file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/dlls/imageformats) -- cgit v1.3.1 From 8fcaea9de32b888ec8839ef6b7f394556383275e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 22 Nov 2019 07:44:09 -0500 Subject: added loot log level option --- src/loot.cpp | 79 +++++++++++++--------- src/settings.cpp | 11 +++ src/settings.h | 5 ++ src/settingsdialog.ui | 136 ++++++++++++++++++-------------------- src/settingsdialogdiagnostics.cpp | 36 +++++++++- src/settingsdialogdiagnostics.h | 3 +- 6 files changed, 164 insertions(+), 106 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 7184cce8..c5df8c9d 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -6,6 +6,30 @@ using namespace MOBase; +log::Levels levelFromLoot(lootcli::LogLevels level) +{ + using LC = lootcli::LogLevels; + + switch (level) + { + case LC::Trace: // fall-through + case LC::Debug: + return log::Debug; + + case LC::Info: + return log::Info; + + case LC::Warning: + return log::Warning; + + case LC::Error: + return log::Error; + + default: + return log::Info; + } +} + class LootDialog : public QDialog { @@ -15,6 +39,7 @@ public: m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), m_finished(false) { createUI(); + m_progress->setMaximum(0); QObject::connect( &m_loot, &Loot::output, this, @@ -45,6 +70,11 @@ public: void setProgress(lootcli::Progress p) { setText(progressToString(p)); + + if (p == lootcli::Progress::Done) { + m_progress->setRange(0, 1); + m_progress->setValue(1); + } } QString progressToString(lootcli::Progress p) @@ -65,13 +95,12 @@ public: } } - void setIndeterminate() - { - m_progress->setMaximum(0); - } - void addOutput(const QString& s) { + if (m_core.settings().diagnostics().lootLogLevel() > lootcli::LogLevels::Debug) { + return; + } + const auto lines = s.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts); for (auto&& line : lines) { @@ -101,17 +130,7 @@ public: int exec() override { - QDialog::exec(); - - if (m_errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox( - QMessageBox::Warning, tr("Errors occurred"), - m_errorMessages, QMessageBox::Ok, parentWidget()); - - warn->exec(); - } - - return 0; + return QDialog::exec(); } void onError(const QString& s) @@ -126,8 +145,6 @@ private: QProgressBar* m_progress; QDialogButtonBox* m_buttons; QPlainTextEdit* m_output; - QString m_lastLine; - QString m_errorMessages; bool m_finished; void createUI() @@ -146,11 +163,14 @@ private: ly->addWidget(m_progress); m_output = new QPlainTextEdit; + m_output->setWordWrapMode(QTextOption::NoWrap); ly->addWidget(m_output); m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); ly->addWidget(m_buttons); + + resize(700, 400); } void closeEvent(QCloseEvent* e) override @@ -173,7 +193,6 @@ private: void addLineOutput(const QString& line) { m_output->appendPlainText(line); - m_lastLine = line; } void onFinished() @@ -183,14 +202,12 @@ private: void log(log::Levels lv, const QString& s) { - if (lv == log::Levels::Error) { - MOBase::log::error("{}", s); - - if (!m_errorMessages.isEmpty()) { - m_errorMessages += "\n"; - } + if (lv >= log::Levels::Warning) { + log::log(lv, "{}", s); + } - m_errorMessages += s; + if (m_core.settings().diagnostics().lootLogLevel() > lootcli::LogLevels::Debug) { + addLineOutput(QString("[%1] %2").arg(log::levelToString(lv)).arg(s)); } } }; @@ -210,11 +227,14 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) { m_outPath = QDir::temp().absoluteFilePath("lootreport.json"); + const auto logLevel = core.settings().diagnostics().lootLogLevel(); + QStringList parameters; parameters << "--game" << core.managedGame()->gameShortName() << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) + << "--logLevel" << QString::fromStdString(lootcli::logLevelToString(logLevel)) << "--out" << QString("\"%1\"").arg(m_outPath); if (didUpdateMasterList) { @@ -406,7 +426,7 @@ void Loot::processMessage(const lootcli::Message& m) { case lootcli::MessageType::Log: { - if (m.logLevel == spdlog::level::err) { + if (m.logLevel == lootcli::LogLevels::Error) { std::smatch match; if (std::regex_match(m.log, match, exRequires)) { @@ -422,10 +442,10 @@ void Loot::processMessage(const lootcli::Message& m) QString::fromStdString(modName), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { - emit log(log::levelFromSpdlog(m.logLevel), QString::fromStdString(m.log)); + emit log(levelFromLoot(m.logLevel), QString::fromStdString(m.log)); } } else { - emit log(log::levelFromSpdlog(m.logLevel), QString::fromStdString(m.log)); + emit log(levelFromLoot(m.logLevel), QString::fromStdString(m.log)); } break; @@ -657,7 +677,6 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) loot.start(parent, core, didUpdateMasterList); dialog.setText(QObject::tr("Please wait while LOOT is running")); - dialog.setIndeterminate(); dialog.exec(); return dialog.result(); diff --git a/src/settings.cpp b/src/settings.cpp index 5aeb82fe..b533b400 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1977,6 +1977,17 @@ void DiagnosticsSettings::setLogLevel(log::Levels level) set(m_Settings, "Settings", "log_level", level); } +lootcli::LogLevels DiagnosticsSettings::lootLogLevel() const +{ + return get( + m_Settings, "Settings", "loot_log_level", lootcli::LogLevels::Info); +} + +void DiagnosticsSettings::setLootLogLevel(lootcli::LogLevels level) +{ + set(m_Settings, "Settings", "loot_log_level", level); +} + CrashDumpsType DiagnosticsSettings::crashDumpsType() const { return get(m_Settings, diff --git a/src/settings.h b/src/settings.h index d604823a..d71fabf4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define SETTINGS_H #include "loadmechanism.h" +#include #include #include #include @@ -619,6 +620,10 @@ public: MOBase::log::Levels logLevel() const; void setLogLevel(MOBase::log::Levels level); + // log level for loot + lootcli::LogLevels lootLogLevel() const; + void setLootLogLevel(lootcli::LogLevels level); + // crash dump type for both MO and usvfs // CrashDumpsType crashDumpsType() const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 0ecbd101..b88c8b71 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1386,44 +1386,23 @@ programs you are intentionally running. Diagnostics - - - - - - Max Dumps To Keep - - - - - - - Qt::Horizontal - - - - 60 - 20 - - - - - - - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - - - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - Set "Crash Dumps" above to None to disable crash dump collection. - - - - - - + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 232 + + + + + Hint: right click link and copy link location @@ -1444,16 +1423,42 @@ programs you are intentionally running. - - - + + + + QFormLayout::ExpandingFieldsGrow + + + 12 + + + + + Log Level + + + + + + + Decides the amount of data printed to "ModOrganizer.log" + + + + Decides the amount of data printed to "ModOrganizer.log". + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. + + + + + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. @@ -1469,46 +1474,36 @@ programs you are intentionally running. - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 232 - - - - - - - - + + - Log Level + Max Dumps To Keep - - + + - Decides the amount of data printed to "ModOrganizer.log" + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + Set "Crash Dumps" above to None to disable crash dump collection. + + + + LOOT Log Level + + + + + + @@ -1589,9 +1584,6 @@ programs you are intentionally running. bsaDateBtn execBlacklistBtn resetGeometryBtn - logLevelBox - dumpsTypeBox - dumpsMaxEdit diff --git a/src/settingsdialogdiagnostics.cpp b/src/settingsdialogdiagnostics.cpp index 386c7425..74cadaa9 100644 --- a/src/settingsdialogdiagnostics.cpp +++ b/src/settingsdialogdiagnostics.cpp @@ -9,7 +9,8 @@ using namespace MOBase; DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { - setLevelsBox(); + setLogLevel(); + setLootLogLevel(); setCrashDumpTypesBox(); ui->dumpsMaxEdit->setValue(settings().diagnostics().crashDumpsMax()); @@ -26,7 +27,7 @@ DiagnosticsSettingsTab::DiagnosticsSettingsTab(Settings& s, SettingsDialog& d) ); } -void DiagnosticsSettingsTab::setLevelsBox() +void DiagnosticsSettingsTab::setLogLevel() { ui->logLevelBox->clear(); @@ -35,14 +36,40 @@ void DiagnosticsSettingsTab::setLevelsBox() ui->logLevelBox->addItem(QObject::tr("Warning"), log::Warning); ui->logLevelBox->addItem(QObject::tr("Error"), log::Error); + const auto sel = settings().diagnostics().logLevel(); + for (int i=0; ilogLevelBox->count(); ++i) { - if (ui->logLevelBox->itemData(i) == settings().diagnostics().logLevel()) { + if (ui->logLevelBox->itemData(i) == sel) { ui->logLevelBox->setCurrentIndex(i); break; } } } +void DiagnosticsSettingsTab::setLootLogLevel() +{ + using L = lootcli::LogLevels; + + auto v = [](L level) { return QVariant(static_cast(level)); }; + + ui->lootLogLevel->clear(); + + ui->lootLogLevel->addItem(QObject::tr("Trace"), v(L::Trace)); + ui->lootLogLevel->addItem(QObject::tr("Debug"), v(L::Debug)); + ui->lootLogLevel->addItem(QObject::tr("Info (recommended)"), v(L::Info)); + ui->lootLogLevel->addItem(QObject::tr("Warning"), v(L::Warning)); + ui->lootLogLevel->addItem(QObject::tr("Error"), v(L::Error)); + + const auto sel = settings().diagnostics().lootLogLevel(); + + for (int i=0; ilootLogLevel->count(); ++i) { + if (ui->lootLogLevel->itemData(i) == v(sel)) { + ui->lootLogLevel->setCurrentIndex(i); + break; + } + } +} + void DiagnosticsSettingsTab::setCrashDumpTypesBox() { ui->dumpsTypeBox->clear(); @@ -76,4 +103,7 @@ void DiagnosticsSettingsTab::update() static_cast(ui->dumpsTypeBox->currentData().toInt())); settings().diagnostics().setCrashDumpsMax(ui->dumpsMaxEdit->value()); + + settings().diagnostics().setLootLogLevel( + static_cast(ui->lootLogLevel->currentData().toInt())); } diff --git a/src/settingsdialogdiagnostics.h b/src/settingsdialogdiagnostics.h index f0fbf770..e01ee22f 100644 --- a/src/settingsdialogdiagnostics.h +++ b/src/settingsdialogdiagnostics.h @@ -12,7 +12,8 @@ public: void update(); private: - void setLevelsBox(); + void setLogLevel(); + void setLootLogLevel(); void setCrashDumpTypesBox(); }; -- cgit v1.3.1 From d05df3bb7c467bceca4b8b8f9fb863cdc51b0e38 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 22 Nov 2019 08:01:07 -0500 Subject: cancel/close button --- src/loot.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index c5df8c9d..8345fc63 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -36,7 +36,8 @@ class LootDialog : public QDialog public: LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : QDialog(parent), m_core(core), m_loot(loot), - m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), m_finished(false) + m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), + m_finished(false), m_cancelling(false) { createUI(); m_progress->setMaximum(0); @@ -124,8 +125,12 @@ public: void cancel() { - addOutput(tr("Stopping LOOT...")); - m_loot.cancel(); + if (!m_finished && !m_cancelling) { + addLineOutput(tr("Stopping LOOT...")); + m_loot.cancel(); + m_buttons->setEnabled(false); + m_cancelling = true; + } } int exec() override @@ -146,6 +151,7 @@ private: QDialogButtonBox* m_buttons; QPlainTextEdit* m_output; bool m_finished; + bool m_cancelling; void createUI() { @@ -186,7 +192,11 @@ private: void onButton(QAbstractButton* b) { if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - cancel(); + if (m_finished) { + close(); + } else { + cancel(); + } } } @@ -198,6 +208,12 @@ private: void onFinished() { m_finished = true; + + if (m_cancelling) { + close(); + } else { + m_buttons->setStandardButtons(QDialogButtonBox::Close); + } } void log(log::Levels lv, const QString& s) -- cgit v1.3.1 From 0faf628e928a7caf46aeecff2a15dd2bcd2726fe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 22 Nov 2019 08:26:29 -0500 Subject: open json report button --- src/loot.cpp | 36 +++++++++++++++++++++++++++++++++--- src/loot.h | 1 + 2 files changed, 34 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 8345fc63..35ac9cff 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -37,6 +37,7 @@ public: LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : QDialog(parent), m_core(core), m_loot(loot), m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), + m_report(nullptr), m_output(nullptr), m_finished(false), m_cancelling(false) { createUI(); @@ -138,9 +139,10 @@ public: return QDialog::exec(); } - void onError(const QString& s) + void openReport() { - reportError(s); + const auto path = m_loot.outPath(); + shell::Open(path); } private: @@ -149,6 +151,7 @@ private: QLabel* m_label; QProgressBar* m_progress; QDialogButtonBox* m_buttons; + QPushButton* m_report; QPlainTextEdit* m_output; bool m_finished; bool m_cancelling; @@ -168,6 +171,27 @@ private: m_progress = new QProgressBar; ly->addWidget(m_progress); + auto* more = createMoreUI(); + ly->addWidget(more); + + resize(700, 400); + } + + QWidget* createMoreUI() + { + auto* more = new QWidget; + auto* ly = new QVBoxLayout(more); + ly->setContentsMargins(0, 0, 0, 0); + + auto* buttons = new QHBoxLayout; + buttons->setContentsMargins(0, 0, 0, 0); + m_report = new QPushButton(tr("Open JSON report")); + m_report->setEnabled(false); + connect(m_report, &QPushButton::clicked, [&]{ openReport(); }); + buttons->addWidget(m_report); + buttons->addStretch(1); + ly->addLayout(buttons); + m_output = new QPlainTextEdit; m_output->setWordWrapMode(QTextOption::NoWrap); ly->addWidget(m_output); @@ -176,7 +200,7 @@ private: connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); ly->addWidget(m_buttons); - resize(700, 400); + return more; } void closeEvent(QCloseEvent* e) override @@ -212,6 +236,7 @@ private: if (m_cancelling) { close(); } else { + m_report->setEnabled(true); m_buttons->setStandardButtons(QDialogButtonBox::Close); } } @@ -321,6 +346,11 @@ bool Loot::result() const return m_result; } +const QString& Loot::outPath() const +{ + return m_outPath; +} + void Loot::lootThread() { try { diff --git a/src/loot.h b/src/loot.h index ab959f2e..6dc7c9f3 100644 --- a/src/loot.h +++ b/src/loot.h @@ -23,6 +23,7 @@ public: bool start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); void cancel(); bool result() const; + const QString& outPath() const; signals: void output(const QString& s); -- cgit v1.3.1 From 237825f5b6c77969376198ed60d81c26a6a8aded Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 22 Nov 2019 09:14:02 -0500 Subject: emit logs for general messages handle new json output file --- src/loot.cpp | 177 +++++++++++++++++++++++++++++++---------------------------- src/loot.h | 14 ++--- 2 files changed, 99 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 35ac9cff..eef16b45 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -262,6 +262,16 @@ Loot::Loot() Loot::~Loot() { m_thread->wait(); + + if (!m_outPath.isEmpty()) { + const auto r = shell::Delete(m_outPath); + + if (!r) { + log::error( + "failed to remove temporary loot json report '{}': {}", + m_outPath, r.toString()); + } + } } bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) @@ -369,32 +379,39 @@ void Loot::lootThread() bool Loot::waitForCompletion() { - HANDLE waitHandle = m_lootProcess.get(); - DWORD res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); + bool terminating = false; - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (m_cancel) { - ::TerminateProcess(m_lootProcess.get(), 1); + for (;;) { + DWORD res = WaitForSingleObject(m_lootProcess.get(), 100); + + if (res == WAIT_OBJECT_0) { + // done + break; } - std::string lootOut = readFromPipe(); - processStdout(lootOut); + if (res == WAIT_FAILED) { + const auto e = GetLastError(); + log::error("failed to wait on loot process, {}", formatSystemMessage(e)); + return false; + } - res = ::MsgWaitForMultipleObjects(1, &waitHandle, false, 100, QS_KEY | QS_MOUSE); - } + if (m_cancel) { + // terminate and wait to finish + ::TerminateProcess(m_lootProcess.get(), 1); + WaitForSingleObject(m_lootProcess.get(), INFINITE); + return false; + } - const std::string remainder = readFromPipe(); - if (!remainder.empty()) { - processStdout(remainder); + processStdout(readFromPipe()); } if (m_cancel) { return false; } + processStdout(readFromPipe()); // checking exit code - DWORD exitCode = 0; if (!::GetExitCodeProcess(m_lootProcess.get(), &exitCode)) { @@ -559,113 +576,107 @@ void Loot::processOutputFile() return; } - if (!doc.isArray()) { - logJsonError("root is {}, not an array", jsonType(doc)); + if (!doc.isObject()) { + logJsonError("root is {}, not an object", jsonType(doc)); return; } - const QJsonArray array = doc.array(); + const QJsonObject object = doc.object(); - for (auto pluginValue : array) { - processOutputPlugin(pluginValue); - } -} + if (object.contains("messages")) { + const auto messagesValue = object["messages"]; -bool Loot::processOutputPlugin(const QJsonValue& pluginValue) -{ - if (!pluginValue.isObject()) { - logJsonError( - "value in root array is {}, not an object", jsonType(pluginValue)); - return false; - } + if (messagesValue.isArray()) { + processMessages(messagesValue.toArray()); + } else { + logJsonError( + "'messages' property is {}, not an array", jsonType(messagesValue)); + } - const auto plugin = pluginValue.toObject(); + } + if (object.contains("plugins")) { + const auto pluginsValue = object["plugins"]; - if (!plugin.contains("name")) { - logJsonError("plugin value doesn't have a 'name' property"); - return false; + if (pluginsValue.isArray()) { + processPlugins(pluginsValue.toArray()); + } else { + logJsonError( + "'plugins' property is {}, not an array", jsonType(pluginsValue)); + } } +} - const auto pluginNameValue = plugin["name"]; - if (!pluginNameValue.isString()) { - logJsonError( - "plugin property 'name' is {}, not a string", jsonType(pluginNameValue)); - return false; +bool Loot::processMessages(const QJsonArray& messages) +{ + for (auto messageValue : messages) { + if (messageValue.isObject()) { + processMessage(messageValue.toObject()); + } else { + logJsonError("a message is {}, not an object", jsonType(messageValue)); + } } - const auto pluginName = pluginNameValue.toString(); - - processPluginMessages(pluginName, plugin); - processPluginDirty(pluginName, plugin); - return true; } -bool Loot::processPluginMessages( - const QString& pluginName, const QJsonObject& plugin) +bool Loot::processMessage(const QJsonObject& message) { - if (!plugin.contains("messages")) { - return true; - } - - const auto messagesValue = plugin["messages"]; - - if (!messagesValue.isArray()) { - logJsonError( - "'messages' value for plugin '{}' is {}, not an array", - pluginName, jsonType(messagesValue)); + const auto messageType = message["type"].toString(); + const auto messageString = message["message"].toString(); + if (messageType.isEmpty()) { + logJsonError("there's a message with no 'type' property"); return false; } - const auto messages = messagesValue.toArray(); + if (messageString.isEmpty()) { + logJsonError("there's a message with no 'message' property"); + return false; + } + emit log(levelFromLoot( + lootcli::logLevelFromString(messageType.toStdString())), + messageString); - for (auto messageValue : messages) { - if (!messageValue.isObject()) { - logJsonError( - "plugin '{}' has a message that's {}, not an object", - pluginName, jsonType(messageValue)); + return true; +} - continue; +bool Loot::processPlugins(const QJsonArray& plugins) +{ + for (auto pluginValue : plugins) { + if (pluginValue.isObject()) { + processPlugin(pluginValue.toObject()); + } else { + logJsonError("a plugin is {}, not an object", jsonType(pluginValue)); } - - processPluginMessage(pluginName, messageValue.toObject()); } return true; } -bool Loot::processPluginMessage( - const QString& pluginName, const QJsonObject& message) +bool Loot::processPlugin(const QJsonObject& plugin) { - const auto messageType = message["type"].toString(); - const auto messageString = message["message"].toString(); - - if (messageType.isEmpty()) { - logJsonError( - "plugin '{}' has a message with no 'type' property", pluginName); + if (!plugin.contains("name")) { + logJsonError("plugin missing 'name' property"); return false; } - if (messageString.isEmpty()) { - logJsonError( - "plugin '{}' has a message with no 'message' property", pluginName); + const auto nameValue = plugin["name"]; + if (!nameValue.isString()) { + logJsonError("plugin property 'name' is {}, not a string", jsonType(nameValue)); return false; } - const auto info = QString("%1: %2") - .arg(messageType) - .arg(messageString); + const auto name = nameValue.toString(); + + processPluginDirty(name, plugin); - emit information(pluginName, info); return true; } -bool Loot::processPluginDirty( - const QString& pluginName, const QJsonObject& plugin) +bool Loot::processPluginDirty(const QString& name, const QJsonObject& plugin) { if (!plugin.contains("dirty")) { return true; @@ -676,7 +687,7 @@ bool Loot::processPluginDirty( if (!dirtyValue.isArray()) { logJsonError( "'dirty' value for plugin '{}' is {}, not an array", - pluginName, jsonType(dirtyValue)); + name, jsonType(dirtyValue)); return false; } @@ -688,7 +699,7 @@ bool Loot::processPluginDirty( if (!stringValue.isString()) { logJsonError( "'dirty' value for plugin '{}' is {}, not a string", - pluginName, jsonType(stringValue)); + name, jsonType(stringValue)); continue; } @@ -696,11 +707,11 @@ bool Loot::processPluginDirty( const auto string = stringValue.toString(); if (string.isEmpty()) { - logJsonError("'dirty' string for plugin '{}' is empty", pluginName); + logJsonError("'dirty' string for plugin '{}' is empty", name); continue; } - emit information(pluginName, string); + emit information(name, string); } return true; diff --git a/src/loot.h b/src/loot.h index 6dc7c9f3..54fc6fd1 100644 --- a/src/loot.h +++ b/src/loot.h @@ -50,16 +50,12 @@ private: void processMessage(const lootcli::Message& m); void processOutputFile(); - bool processOutputPlugin(const QJsonValue& pluginValue); + bool processMessages(const QJsonArray& messages); + bool processMessage(const QJsonObject& message); + bool processPlugins(const QJsonArray& plugins); + bool processPlugin(const QJsonObject& plugin); - bool processPluginMessages( - const QString& pluginName, const QJsonObject& plugin); - - bool processPluginMessage( - const QString& pluginName, const QJsonObject& message); - - bool processPluginDirty( - const QString& pluginName, const QJsonObject& plugin); + bool processPluginDirty(const QString& name, const QJsonObject& plugin); template void logJsonError(Format&& f, Args&&... args) -- cgit v1.3.1 From d649a86195112a9c5f135d1fd7b66e9ffd15d43f Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 23 Nov 2019 17:53:31 -0700 Subject: Sort the files when presenting them during querying info --- src/downloadmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index b4a7b57d..c84d4bd4 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1606,6 +1606,8 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); } else { SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); + std::sort(files.begin(), files.end(), [](const QVariant& lhs, const QVariant& rhs) + {return lhs.toMap()["uploaded_timestamp"].toInt() > rhs.toMap()["uploaded_timestamp"].toInt();}); for (QVariant file : files) { QVariantMap fileInfo = file.toMap(); if (fileInfo["category_id"].toInt() != 6) -- cgit v1.3.1 From 5c9de17b6376ce94b1cdff4f4edbba798e9bfd08 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 23 Nov 2019 22:08:53 -0500 Subject: rewrite of json report parsing added json.h with some utilities --- src/CMakeLists.txt | 2 + src/json.h | 190 ++++++++++++++++++++++++++++++ src/loot.cpp | 332 ++++++++++++++++++++++++++++++++--------------------- src/loot.h | 32 +++--- 4 files changed, 412 insertions(+), 144 deletions(-) create mode 100644 src/json.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 824ffa33..3b74ea37 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -265,6 +265,7 @@ SET(organizer_HDRS processrunner.h uilocker.h loot.h + json.h shared/windows_error.h shared/error_report.h @@ -468,6 +469,7 @@ set(utilities usvfsconnector shared/windows_error loot + json ) set(widgets diff --git a/src/json.h b/src/json.h new file mode 100644 index 00000000..d182f330 --- /dev/null +++ b/src/json.h @@ -0,0 +1,190 @@ +#ifndef MODORGANIZER_JSON_INCLUDED +#define MODORGANIZER_JSON_INCLUDED + +#include +#include +#include +#include +#include + +namespace json +{ + +class failed {}; + + +namespace details +{ + +QString typeName(const QJsonValue& v) +{ + if (v.isUndefined()) { + return "undefined"; + } else if (v.isNull()) { + return "null"; + } else if (v.isArray()) { + return "an array"; + } else if (v.isBool()) { + return "a bool"; + } else if (v.isDouble()) { + return "a double"; + } else if (v.isObject()) { + return "an object"; + } else if (v.isString()) { + return "a string"; + } else { + return "an unknown type"; + } +} + +QString typeName(const QJsonDocument& doc) +{ + if (doc.isEmpty()) { + return "empty"; + } else if (doc.isNull()) { + return "null"; + } else if (doc.isArray()) { + return "an array"; + } else if (doc.isObject()) { + return "an object"; + } else { + return "an unknown type"; + } +} + + +template +T convert(const QJsonValue& v) = delete; + +template <> +bool convert(const QJsonValue& v) +{ + if (!v.isBool()) { + throw failed(); + } + + return v.toBool(); +} + +template <> +QJsonObject convert(const QJsonValue& v) +{ + if (!v.isObject()) { + throw failed(); + } + + return v.toObject(); +} + +template <> +QString convert(const QJsonValue& v) +{ + if (!v.isString()) { + throw failed(); + } + + return v.toString(); +} + +template <> +QJsonArray convert(const QJsonValue& v) +{ + if (!v.isArray()) { + throw failed(); + } + + return v.toArray(); +} + +template <> +qint64 convert(const QJsonValue& v) +{ + if (!v.isDouble()) { + throw failed(); + } + + return static_cast(v.toDouble()); +} + +} // namespace + + +template +T convert(const QJsonValue& value, const char* what) +{ + try + { + return details::convert(value); + } + catch(failed&) + { + MOBase::log::error( + "'{}' is a {}, not a {}", + what, details::typeName(value), typeid(T).name); + + throw; + } +} + +template +T convertWarn(const QJsonValue& value, const char* what, T def={}) +{ + try + { + return details::convert(value); + } + catch(failed&) + { + MOBase::log::warn( + "'{}' is a {}, not a {}", + what, details::typeName(value), typeid(T).name()); + + return def; + } +} + +template +T get(const QJsonObject& o, const char* e) +{ + if (!o.contains(e)) { + MOBase::log::error("property '{}' is missing", e); + throw failed(); + } + + return convert(o[e], e); +} + +template +T getWarn(const QJsonObject& o, const char* e, T def={}) +{ + if (!o.contains(e)) { + MOBase::log::warn("property '{}' is missing", e); + return def; + } + + return convertWarn(o[e], e); +} + +template +T getOpt(const QJsonObject& o, const char* e, T def={}) +{ + if (!o.contains(e)) { + return def; + } + + return convertWarn(o[e], e); +} + + +template +void requireObject(const Value& v, const char* what) +{ + if (!v.isObject()) { + MOBase::log::error("{} is {}, not an object", what, details::typeName(v)); + throw failed(); + } +} + +} // namespace + +#endif // MODORGANIZER_JSON_INCLUDED diff --git a/src/loot.cpp b/src/loot.cpp index eef16b45..88ea8ce8 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -1,10 +1,12 @@ #include "loot.h" #include "spawn.h" #include "organizercore.h" +#include "json.h" #include #include using namespace MOBase; +using namespace json; log::Levels levelFromLoot(lootcli::LogLevels level) { @@ -193,7 +195,6 @@ private: ly->addLayout(buttons); m_output = new QPlainTextEdit; - m_output->setWordWrapMode(QTextOption::NoWrap); ly->addWidget(m_output); m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); @@ -254,6 +255,81 @@ private: }; +struct Loot::Message +{ + QString type; + QString text; +}; + +struct Loot::File +{ + QString name; + QString displayName; +}; + +struct Loot::Dirty +{ + qint64 crc=0; + qint64 itm=0; + qint64 deletedReferences=0; + qint64 deletedNavmesh=0; + QString cleaningUtility; + QString info; + + QString toString(bool isClean) const + { + if (isClean) { + return QObject::tr("Verified clean by %1") + .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility); + } + + QString s = cleaningString(); + + if (!info.isEmpty()) { + s += " " + info; + } + + return s; + } + + QString cleaningString() const + { + return QObject::tr("%1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es).") + .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility) + .arg(itm) + .arg(deletedReferences) + .arg(deletedNavmesh); + } +}; + +struct Loot::Plugin +{ + QString name; + std::vector incompatibilities; + std::vector messages; + std::vector dirty, clean; + std::vector missingMasters; + bool loadsArchive = false; + bool isMaster = false; + bool isLightMaster = false; +}; + +struct Loot::Stats +{ + qint64 time = 0; + QString version; +}; + +struct Loot::Report +{ + std::vector messages; + std::vector plugins; + Stats stats; +}; + + +class ReportFailed {}; + Loot::Loot() : m_thread(nullptr), m_cancel(false), m_result(false) { @@ -522,49 +598,16 @@ void Loot::processMessage(const lootcli::Message& m) } } -QString jsonType(const QJsonValue& v) -{ - if (v.isUndefined()) { - return "undefined"; - } else if (v.isNull()) { - return "null"; - } else if (v.isArray()) { - return "an array"; - } else if (v.isBool()) { - return "a bool"; - } else if (v.isDouble()) { - return "a double"; - } else if (v.isObject()) { - return "an object"; - } else if (v.isString()) { - return "a string"; - } else { - return "an unknown type"; - } -} - -QString jsonType(const QJsonDocument& doc) -{ - if (doc.isEmpty()) { - return "empty"; - } else if (doc.isNull()) { - return "null"; - } else if (doc.isArray()) { - return "an array"; - } else if (doc.isObject()) { - return "an object"; - } else { - return "an unknown type"; - } -} - void Loot::processOutputFile() { + log::info("parsing json output file at '{}'", m_outPath); + QFile outFile(m_outPath); if (!outFile.open(QIODevice::ReadOnly)) { - logJsonError( - "failed to open file, {} (error {})", - outFile.errorString(), outFile.error()); + emit log( + MOBase::log::Error, + QString("failed to open file, %1 (error %2)") + .arg(outFile.errorString()).arg(outFile.error())); return; } @@ -572,149 +615,178 @@ void Loot::processOutputFile() QJsonParseError e; const QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll(), &e); if (doc.isNull()) { - logJsonError("invalid json, {} (error {})", e.errorString(), e.error); - return; - } + emit log( + MOBase::log::Error, + QString("invalid json, %1 (error %2)") + .arg(e.errorString()).arg(e.error)); - if (!doc.isObject()) { - logJsonError("root is {}, not an object", jsonType(doc)); return; } - const QJsonObject object = doc.object(); + const auto report = createReport(doc); - if (object.contains("messages")) { - const auto messagesValue = object["messages"]; + for (auto&& m : report.messages) { + emit log(levelFromLoot( + lootcli::logLevelFromString(m.type.toStdString())), + m.text); + } - if (messagesValue.isArray()) { - processMessages(messagesValue.toArray()); - } else { - logJsonError( - "'messages' property is {}, not an array", jsonType(messagesValue)); + for (auto&& p : report.plugins) { + for (auto&& d : p.dirty) { + emit information(p.name, d.toString(false)); } - } +} - if (object.contains("plugins")) { - const auto pluginsValue = object["plugins"]; +Loot::Report Loot::createReport(const QJsonDocument& doc) const +{ + requireObject(doc, "root"); - if (pluginsValue.isArray()) { - processPlugins(pluginsValue.toArray()); - } else { - logJsonError( - "'plugins' property is {}, not an array", jsonType(pluginsValue)); - } - } + Report r; + const QJsonObject object = doc.object(); + + r.messages = reportMessages(getOpt(object, "messages")); + r.plugins = reportPlugins(getOpt(object, "plugins")); + + return r; } -bool Loot::processMessages(const QJsonArray& messages) +std::vector Loot::reportPlugins(const QJsonArray& plugins) const { - for (auto messageValue : messages) { - if (messageValue.isObject()) { - processMessage(messageValue.toObject()); - } else { - logJsonError("a message is {}, not an object", jsonType(messageValue)); + std::vector v; + + for (auto pluginValue : plugins) { + const auto o = convertWarn(pluginValue, "plugin"); + if (o.isEmpty()) { + continue; + } + + auto p = reportPlugin(o); + if (!p.name.isEmpty()) { + v.emplace_back(std::move(p)); } } - return true; + return v; } -bool Loot::processMessage(const QJsonObject& message) +Loot::Plugin Loot::reportPlugin(const QJsonObject& plugin) const { - const auto messageType = message["type"].toString(); - const auto messageString = message["message"].toString(); + Plugin p; - if (messageType.isEmpty()) { - logJsonError("there's a message with no 'type' property"); - return false; + p.name = getWarn(plugin, "name"); + if (p.name.isEmpty()) { + return {}; } - if (messageString.isEmpty()) { - logJsonError("there's a message with no 'message' property"); - return false; + if (plugin.contains("incompatibilities")) { + p.incompatibilities = reportFiles(getOpt(plugin, "incompatibilities")); } - emit log(levelFromLoot( - lootcli::logLevelFromString(messageType.toStdString())), - messageString); + if (plugin.contains("messages")) { + p.messages = reportMessages(getOpt(plugin, "messages")); + } - return true; -} + if (plugin.contains("dirty")) { + p.dirty = reportDirty(getOpt(plugin, "dirty")); + } -bool Loot::processPlugins(const QJsonArray& plugins) -{ - for (auto pluginValue : plugins) { - if (pluginValue.isObject()) { - processPlugin(pluginValue.toObject()); - } else { - logJsonError("a plugin is {}, not an object", jsonType(pluginValue)); - } + if (plugin.contains("clean")) { + p.clean = reportDirty(getOpt(plugin, "clean")); } - return true; + if (plugin.contains("missingMasters")) { + p.missingMasters = reportStringArray(getOpt(plugin, "missingMasters")); + } + + p.loadsArchive = getOpt(plugin, "loadsArchive", false); + p.isMaster = getOpt(plugin, "isMaster", false); + p.isLightMaster = getOpt(plugin, "isLightMaster", false); + + return p; } -bool Loot::processPlugin(const QJsonObject& plugin) +std::vector Loot::reportMessages(const QJsonArray& array) const { - if (!plugin.contains("name")) { - logJsonError("plugin missing 'name' property"); - return false; - } + std::vector v; - const auto nameValue = plugin["name"]; - if (!nameValue.isString()) { - logJsonError("plugin property 'name' is {}, not a string", jsonType(nameValue)); - return false; - } + for (auto messageValue : array) { + const auto o = convertWarn(messageValue, "message"); + if (o.isEmpty()) { + continue; + } - const auto name = nameValue.toString(); + Message m; + m.type = getWarn(o, "type"); + m.text = getWarn(o, "text"); - processPluginDirty(name, plugin); + if (!m.text.isEmpty()) { + v.emplace_back(std::move(m)); + } + } - return true; + return v; } - -bool Loot::processPluginDirty(const QString& name, const QJsonObject& plugin) +std::vector Loot::reportFiles(const QJsonArray& array) const { - if (!plugin.contains("dirty")) { - return true; - } + std::vector v; + + for (auto&& fileValue : array) { + const auto o = convertWarn(fileValue, "file"); + if (o.isEmpty()) { + continue; + } - const auto dirtyValue = plugin["dirty"]; + File f; - if (!dirtyValue.isArray()) { - logJsonError( - "'dirty' value for plugin '{}' is {}, not an array", - name, jsonType(dirtyValue)); + f.name = getWarn(o, "name"); + f.displayName = getOpt(o, "displayName"); - return false; + if (!f.name.isEmpty()) { + v.emplace_back(std::move(f)); + } } - const auto dirty = dirtyValue.toArray(); + return v; +} + +std::vector Loot::reportDirty(const QJsonArray& array) const +{ + std::vector v; + for (auto&& dirtyValue : array) { + const auto o = convertWarn(dirtyValue, "dirty"); - for (auto stringValue : dirty) { - if (!stringValue.isString()) { - logJsonError( - "'dirty' value for plugin '{}' is {}, not a string", - name, jsonType(stringValue)); + Dirty d; - continue; - } + d.crc = getWarn(o, "crc"); + d.itm = getOpt(o, "itm"); + d.deletedReferences = getOpt(o, "deletedReferences"); + d.deletedNavmesh = getOpt(o, "deletedNavmesh"); + d.cleaningUtility = getOpt(o, "cleaningUtility"); + d.info = getOpt(o, "info"); - const auto string = stringValue.toString(); + v.emplace_back(std::move(d)); + } + + return v; +} - if (string.isEmpty()) { - logJsonError("'dirty' string for plugin '{}' is empty", name); +std::vector Loot::reportStringArray(const QJsonArray& array) const +{ + std::vector v; + + for (auto&& sv : array) { + auto s = convertWarn(sv, "string"); + if (s.isEmpty()) { continue; } - emit information(name, string); + v.emplace_back(std::move(s)); } - return true; + return v; } diff --git a/src/loot.h b/src/loot.h index 54fc6fd1..95dbbe50 100644 --- a/src/loot.h +++ b/src/loot.h @@ -33,6 +33,14 @@ signals: void finished(); private: + struct Report; + struct Stats; + struct Message; + struct Plugin; + struct Dirty; + struct File; + class BadReport {}; + std::unique_ptr m_thread; std::atomic m_cancel; std::atomic m_result; @@ -50,20 +58,16 @@ private: void processMessage(const lootcli::Message& m); void processOutputFile(); - bool processMessages(const QJsonArray& messages); - bool processMessage(const QJsonObject& message); - bool processPlugins(const QJsonArray& plugins); - bool processPlugin(const QJsonObject& plugin); - - bool processPluginDirty(const QString& name, const QJsonObject& plugin); - - template - void logJsonError(Format&& f, Args&&... args) - { - MOBase::log::error( - std::string("loot output file '{}': ") + f, - m_outPath, std::forward(args)...); - }; + + Report createReport(const QJsonDocument& doc) const; + Message reportMessage(const QJsonObject& message) const; + std::vector reportPlugins(const QJsonArray& plugins) const; + Loot::Plugin reportPlugin(const QJsonObject& plugin) const; + + std::vector reportMessages(const QJsonArray& array) const; + std::vector reportFiles(const QJsonArray& array) const; + std::vector reportDirty(const QJsonArray& array) const; + std::vector reportStringArray(const QJsonArray& array) const; }; -- cgit v1.3.1 From 3a085212c939ae8c5e6022a4c9bddfb7df95400f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 23 Nov 2019 22:35:08 -0500 Subject: added loot report to the plugin list, not used yet split PluginList::data() into individual functions disabled loot message processing, will use report instead --- src/loot.cpp | 131 ++++++++---------------- src/loot.h | 62 ++++++++++-- src/pluginlist.cpp | 293 +++++++++++++++++++++++++++++++++-------------------- src/pluginlist.h | 17 ++++ 4 files changed, 295 insertions(+), 208 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 88ea8ce8..66e8a01d 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -57,10 +57,6 @@ public: &m_loot, &Loot::log, this, [&](auto&& lv, auto&& s){ log(lv, s); }, Qt::QueuedConnection); - QObject::connect( - &m_loot, &Loot::information, this, - [&](auto&& mod, auto&& i){ setInfo(mod, i); }, Qt::QueuedConnection); - QObject::connect( &m_loot, &Loot::finished, this, [&]{ onFinished(); }, Qt::QueuedConnection); @@ -116,11 +112,6 @@ public: } } - void setInfo(const QString& mod, const QString& info) - { - m_core.pluginList()->addInformation(mod.toStdString().c_str(), info); - } - bool result() const { return m_loot.result(); @@ -237,6 +228,7 @@ private: if (m_cancelling) { close(); } else { + handleReport(); m_report->setEnabled(true); m_buttons->setStandardButtons(QDialogButtonBox::Close); } @@ -252,83 +244,55 @@ private: addLineOutput(QString("[%1] %2").arg(log::levelToString(lv)).arg(s)); } } -}; - -struct Loot::Message -{ - QString type; - QString text; -}; - -struct Loot::File -{ - QString name; - QString displayName; -}; - -struct Loot::Dirty -{ - qint64 crc=0; - qint64 itm=0; - qint64 deletedReferences=0; - qint64 deletedNavmesh=0; - QString cleaningUtility; - QString info; - - QString toString(bool isClean) const + void handleReport() { - if (isClean) { - return QObject::tr("Verified clean by %1") - .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility); - } - - QString s = cleaningString(); + const auto& report = m_loot.report(); - if (!info.isEmpty()) { - s += " " + info; + if (!report.messages.empty()) { + addLineOutput(""); } - return s; - } + for (auto&& m : report.messages) { + log(levelFromLoot( + lootcli::logLevelFromString(m.type.toStdString())), + m.text); + } - QString cleaningString() const - { - return QObject::tr("%1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es).") - .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility) - .arg(itm) - .arg(deletedReferences) - .arg(deletedNavmesh); + for (auto&& p : report.plugins) { + for (auto&& d : p.dirty) { + m_core.pluginList()->addInformation(p.name, d.toString(false)); + } + } } }; -struct Loot::Plugin -{ - QString name; - std::vector incompatibilities; - std::vector messages; - std::vector dirty, clean; - std::vector missingMasters; - bool loadsArchive = false; - bool isMaster = false; - bool isLightMaster = false; -}; -struct Loot::Stats +QString Loot::Dirty::toString(bool isClean) const { - qint64 time = 0; - QString version; -}; + if (isClean) { + return QObject::tr("Verified clean by %1") + .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility); + } -struct Loot::Report -{ - std::vector messages; - std::vector plugins; - Stats stats; -}; + QString s = cleaningString(); + if (!info.isEmpty()) { + s += " " + info; + } + + return s; +} + +QString Loot::Dirty::cleaningString() const +{ + return QObject::tr("%1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es).") + .arg(cleaningUtility.isEmpty() ? "?" : cleaningUtility) + .arg(itm) + .arg(deletedReferences) + .arg(deletedNavmesh); +} -class ReportFailed {}; Loot::Loot() : m_thread(nullptr), m_cancel(false), m_result(false) @@ -437,6 +401,11 @@ const QString& Loot::outPath() const return m_outPath; } +const Loot::Report& Loot::report() const +{ + return m_report; +} + void Loot::lootThread() { try { @@ -558,7 +527,7 @@ void Loot::processStdout(const std::string &lootOut) void Loot::processMessage(const lootcli::Message& m) { - static const std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + /*static const std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); static const std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); switch (m.type) @@ -595,7 +564,7 @@ void Loot::processMessage(const lootcli::Message& m) emit progress(m.progress); break; } - } + }*/ } void Loot::processOutputFile() @@ -623,19 +592,7 @@ void Loot::processOutputFile() return; } - const auto report = createReport(doc); - - for (auto&& m : report.messages) { - emit log(levelFromLoot( - lootcli::logLevelFromString(m.type.toStdString())), - m.text); - } - - for (auto&& p : report.plugins) { - for (auto&& d : p.dirty) { - emit information(p.name, d.toString(false)); - } - } + m_report = createReport(doc); } Loot::Report Loot::createReport(const QJsonDocument& doc) const diff --git a/src/loot.h b/src/loot.h index 95dbbe50..dc9b0d7b 100644 --- a/src/loot.h +++ b/src/loot.h @@ -17,6 +17,57 @@ class Loot : public QObject Q_OBJECT; public: + struct Message + { + QString type; + QString text; + }; + + struct File + { + QString name; + QString displayName; + }; + + struct Dirty + { + qint64 crc=0; + qint64 itm=0; + qint64 deletedReferences=0; + qint64 deletedNavmesh=0; + QString cleaningUtility; + QString info; + + QString toString(bool isClean) const; + QString cleaningString() const; + }; + + struct Plugin + { + QString name; + std::vector incompatibilities; + std::vector messages; + std::vector dirty, clean; + std::vector missingMasters; + bool loadsArchive = false; + bool isMaster = false; + bool isLightMaster = false; + }; + + struct Stats + { + qint64 time = 0; + QString version; + }; + + struct Report + { + std::vector messages; + std::vector plugins; + Stats stats; + }; + + Loot(); ~Loot(); @@ -24,23 +75,15 @@ public: void cancel(); bool result() const; const QString& outPath() const; + const Report& report() const; signals: void output(const QString& s); void progress(const lootcli::Progress p); void log(MOBase::log::Levels level, const QString& s); - void information(const QString& mod, const QString& info); void finished(); private: - struct Report; - struct Stats; - struct Message; - struct Plugin; - struct Dirty; - struct File; - class BadReport {}; - std::unique_ptr m_thread; std::atomic m_cancel; std::atomic m_result; @@ -48,6 +91,7 @@ private: env::HandlePtr m_lootProcess; env::HandlePtr m_stdout; std::string m_outputBuffer; + Report m_report; std::string readFromPipe(); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index b50a51d8..ab421f2b 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -423,6 +423,17 @@ void PluginList::addInformation(const QString &name, const QString &message) } } +void PluginList::addLootReport(const QString& name, Loot::Plugin plugin) +{ + auto iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name.toLower()].m_Loot = std::move(plugin); + } else { + log::warn("failed to associate loot report for \"{}\"", name); + } +} + bool PluginList::isEnabled(int index) { return m_ESPs.at(index).m_Enabled; @@ -908,137 +919,195 @@ void PluginList::testMasters() QVariant PluginList::data(const QModelIndex &modelIndex, int role) const { int index = modelIndex.row(); - if ((role == Qt::DisplayRole) - || (role == Qt::EditRole)) { - switch (modelIndex.column()) { - case COL_NAME: { - return m_ESPs[index].m_Name; - } break; - case COL_PRIORITY: { - return m_ESPs[index].m_Priority; - } break; - case COL_MODINDEX: { - return m_ESPs[index].m_Index; - } break; - default: { - return QVariant(); - } break; - } + + if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { + return displayData(modelIndex); } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { - if (m_ESPs[index].m_ForceEnabled) { - return QVariant(); - } else { - return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; - } + return checkstateData(modelIndex); } else if (role == Qt::ForegroundRole) { - if ((modelIndex.column() == COL_NAME) && - m_ESPs[index].m_ForceEnabled) { - return QBrush(Qt::gray); - } - } else if (role == Qt::BackgroundRole - || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { - if (m_ESPs[index].m_ModSelected) { - return Settings::instance().colors().pluginListContained(); - } else { - return QVariant(); - } + return foregroundData(modelIndex); + } else if (role == Qt::BackgroundRole || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + return backgroundData(modelIndex); } else if (role == Qt::FontRole) { - QFont result; - if (m_ESPs[index].m_IsMaster) { - result.setItalic(true); - result.setWeight(QFont::Bold); - } else if (m_ESPs[index].m_IsLight || m_ESPs[index].m_IsLightFlagged) { - result.setItalic(true); - } - return result; + return fontData(modelIndex); } else if (role == Qt::TextAlignmentRole) { - if (modelIndex.column() == 0) { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); - } + return alignmentData(modelIndex); } else if (role == Qt::ToolTipRole) { - QString name = m_ESPs[index].m_Name.toLower(); - auto addInfoIter = m_AdditionalInfo.find(name); - QString toolTip; - if (addInfoIter != m_AdditionalInfo.end()) { - if (!addInfoIter->second.m_Messages.isEmpty()) { - toolTip += "
      "; - for (auto&& message : addInfoIter->second.m_Messages) { - toolTip += "
    • " + message + "
    • "; - } - toolTip += "

    "; + return tooltipData(modelIndex); + } else if (role == Qt::UserRole + 1) { + return iconData(modelIndex); + } + return QVariant(); +} + +QVariant PluginList::displayData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + switch (modelIndex.column()) { + case COL_NAME: { + return m_ESPs[index].m_Name; + } break; + case COL_PRIORITY: { + return m_ESPs[index].m_Priority; + } break; + case COL_MODINDEX: { + return m_ESPs[index].m_Index; + } break; + default: { + return QVariant(); + } break; + } +} + +QVariant PluginList::checkstateData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + if (m_ESPs[index].m_ForceEnabled) { + return QVariant(); + } else { + return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; + } +} + +QVariant PluginList::foregroundData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + if ((modelIndex.column() == COL_NAME) && + m_ESPs[index].m_ForceEnabled) { + return QBrush(Qt::gray); + } + + return {}; +} + +QVariant PluginList::backgroundData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + if (m_ESPs[index].m_ModSelected) { + return Settings::instance().colors().pluginListContained(); + } else { + return QVariant(); + } +} + +QVariant PluginList::fontData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + QFont result; + + if (m_ESPs[index].m_IsMaster) { + result.setItalic(true); + result.setWeight(QFont::Bold); + } else if (m_ESPs[index].m_IsLight || m_ESPs[index].m_IsLightFlagged) { + result.setItalic(true); + } + + return result; +} + +QVariant PluginList::alignmentData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + if (modelIndex.column() == 0) { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); + } +} + +QVariant PluginList::tooltipData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + QString name = m_ESPs[index].m_Name.toLower(); + auto addInfoIter = m_AdditionalInfo.find(name); + QString toolTip; + if (addInfoIter != m_AdditionalInfo.end()) { + if (!addInfoIter->second.m_Messages.isEmpty()) { + toolTip += "
      "; + for (auto&& message : addInfoIter->second.m_Messages) { + toolTip += "
    • " + message + "
    • "; } + toolTip += "

    "; } - if (m_ESPs[index].m_ForceEnabled) { - QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); - text += tr("
    This plugin can't be disabled (enforced by the game)."); - toolTip += text; - } else { - QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); - if (m_ESPs[index].m_Author.size() > 0) { - text += "
    " + tr("Author") + ": " + TruncateString(m_ESPs[index].m_Author); - } - if (m_ESPs[index].m_Description.size() > 0) { - text += "
    " + tr("Description") + ": " + TruncateString(m_ESPs[index].m_Description); - } - if (m_ESPs[index].m_MasterUnset.size() > 0) { - text += "
    " + tr("Missing Masters") + ": " + TruncateString(SetJoin(m_ESPs[index].m_MasterUnset, ", ")) + ""; - } - std::set enabledMasters; - std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), - m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), - std::inserter(enabledMasters, enabledMasters.end())); - if (!enabledMasters.empty()) { - text += "
    " + tr("Enabled Masters") + ": " + TruncateString(SetJoin(enabledMasters, ", ")); - } - if (!m_ESPs[index].m_Archives.empty()) { - text += "
    " + tr("Loads Archives") + ": " + TruncateString(SetJoin(m_ESPs[index].m_Archives, ", ")); - text += "
    " + tr("There are Archives connected to this plugin. " - "Their assets will be added to your game, overwriting in case of conflicts following the plugin order. " - "Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin)"); - } - if (m_ESPs[index].m_HasIni) { - text += "
    " + tr("Loads INI settings") + ": "; - text += "
    " + tr("There is an ini file connected to this plugin. " - "Its settings will be added to your game settings, overwriting in case of conflicts."); - } - if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { - text += "

    " + tr("This ESP is flagged as an ESL. " - "It will adhere to the ESP load order but the records will be loaded in ESL space."); - } - toolTip += text; + } + if (m_ESPs[index].m_ForceEnabled) { + QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); + text += tr("
    This plugin can't be disabled (enforced by the game)."); + toolTip += text; + } else { + QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); + if (m_ESPs[index].m_Author.size() > 0) { + text += "
    " + tr("Author") + ": " + TruncateString(m_ESPs[index].m_Author); + } + if (m_ESPs[index].m_Description.size() > 0) { + text += "
    " + tr("Description") + ": " + TruncateString(m_ESPs[index].m_Description); } - return toolTip; - } else if (role == Qt::UserRole + 1) { - QVariantList result; - QString nameLower = m_ESPs[index].m_Name.toLower(); if (m_ESPs[index].m_MasterUnset.size() > 0) { - result.append(":/MO/gui/warning"); + text += "
    " + tr("Missing Masters") + ": " + TruncateString(SetJoin(m_ESPs[index].m_MasterUnset, ", ")) + ""; } - if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { - result.append(":/MO/gui/locked"); + std::set enabledMasters; + std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), + m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), + std::inserter(enabledMasters, enabledMasters.end())); + if (!enabledMasters.empty()) { + text += "
    " + tr("Enabled Masters") + ": " + TruncateString(SetJoin(enabledMasters, ", ")); } - auto bossInfoIter = m_AdditionalInfo.find(nameLower); - if (bossInfoIter != m_AdditionalInfo.end()) { - if (!bossInfoIter->second.m_Messages.isEmpty()) { - result.append(":/MO/gui/information"); - } + if (!m_ESPs[index].m_Archives.empty()) { + text += "
    " + tr("Loads Archives") + ": " + TruncateString(SetJoin(m_ESPs[index].m_Archives, ", ")); + text += "
    " + tr("There are Archives connected to this plugin. " + "Their assets will be added to your game, overwriting in case of conflicts following the plugin order. " + "Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin)"); } if (m_ESPs[index].m_HasIni) { - result.append(":/MO/gui/attachment"); - } - if (!m_ESPs[index].m_Archives.empty()) { - result.append(":/MO/gui/archive_conflict_neutral"); + text += "
    " + tr("Loads INI settings") + ": "; + text += "
    " + tr("There is an ini file connected to this plugin. " + "Its settings will be added to your game settings, overwriting in case of conflicts."); } if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { - result.append(":/MO/gui/awaiting"); + text += "

    " + tr("This ESP is flagged as an ESL. " + "It will adhere to the ESP load order but the records will be loaded in ESL space."); } - return result; + toolTip += text; } - return QVariant(); + return toolTip; } +QVariant PluginList::iconData(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + + QVariantList result; + QString nameLower = m_ESPs[index].m_Name.toLower(); + if (m_ESPs[index].m_MasterUnset.size() > 0) { + result.append(":/MO/gui/warning"); + } + if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { + result.append(":/MO/gui/locked"); + } + auto bossInfoIter = m_AdditionalInfo.find(nameLower); + if (bossInfoIter != m_AdditionalInfo.end()) { + if (!bossInfoIter->second.m_Messages.isEmpty()) { + result.append(":/MO/gui/information"); + } + } + if (m_ESPs[index].m_HasIni) { + result.append(":/MO/gui/attachment"); + } + if (!m_ESPs[index].m_Archives.empty()) { + result.append(":/MO/gui/archive_conflict_neutral"); + } + if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { + result.append(":/MO/gui/awaiting"); + } + return result; +} bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) { diff --git a/src/pluginlist.h b/src/pluginlist.h index 092ba378..5cbe0a17 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -23,6 +23,8 @@ along with Mod Organizer. If not, see . #include #include #include "profile.h" +#include "loot.h" + namespace MOBase { class IPluginGame; } #include @@ -154,6 +156,11 @@ public: */ void addInformation(const QString &name, const QString &message); + /** + * adds information from a loot report + */ + void addLootReport(const QString& name, Loot::Plugin plugin); + /** * @brief test if a plugin is enabled * @@ -324,6 +331,7 @@ private: struct AdditionalInfo { QStringList m_Messages; + Loot::Plugin m_Loot; }; friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); @@ -372,6 +380,15 @@ private: const MOBase::IPluginGame *m_GamePlugin; + + QVariant displayData(const QModelIndex &modelIndex) const; + QVariant checkstateData(const QModelIndex &modelIndex) const; + QVariant foregroundData(const QModelIndex &modelIndex) const; + QVariant backgroundData(const QModelIndex &modelIndex) const; + QVariant fontData(const QModelIndex &modelIndex) const; + QVariant alignmentData(const QModelIndex &modelIndex) const; + QVariant tooltipData(const QModelIndex &modelIndex) const; + QVariant iconData(const QModelIndex &modelIndex) const; }; #pragma warning(pop) -- cgit v1.3.1 From 3728db1527814e3307d443319861db438787627c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 23 Nov 2019 22:56:17 -0500 Subject: removed m_ prefix from struct members some refactoring, whitespace --- src/pluginlist.cpp | 399 ++++++++++++++++++++++++++++------------------------- src/pluginlist.h | 59 ++++---- 2 files changed, 241 insertions(+), 217 deletions(-) (limited to 'src') diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index ab421f2b..52c3fc3c 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -56,33 +56,40 @@ using namespace MOBase; using namespace MOShared; -static bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - return LHS.m_Name.toUpper() < RHS.m_Name.toUpper(); +static bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) +{ + return LHS.name.toUpper() < RHS.name.toUpper(); } -static bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - if (LHS.m_IsMaster && !RHS.m_IsMaster) { +static bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) +{ + if (LHS.isMaster && !RHS.isMaster) { return true; - } else if (!LHS.m_IsMaster && RHS.m_IsMaster) { + } else if (!LHS.isMaster && RHS.isMaster) { return false; } else { - return LHS.m_Priority < RHS.m_Priority; + return LHS.priority < RHS.priority; } } -static bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - return QFileInfo(LHS.m_FullPath).lastModified() < QFileInfo(RHS.m_FullPath).lastModified(); +static bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) +{ + return QFileInfo(LHS.fullPath).lastModified() < QFileInfo(RHS.fullPath).lastModified(); } -static QString TruncateString(const QString& text) { +static QString TruncateString(const QString& text) +{ QString new_text = text; + if (new_text.length() > 1024) { new_text.truncate(1024); new_text += "..."; } + return new_text; } + PluginList::PluginList(QObject *parent) : QAbstractItemModel(parent) , m_FontMetrics(QFont()) @@ -125,8 +132,9 @@ QString PluginList::getColumnToolTip(int column) void PluginList::highlightPlugins(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile) { for (auto &esp : m_ESPs) { - esp.m_ModSelected = false; + esp.modSelected = false; } + for (QModelIndex idx : selection->selectedRows(ModList::COL_PRIORITY)) { int modIndex = idx.data(Qt::UserRole + 1).toInt(); if (modIndex == UINT_MAX) @@ -147,12 +155,13 @@ void PluginList::highlightPlugins(const QItemSelectionModel *selection, const MO } std::map::iterator iter = m_ESPsByName.find(plugin.toLower()); if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_ModSelected = true; + m_ESPs[iter->second].modSelected = true; } } } } } + emit dataChanged(this->index(0, 0), this->index(static_cast(m_ESPs.size()) - 1, this->columnCount() - 1)); } @@ -225,7 +234,7 @@ void PluginList::refresh(const QString &profileName } m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni, loadedArchives, lightPluginsAreSupported)); - m_ESPs.rbegin()->m_Priority = -1; + m_ESPs.rbegin()->priority = -1; } catch (const std::exception &e) { reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); } @@ -234,13 +243,13 @@ void PluginList::refresh(const QString &profileName for (const auto &espName : m_ESPsByName) { if (!availablePlugins.contains(espName.first)) { - m_ESPs[espName.second].m_Name = ""; + m_ESPs[espName.second].name = ""; } } m_ESPs.erase(std::remove_if(m_ESPs.begin(), m_ESPs.end(), [](const ESPInfo &info) -> bool { - return info.m_Name.isEmpty(); + return info.name.isEmpty(); }), m_ESPs.end()); @@ -273,7 +282,7 @@ void PluginList::fixPriorities() std::vector> espPrios; for (int i = 0; i < m_ESPs.size(); ++i) { - int prio = m_ESPs[i].m_Priority; + int prio = m_ESPs[i].priority; if (prio == -1) { prio = INT_MAX; } @@ -286,7 +295,7 @@ void PluginList::fixPriorities() }); for (int i = 0; i < espPrios.size(); ++i) { - m_ESPs[espPrios[i].second].m_Priority = i; + m_ESPs[espPrios[i].second].priority = i; } } @@ -295,8 +304,8 @@ void PluginList::enableESP(const QString &name, bool enable) std::map::iterator iter = m_ESPsByName.find(name.toLower()); if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = - enable | m_ESPs[iter->second].m_ForceEnabled; + m_ESPs[iter->second].enabled = + enable | m_ESPs[iter->second].forceEnabled; emit writePluginsList(); } else { @@ -307,7 +316,7 @@ void PluginList::enableESP(const QString &name, bool enable) int PluginList::findPluginByPriority(int priority) { for (int i = 0; i < m_ESPs.size(); i++ ) { - if (m_ESPs[i].m_Priority == priority) { + if (m_ESPs[i].priority == priority) { return i; } } @@ -321,8 +330,8 @@ void PluginList::enableSelected(const QItemSelectionModel *selectionModel) bool dirty = false; for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { int rowIndex = findPluginByPriority(row.data().toInt()); - if (!m_ESPs[rowIndex].m_Enabled) { - m_ESPs[rowIndex].m_Enabled = true; + if (!m_ESPs[rowIndex].enabled) { + m_ESPs[rowIndex].enabled = true; dirty = true; } } @@ -336,8 +345,8 @@ void PluginList::disableSelected(const QItemSelectionModel *selectionModel) bool dirty = false; for (auto row : selectionModel->selectedRows(COL_PRIORITY)) { int rowIndex = findPluginByPriority(row.data().toInt()); - if (!m_ESPs[rowIndex].m_ForceEnabled && m_ESPs[rowIndex].m_Enabled) { - m_ESPs[rowIndex].m_Enabled = false; + if (!m_ESPs[rowIndex].forceEnabled && m_ESPs[rowIndex].enabled) { + m_ESPs[rowIndex].enabled = false; dirty = true; } } @@ -351,7 +360,7 @@ void PluginList::enableAll() if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { for (ESPInfo &info : m_ESPs) { - info.m_Enabled = true; + info.enabled = true; } emit writePluginsList(); } @@ -363,8 +372,8 @@ void PluginList::disableAll() if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { for (ESPInfo &info : m_ESPs) { - if (!info.m_ForceEnabled) { - info.m_Enabled = false; + if (!info.forceEnabled) { + info.enabled = false; } } emit writePluginsList(); @@ -377,7 +386,7 @@ void PluginList::sendToPriority(const QItemSelectionModel *selectionModel, int n std::vector pluginsToMove; for (auto row: selectionModel->selectedRows(COL_PRIORITY)) { int rowIndex = findPluginByPriority(row.data().toInt()); - if (!m_ESPs[rowIndex].m_ForceEnabled) { + if (!m_ESPs[rowIndex].forceEnabled) { pluginsToMove.push_back(rowIndex); } } @@ -392,7 +401,7 @@ bool PluginList::isEnabled(const QString &name) std::map::iterator iter = m_ESPsByName.find(name.toLower()); if (iter != m_ESPsByName.end()) { - return m_ESPs[iter->second].m_Enabled; + return m_ESPs[iter->second].enabled; } else { return false; } @@ -403,7 +412,7 @@ void PluginList::clearInformation(const QString &name) std::map::iterator iter = m_ESPsByName.find(name.toLower()); if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.clear(); + m_AdditionalInfo[name.toLower()].messages.clear(); } } @@ -417,7 +426,7 @@ void PluginList::addInformation(const QString &name, const QString &message) std::map::iterator iter = m_ESPsByName.find(name.toLower()); if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.append(message); + m_AdditionalInfo[name.toLower()].messages.append(message); } else { log::warn("failed to associate message for \"{}\"", name); } @@ -428,7 +437,7 @@ void PluginList::addLootReport(const QString& name, Loot::Plugin plugin) auto iter = m_ESPsByName.find(name.toLower()); if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Loot = std::move(plugin); + m_AdditionalInfo[name.toLower()].loot = std::move(plugin); } else { log::warn("failed to associate loot report for \"{}\"", name); } @@ -436,7 +445,7 @@ void PluginList::addLootReport(const QString& name, Loot::Plugin plugin) bool PluginList::isEnabled(int index) { - return m_ESPs.at(index).m_Enabled; + return m_ESPs.at(index).enabled; } void PluginList::readLockedOrderFrom(const QString &fileName) @@ -461,15 +470,15 @@ void PluginList::readLockedOrderFrom(const QString &fileName) int priority = fields.at(1).trimmed().toInt(); QString name = QString::fromUtf8(fields.at(0)); // Avoid locking a force-enabled plugin - if (!m_ESPs[m_ESPsByName.at(name)].m_ForceEnabled) { + if (!m_ESPs[m_ESPsByName.at(name)].forceEnabled) { // Is this an open and unclaimed priority? - if (m_ESPs[m_ESPsByPriority.at(priority)].m_ForceEnabled || + if (m_ESPs[m_ESPsByPriority.at(priority)].forceEnabled || std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), [&](const std::pair &a) { return a.second == priority; }) != m_LockedOrder.end()) { // Attempt to find a priority but step over force-enabled plugins and already-set locks int calcPriority = priority; do { ++calcPriority; - } while (calcPriority < m_ESPsByPriority.size() || (m_ESPs[m_ESPsByPriority.at(calcPriority)].m_ForceEnabled && + } while (calcPriority < m_ESPsByPriority.size() || (m_ESPs[m_ESPsByPriority.at(calcPriority)].forceEnabled && std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), [&](const std::pair &a) { return a.second == calcPriority; }) != m_LockedOrder.end())); // If we have a match, we can reassign the priority... if (calcPriority < m_ESPsByPriority.size()) @@ -517,8 +526,8 @@ void PluginList::saveTo(const QString &lockedOrderFileName for (size_t i = 0; i < m_ESPs.size(); ++i) { int priority = m_ESPsByPriority[i]; - if (!m_ESPs[priority].m_Enabled) { - deleterFile->write(m_ESPs[priority].m_Name.toUtf8()); + if (!m_ESPs[priority].enabled) { + deleterFile->write(m_ESPs[priority].name.toUtf8()); deleterFile->write("\r\n"); } } @@ -541,13 +550,16 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) log::debug("setting file times on esps"); for (ESPInfo &esp : m_ESPs) { - std::wstring espName = ToWString(esp.m_Name); + std::wstring espName = ToWString(esp.name); const FileEntry::Ptr fileEntry = directoryStructure.findFile(espName); if (fileEntry.get() != nullptr) { QString fileName; bool archive = false; int originid = fileEntry->getOrigin(archive); - fileName = QString("%1\\%2").arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))).arg(esp.m_Name); + + fileName = QString("%1\\%2") + .arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))) + .arg(esp.name); HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); @@ -561,13 +573,13 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) } ULONGLONG temp = 0; - temp = (145731ULL + esp.m_Priority) * 24 * 60 * 60 * 10000000ULL; + temp = (145731ULL + esp.priority) * 24 * 60 * 60 * 10000000ULL; FILETIME newWriteTime; newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); - esp.m_Time = newWriteTime; + esp.time = newWriteTime; fileEntry->setFileTime(newWriteTime); if (!::SetFileTime(file, nullptr, nullptr, &newWriteTime)) { throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData()); @@ -583,7 +595,7 @@ int PluginList::enabledCount() const { int enabled = 0; for (const auto &info : m_ESPs) { - if (info.m_Enabled) { + if (info.enabled) { ++enabled; } } @@ -592,19 +604,19 @@ int PluginList::enabledCount() const QString PluginList::getIndexPriority(int index) const { - return m_ESPs[index].m_Index; + return m_ESPs[index].index; } bool PluginList::isESPLocked(int index) const { - return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end(); + return m_LockedOrder.find(m_ESPs.at(index).name.toLower()) != m_LockedOrder.end(); } void PluginList::lockESPIndex(int index, bool lock) { if (lock) { - if (!m_ESPs.at(index).m_ForceEnabled) - m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder; + if (!m_ESPs.at(index).forceEnabled) + m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).loadOrder; else return; } else { @@ -622,10 +634,10 @@ void PluginList::syncLoadOrder() for (unsigned int i = 0; i < m_ESPs.size(); ++i) { int index = m_ESPsByPriority[i]; - if (m_ESPs[index].m_Enabled) { - m_ESPs[index].m_LoadOrder = loadOrder++; + if (m_ESPs[index].enabled) { + m_ESPs[index].loadOrder = loadOrder++; } else { - m_ESPs[index].m_LoadOrder = -1; + m_ESPs[index].loadOrder = -1; } } } @@ -650,7 +662,7 @@ void PluginList::refreshLoadOrder() // find the location to insert at while ((targetPrio < static_cast(m_ESPs.size() - 1)) && - (m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) { + (m_ESPs[m_ESPsByPriority[targetPrio]].loadOrder < iter->first)) { ++targetPrio; } @@ -660,9 +672,9 @@ void PluginList::refreshLoadOrder() int temp = targetPrio; int index = nameIter->second; - if (m_ESPs[index].m_Priority != temp) { + if (m_ESPs[index].priority != temp) { setPluginPriority(index, temp); - m_ESPs[index].m_LoadOrder = iter->first; + m_ESPs[index].loadOrder = iter->first; syncLoadOrder(); savePluginsList = true; } @@ -689,7 +701,7 @@ QStringList PluginList::pluginNames() const QStringList result; for (const ESPInfo &info : m_ESPs) { - result.append(info.m_Name); + result.append(info.name); } return result; @@ -701,15 +713,15 @@ IPluginList::PluginStates PluginList::state(const QString &name) const if (iter == m_ESPsByName.end()) { return IPluginList::STATE_MISSING; } else { - return m_ESPs[iter->second].m_Enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE; + return m_ESPs[iter->second].enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE; } } void PluginList::setState(const QString &name, PluginStates state) { auto iter = m_ESPsByName.find(name.toLower()); if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) || - m_ESPs[iter->second].m_ForceEnabled; + m_ESPs[iter->second].enabled = (state == IPluginList::STATE_ACTIVE) || + m_ESPs[iter->second].forceEnabled; } else { log::warn("Plugin not found: {}", name); } @@ -718,20 +730,20 @@ void PluginList::setState(const QString &name, PluginStates state) { void PluginList::setLoadOrder(const QStringList &pluginList) { for (ESPInfo &info : m_ESPs) { - info.m_Priority = -1; + info.priority = -1; } int maxPriority = 0; for (const QString &plugin : pluginList) { auto iter = m_ESPsByName.find(plugin.toLower()); if (iter !=m_ESPsByName.end()) { - m_ESPs[iter->second].m_Priority = maxPriority++; + m_ESPs[iter->second].priority = maxPriority++; } } // use old priorities for (ESPInfo &info : m_ESPs) { - if (info.m_Priority == -1) { - info.m_Priority = maxPriority++; + if (info.priority == -1) { + info.priority = maxPriority++; } } updateIndices(); @@ -743,7 +755,7 @@ int PluginList::priority(const QString &name) const if (iter == m_ESPsByName.end()) { return -1; } else { - return m_ESPs[iter->second].m_Priority; + return m_ESPs[iter->second].priority; } } @@ -753,7 +765,7 @@ int PluginList::loadOrder(const QString &name) const if (iter == m_ESPsByName.end()) { return -1; } else { - return m_ESPs[iter->second].m_LoadOrder; + return m_ESPs[iter->second].loadOrder; } } @@ -763,7 +775,7 @@ bool PluginList::isMaster(const QString &name) const if (iter == m_ESPsByName.end()) { return false; } else { - return m_ESPs[iter->second].m_IsMaster; + return m_ESPs[iter->second].isMaster; } } @@ -773,7 +785,7 @@ bool PluginList::isLight(const QString &name) const if (iter == m_ESPsByName.end()) { return false; } else { - return m_ESPs[iter->second].m_IsLight; + return m_ESPs[iter->second].isLight; } } @@ -783,7 +795,7 @@ bool PluginList::isLightFlagged(const QString &name) const if (iter == m_ESPsByName.end()) { return false; } else { - return m_ESPs[iter->second].m_IsLightFlagged; + return m_ESPs[iter->second].isLightFlagged; } } @@ -794,7 +806,7 @@ QStringList PluginList::masters(const QString &name) const return QStringList(); } else { QStringList result; - for (const QString &master : m_ESPs[iter->second].m_Masters) { + for (const QString &master : m_ESPs[iter->second].masters) { result.append(master); } return result; @@ -807,7 +819,7 @@ QString PluginList::origin(const QString &name) const if (iter == m_ESPsByName.end()) { return QString(); } else { - return m_ESPs[iter->second].m_OriginName; + return m_ESPs[iter->second].originName; } } @@ -837,15 +849,15 @@ void PluginList::updateIndices() m_ESPsByPriority.clear(); m_ESPsByPriority.resize(m_ESPs.size()); for (unsigned int i = 0; i < m_ESPs.size(); ++i) { - if (m_ESPs[i].m_Priority < 0) { + if (m_ESPs[i].priority < 0) { continue; } - if (m_ESPs[i].m_Priority >= static_cast(m_ESPs.size())) { - log::error("invalid plugin priority: {}", m_ESPs[i].m_Priority); + if (m_ESPs[i].priority >= static_cast(m_ESPs.size())) { + log::error("invalid plugin priority: {}", m_ESPs[i].priority); continue; } - m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; - m_ESPsByPriority.at(static_cast(m_ESPs[i].m_Priority)) = i; + m_ESPsByName[m_ESPs[i].name.toLower()] = i; + m_ESPsByPriority.at(static_cast(m_ESPs[i].priority)) = i; } generatePluginIndexes(); @@ -858,17 +870,17 @@ void PluginList::generatePluginIndexes() bool lightPluginsSupported = m_GamePlugin->feature()->lightPluginsAreSupported(); for (int l = 0; l < m_ESPs.size(); ++l) { int i = m_ESPsByPriority.at(l); - if (!m_ESPs[i].m_Enabled) { - m_ESPs[i].m_Index = QString(); + if (!m_ESPs[i].enabled) { + m_ESPs[i].index = QString(); ++numSkipped; continue; } - if (lightPluginsSupported && (m_ESPs[i].m_IsLight || m_ESPs[i].m_IsLightFlagged)) { + if (lightPluginsSupported && (m_ESPs[i].isLight || m_ESPs[i].isLightFlagged)) { int ESLpos = 254 + ((numESLs + 1) / 4096); - m_ESPs[i].m_Index = QString("%1:%2").arg(ESLpos, 2, 16, QChar('0')).arg((numESLs) % 4096, 3, 16, QChar('0')).toUpper(); + m_ESPs[i].index = QString("%1:%2").arg(ESLpos, 2, 16, QChar('0')).arg((numESLs) % 4096, 3, 16, QChar('0')).toUpper(); ++numESLs; } else { - m_ESPs[i].m_Index = QString("%1").arg(l - numESLs - numSkipped, 2, 16, QChar('0')).toUpper(); + m_ESPs[i].index = QString("%1").arg(l - numESLs - numSkipped, 2, 16, QChar('0')).toUpper(); } } emit esplist_changed(); @@ -892,28 +904,23 @@ int PluginList::columnCount(const QModelIndex &) const void PluginList::testMasters() { -// emit layoutAboutToBeChanged(); - std::set enabledMasters; for (const auto& iter: m_ESPs) { - if (iter.m_Enabled) { - enabledMasters.insert(iter.m_Name.toLower()); + if (iter.enabled) { + enabledMasters.insert(iter.name.toLower()); } } for (auto& iter: m_ESPs) { - iter.m_MasterUnset.clear(); - if (iter.m_Enabled) { - for (const auto& master: iter.m_Masters) { + iter.masterUnset.clear(); + if (iter.enabled) { + for (const auto& master: iter.masters) { if (enabledMasters.find(master.toLower()) == enabledMasters.end()) { - iter.m_MasterUnset.insert(master); + iter.masterUnset.insert(master); } } } } - -#pragma message("emitting this seems to cause a crash!") -// emit layoutChanged(); } QVariant PluginList::data(const QModelIndex &modelIndex, int role) const @@ -942,41 +949,40 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const QVariant PluginList::displayData(const QModelIndex &modelIndex) const { - int index = modelIndex.row(); + const int index = modelIndex.row(); + + switch (modelIndex.column()) + { + case COL_NAME: + return m_ESPs[index].name; - switch (modelIndex.column()) { - case COL_NAME: { - return m_ESPs[index].m_Name; - } break; - case COL_PRIORITY: { - return m_ESPs[index].m_Priority; - } break; - case COL_MODINDEX: { - return m_ESPs[index].m_Index; - } break; - default: { - return QVariant(); - } break; + case COL_PRIORITY: + return m_ESPs[index].priority; + + case COL_MODINDEX: + return m_ESPs[index].index; + + default: + return {}; } } QVariant PluginList::checkstateData(const QModelIndex &modelIndex) const { - int index = modelIndex.row(); + const int index = modelIndex.row(); - if (m_ESPs[index].m_ForceEnabled) { - return QVariant(); - } else { - return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; + if (m_ESPs[index].forceEnabled) { + return {}; } + + return m_ESPs[index].enabled ? Qt::Checked : Qt::Unchecked; } QVariant PluginList::foregroundData(const QModelIndex &modelIndex) const { - int index = modelIndex.row(); + const int index = modelIndex.row(); - if ((modelIndex.column() == COL_NAME) && - m_ESPs[index].m_ForceEnabled) { + if ((modelIndex.column() == COL_NAME) && m_ESPs[index].forceEnabled) { return QBrush(Qt::gray); } @@ -985,25 +991,25 @@ QVariant PluginList::foregroundData(const QModelIndex &modelIndex) const QVariant PluginList::backgroundData(const QModelIndex &modelIndex) const { - int index = modelIndex.row(); + const int index = modelIndex.row(); - if (m_ESPs[index].m_ModSelected) { + if (m_ESPs[index].modSelected) { return Settings::instance().colors().pluginListContained(); - } else { - return QVariant(); } + + return {}; } QVariant PluginList::fontData(const QModelIndex &modelIndex) const { - int index = modelIndex.row(); + const int index = modelIndex.row(); QFont result; - if (m_ESPs[index].m_IsMaster) { + if (m_ESPs[index].isMaster) { result.setItalic(true); result.setWeight(QFont::Bold); - } else if (m_ESPs[index].m_IsLight || m_ESPs[index].m_IsLightFlagged) { + } else if (m_ESPs[index].isLight || m_ESPs[index].isLightFlagged) { result.setItalic(true); } @@ -1012,7 +1018,7 @@ QVariant PluginList::fontData(const QModelIndex &modelIndex) const QVariant PluginList::alignmentData(const QModelIndex &modelIndex) const { - int index = modelIndex.row(); + const int index = modelIndex.row(); if (modelIndex.column() == 0) { return QVariant(Qt::AlignLeft | Qt::AlignVCenter); @@ -1023,59 +1029,71 @@ QVariant PluginList::alignmentData(const QModelIndex &modelIndex) const QVariant PluginList::tooltipData(const QModelIndex &modelIndex) const { - int index = modelIndex.row(); + const int index = modelIndex.row(); + const auto& esp = m_ESPs[index]; - QString name = m_ESPs[index].m_Name.toLower(); - auto addInfoIter = m_AdditionalInfo.find(name); QString toolTip; - if (addInfoIter != m_AdditionalInfo.end()) { - if (!addInfoIter->second.m_Messages.isEmpty()) { + + // additional info + auto itor = m_AdditionalInfo.find(esp.name.toLower()); + + if (itor != m_AdditionalInfo.end()) { + if (!itor->second.messages.isEmpty()) { toolTip += "
      "; - for (auto&& message : addInfoIter->second.m_Messages) { + + for (auto&& message : itor->second.messages) { toolTip += "
    • " + message + "
    • "; } + toolTip += "

    "; } } - if (m_ESPs[index].m_ForceEnabled) { - QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); - text += tr("
    This plugin can't be disabled (enforced by the game)."); - toolTip += text; + + toolTip += tr("Origin: %1").arg(esp.originName); + + if (esp.forceEnabled) { + toolTip += tr("
    This plugin can't be disabled (enforced by the game)."); } else { - QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); - if (m_ESPs[index].m_Author.size() > 0) { - text += "
    " + tr("Author") + ": " + TruncateString(m_ESPs[index].m_Author); + if (!esp.author.isEmpty()) { + toolTip += "
    " + tr("Author") + ": " + TruncateString(esp.author); } - if (m_ESPs[index].m_Description.size() > 0) { - text += "
    " + tr("Description") + ": " + TruncateString(m_ESPs[index].m_Description); + + if (esp.description.size() > 0) { + toolTip += "
    " + tr("Description") + ": " + TruncateString(esp.description); } - if (m_ESPs[index].m_MasterUnset.size() > 0) { - text += "
    " + tr("Missing Masters") + ": " + TruncateString(SetJoin(m_ESPs[index].m_MasterUnset, ", ")) + ""; + + if (esp.masterUnset.size() > 0) { + toolTip += "
    " + tr("Missing Masters") + ": " + TruncateString(SetJoin(esp.masterUnset, ", ")) + ""; } + std::set enabledMasters; - std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), - m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), + std::set_difference(esp.masters.begin(), esp.masters.end(), + esp.masterUnset.begin(), esp.masterUnset.end(), std::inserter(enabledMasters, enabledMasters.end())); + if (!enabledMasters.empty()) { - text += "
    " + tr("Enabled Masters") + ": " + TruncateString(SetJoin(enabledMasters, ", ")); + toolTip += "
    " + tr("Enabled Masters") + ": " + TruncateString(SetJoin(enabledMasters, ", ")); } - if (!m_ESPs[index].m_Archives.empty()) { - text += "
    " + tr("Loads Archives") + ": " + TruncateString(SetJoin(m_ESPs[index].m_Archives, ", ")); - text += "
    " + tr("There are Archives connected to this plugin. " + + if (!esp.archives.empty()) { + toolTip += "
    " + tr("Loads Archives") + ": " + TruncateString(SetJoin(esp.archives, ", ")); + toolTip += "
    " + tr("There are Archives connected to this plugin. " "Their assets will be added to your game, overwriting in case of conflicts following the plugin order. " "Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin)"); } - if (m_ESPs[index].m_HasIni) { - text += "
    " + tr("Loads INI settings") + ": "; - text += "
    " + tr("There is an ini file connected to this plugin. " + + if (esp.hasIni) { + toolTip += "
    " + tr("Loads INI settings") + ": "; + toolTip += "
    " + tr("There is an ini file connected to this plugin. " "Its settings will be added to your game settings, overwriting in case of conflicts."); } - if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { - text += "

    " + tr("This ESP is flagged as an ESL. " + + if (esp.isLightFlagged && !esp.isLight) { + toolTip += "

    " + tr("This ESP is flagged as an ESL. " "It will adhere to the ESP load order but the records will be loaded in ESL space."); } - toolTip += text; } + return toolTip; } @@ -1084,8 +1102,8 @@ QVariant PluginList::iconData(const QModelIndex &modelIndex) const int index = modelIndex.row(); QVariantList result; - QString nameLower = m_ESPs[index].m_Name.toLower(); - if (m_ESPs[index].m_MasterUnset.size() > 0) { + QString nameLower = m_ESPs[index].name.toLower(); + if (m_ESPs[index].masterUnset.size() > 0) { result.append(":/MO/gui/warning"); } if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { @@ -1093,17 +1111,17 @@ QVariant PluginList::iconData(const QModelIndex &modelIndex) const } auto bossInfoIter = m_AdditionalInfo.find(nameLower); if (bossInfoIter != m_AdditionalInfo.end()) { - if (!bossInfoIter->second.m_Messages.isEmpty()) { + if (!bossInfoIter->second.messages.isEmpty()) { result.append(":/MO/gui/information"); } } - if (m_ESPs[index].m_HasIni) { + if (m_ESPs[index].hasIni) { result.append(":/MO/gui/attachment"); } - if (!m_ESPs[index].m_Archives.empty()) { + if (!m_ESPs[index].archives.empty()) { result.append(":/MO/gui/archive_conflict_neutral"); } - if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { + if (m_ESPs[index].isLightFlagged && !m_ESPs[index].isLight) { result.append(":/MO/gui/awaiting"); } return result; @@ -1117,8 +1135,8 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int bool result = false; if (role == Qt::CheckStateRole) { - m_ESPs[modIndex.row()].m_Enabled = - value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].m_ForceEnabled; + m_ESPs[modIndex.row()].enabled = + value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].forceEnabled; m_LastCheck.restart(); emit dataChanged(modIndex, modIndex); @@ -1182,7 +1200,7 @@ Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); if (modelIndex.isValid()) { - if (!m_ESPs[index].m_ForceEnabled) { + if (!m_ESPs[index].forceEnabled) { result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; } if (modelIndex.column() == COL_PRIORITY) { @@ -1207,48 +1225,48 @@ void PluginList::setPluginPriority(int row, int &newPriority) else if (newPriorityTemp >= static_cast(m_ESPsByPriority.size())) newPriorityTemp = static_cast(m_ESPsByPriority.size()) - 1; - if (!m_ESPs[row].m_IsMaster && !m_ESPs[row].m_IsLight) { + if (!m_ESPs[row].isMaster && !m_ESPs[row].isLight) { // don't allow esps to be moved above esms while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && - (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster || - m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight)) { + (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isMaster || + m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isLight)) { ++newPriorityTemp; } } else { // don't allow esms to be moved below esps while ((newPriorityTemp > 0) && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight) { + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isMaster && + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).isLight) { --newPriorityTemp; } // also don't allow "regular" esms to be moved above primary plugins while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && - (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_ForceEnabled)) { + (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).forceEnabled)) { ++newPriorityTemp; } } try { - int oldPriority = m_ESPs.at(row).m_Priority; + int oldPriority = m_ESPs.at(row).priority; if (newPriorityTemp > oldPriority) { // priority is higher than the old, so the gap we left is in lower priorities for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; + --m_ESPs.at(m_ESPsByPriority.at(i)).priority; } emit dataChanged(index(oldPriority + 1, 0), index(newPriorityTemp, columnCount())); } else { for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; + ++m_ESPs.at(m_ESPsByPriority.at(i)).priority; } emit dataChanged(index(newPriorityTemp, 0), index(oldPriority - 1, columnCount())); ++newPriority; } - m_ESPs.at(row).m_Priority = newPriorityTemp; + m_ESPs.at(row).priority = newPriorityTemp; emit dataChanged(index(row, 0), index(row, columnCount())); - m_PluginMoved(m_ESPs[row].m_Name, oldPriority, newPriorityTemp); + m_PluginMoved(m_ESPs[row].name, oldPriority, newPriorityTemp); } catch (const std::out_of_range&) { - reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].m_Name)); + reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].name)); } updateIndices(); @@ -1266,11 +1284,11 @@ void PluginList::changePluginPriority(std::vector rows, int newPriority) // don't try to move plugins before force-enabled plugins for (std::vector::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - if (iter->m_ForceEnabled) { - newPriority = std::max(newPriority, iter->m_Priority+1); + if (iter->forceEnabled) { + newPriority = std::max(newPriority, iter->priority+1); } - maxPriority = std::max(maxPriority, iter->m_Priority+1); - minPriority = std::min(minPriority, iter->m_Priority); + maxPriority = std::max(maxPriority, iter->priority+1); + minPriority = std::min(minPriority, iter->priority); } // limit the new priority to existing priorities @@ -1280,14 +1298,14 @@ void PluginList::changePluginPriority(std::vector rows, int newPriority) // sort the moving plugins by ascending priorities std::sort(rows.begin(), rows.end(), [&esp](const int &LHS, const int &RHS) { - return esp[LHS].m_Priority < esp[RHS].m_Priority; + return esp[LHS].priority < esp[RHS].priority; }); // if at least on plugin is increasing in priority, the target index is // that of the row BELOW the dropped location, otherwise it's the one above for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { - if (m_ESPs[*iter].m_Priority < newPriority) { + if (m_ESPs[*iter].priority < newPriority) { --newPriority; break; } @@ -1332,7 +1350,7 @@ bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, (row >= static_cast(m_ESPs.size()))) { newPriority = static_cast(m_ESPs.size()); } else { - newPriority = m_ESPs[row].m_Priority; + newPriority = m_ESPs[row].priority; } changePluginPriority(sourceRows, newPriority); @@ -1389,7 +1407,7 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) } for (QModelIndex idx : rows) { idx = proxyModel->mapToSource(idx); - int newPriority = m_ESPs[idx.row()].m_Priority + diff; + int newPriority = m_ESPs[idx.row()].priority + diff; if ((newPriority >= 0) && (newPriority < rowCount())) { setPluginPriority(idx.row(), newPriority); } @@ -1431,27 +1449,28 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni, std::set archives, bool lightPluginsAreSupported) - : m_Name(name), m_FullPath(fullPath), m_Enabled(enabled), m_ForceEnabled(enabled), - m_Priority(0), m_LoadOrder(-1), m_OriginName(originName), m_HasIni(hasIni), m_Archives(archives), m_ModSelected(false) + : name(name), fullPath(fullPath), enabled(enabled), forceEnabled(enabled), + priority(0), loadOrder(-1), originName(originName), hasIni(hasIni), + archives(archives), modSelected(false) { try { ESP::File file(ToWString(fullPath)); - m_IsMaster = file.isMaster(); + isMaster = file.isMaster(); auto extension = name.right(3).toLower(); - m_IsLight = lightPluginsAreSupported && (extension == "esl"); - m_IsLightFlagged = lightPluginsAreSupported && file.isLight(); - - m_Author = QString::fromLatin1(file.author().c_str()); - m_Description = QString::fromLatin1(file.description().c_str()); - std::set masters = file.masters(); - for (auto iter = masters.begin(); iter != masters.end(); ++iter) { - m_Masters.insert(QString(iter->c_str())); + isLight = lightPluginsAreSupported && (extension == "esl"); + isLightFlagged = lightPluginsAreSupported && file.isLight(); + + author = QString::fromLatin1(file.author().c_str()); + description = QString::fromLatin1(file.description().c_str()); + + for (auto&& m : file.masters()) { + masters.insert(QString::fromStdString(m)); } } catch (const std::exception &e) { log::error("failed to parse plugin file {}: {}", fullPath, e.what()); - m_IsMaster = false; - m_IsLight = false; - m_IsLightFlagged = false; + isMaster = false; + isLight = false; + isLightFlagged = false; } } diff --git a/src/pluginlist.h b/src/pluginlist.h index 5cbe0a17..8b1ce90c 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -202,8 +202,8 @@ public: int timeElapsedSinceLastChecked() const; - QString getName(int index) const { return m_ESPs.at(index).m_Name; } - int getPriority(int index) const { return m_ESPs.at(index).m_Priority; } + QString getName(int index) const { return m_ESPs.at(index).name; } + int getPriority(int index) const { return m_ESPs.at(index).priority; } QString getIndexPriority(int index) const; bool isESPLocked(int index) const; void lockESPIndex(int index, bool lock); @@ -301,37 +301,42 @@ signals: private: - struct ESPInfo { - - ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni, std::set archives, bool lightSupported); - QString m_Name; - QString m_FullPath; - bool m_Enabled; - bool m_ForceEnabled; - int m_Priority; - QString m_Index; - int m_LoadOrder; - FILETIME m_Time; - QString m_OriginName; - bool m_IsMaster; - bool m_IsLight; - bool m_IsLightFlagged; - bool m_ModSelected; - QString m_Author; - QString m_Description; - bool m_HasIni; - std::set m_Archives; - std::set m_Masters; - mutable std::set m_MasterUnset; + struct ESPInfo + { + ESPInfo( + const QString &name, bool enabled, const QString &originName, + const QString &fullPath, bool hasIni, std::set archives, + bool lightSupported); + + QString name; + QString fullPath; + bool enabled; + bool forceEnabled; + int priority; + QString index; + int loadOrder; + FILETIME time; + QString originName; + bool isMaster; + bool isLight; + bool isLightFlagged; + bool modSelected; + QString author; + QString description; + bool hasIni; + std::set archives; + std::set masters; + mutable std::set masterUnset; + bool operator < (const ESPInfo& str) const { - return (m_LoadOrder < str.m_LoadOrder); + return (loadOrder < str.loadOrder); } }; struct AdditionalInfo { - QStringList m_Messages; - Loot::Plugin m_Loot; + QStringList messages; + Loot::Plugin loot; }; friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); -- cgit v1.3.1 From 54b18e88159738a3054c71d5a4827646caad4ded Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 23 Nov 2019 23:54:19 -0500 Subject: added loot info to tooltip --- src/loot.cpp | 51 +++++-------- src/loot.h | 2 +- src/pluginlist.cpp | 212 +++++++++++++++++++++++++++++++++++++++++++---------- src/pluginlist.h | 4 + 4 files changed, 195 insertions(+), 74 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 66e8a01d..c315056b 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -254,15 +254,11 @@ private: } for (auto&& m : report.messages) { - log(levelFromLoot( - lootcli::logLevelFromString(m.type.toStdString())), - m.text); + log(m.type, m.text); } for (auto&& p : report.plugins) { - for (auto&& d : p.dirty) { - m_core.pluginList()->addInformation(p.name, d.toString(false)); - } + m_core.pluginList()->addLootReport(p.name, p); } } }; @@ -527,35 +523,11 @@ void Loot::processStdout(const std::string &lootOut) void Loot::processMessage(const lootcli::Message& m) { - /*static const std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - static const std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); - switch (m.type) { case lootcli::MessageType::Log: { - if (m.logLevel == lootcli::LogLevels::Error) { - std::smatch match; - - if (std::regex_match(m.log, match, exRequires)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - emit information( - QString::fromStdString(modName), - tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else if (std::regex_match(m.log, match, exIncompatible)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - emit information( - QString::fromStdString(modName), - tr("incompatible with \"%1\"").arg(dependency.c_str())); - } else { - emit log(levelFromLoot(m.logLevel), QString::fromStdString(m.log)); - } - } else { - emit log(levelFromLoot(m.logLevel), QString::fromStdString(m.log)); - } - + emit log(levelFromLoot(m.logLevel), QString::fromStdString(m.log)); break; } @@ -564,7 +536,7 @@ void Loot::processMessage(const lootcli::Message& m) emit progress(m.progress); break; } - }*/ + } } void Loot::processOutputFile() @@ -674,7 +646,20 @@ std::vector Loot::reportMessages(const QJsonArray& array) const } Message m; - m.type = getWarn(o, "type"); + + const auto type = getWarn(o, "type"); + + if (type == "info") { + m.type = log::Info; + } else if (type == "warn") { + m.type = log::Warning; + } else if (type == "error") { + m.type = log::Error; + } else { + log::error("unknown message type '{}'", type); + m.type = log::Info; + } + m.text = getWarn(o, "text"); if (!m.text.isEmpty()) { diff --git a/src/loot.h b/src/loot.h index dc9b0d7b..3a7c6aa9 100644 --- a/src/loot.h +++ b/src/loot.h @@ -19,7 +19,7 @@ class Loot : public QObject public: struct Message { - QString type; + MOBase::log::Levels type; QString text; }; diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 52c3fc3c..e91d820d 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1034,36 +1034,30 @@ QVariant PluginList::tooltipData(const QModelIndex &modelIndex) const QString toolTip; - // additional info - auto itor = m_AdditionalInfo.find(esp.name.toLower()); - - if (itor != m_AdditionalInfo.end()) { - if (!itor->second.messages.isEmpty()) { - toolTip += "
      "; - - for (auto&& message : itor->second.messages) { - toolTip += "
    • " + message + "
    • "; - } - - toolTip += "

    "; - } - } - - toolTip += tr("Origin: %1").arg(esp.originName); + toolTip += "" + tr("Origin") + ": " + esp.originName; if (esp.forceEnabled) { - toolTip += tr("
    This plugin can't be disabled (enforced by the game)."); + toolTip += + "
    " + + tr("This plugin can't be disabled (enforced by the game).") + + ""; } else { if (!esp.author.isEmpty()) { - toolTip += "
    " + tr("Author") + ": " + TruncateString(esp.author); + toolTip += + "
    " + tr("Author") + ": " + + TruncateString(esp.author); } if (esp.description.size() > 0) { - toolTip += "
    " + tr("Description") + ": " + TruncateString(esp.description); + toolTip += + "
    " + tr("Description") + ": " + + TruncateString(esp.description); } if (esp.masterUnset.size() > 0) { - toolTip += "
    " + tr("Missing Masters") + ": " + TruncateString(SetJoin(esp.masterUnset, ", ")) + ""; + toolTip += + "
    " + tr("Missing Masters") + ": " + + "" + TruncateString(SetJoin(esp.masterUnset, ", ")) + ""; } std::set enabledMasters; @@ -1072,61 +1066,199 @@ QVariant PluginList::tooltipData(const QModelIndex &modelIndex) const std::inserter(enabledMasters, enabledMasters.end())); if (!enabledMasters.empty()) { - toolTip += "
    " + tr("Enabled Masters") + ": " + TruncateString(SetJoin(enabledMasters, ", ")); + toolTip += + "
    " + tr("Enabled Masters") + ": " + + TruncateString(SetJoin(enabledMasters, ", ")); } if (!esp.archives.empty()) { - toolTip += "
    " + tr("Loads Archives") + ": " + TruncateString(SetJoin(esp.archives, ", ")); - toolTip += "
    " + tr("There are Archives connected to this plugin. " - "Their assets will be added to your game, overwriting in case of conflicts following the plugin order. " - "Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin)"); + toolTip += + "
    " + tr("Loads Archives") + ": " + + TruncateString(SetJoin(esp.archives, ", ")) + + "
    " + tr( + "There are Archives connected to this plugin. Their assets will be " + "added to your game, overwriting in case of conflicts following the " + "plugin order. Loose files will always overwrite assets from " + "Archives. (This flag only checks for Archives from the same mod as " + "the plugin)"); } if (esp.hasIni) { - toolTip += "
    " + tr("Loads INI settings") + ": "; - toolTip += "
    " + tr("There is an ini file connected to this plugin. " - "Its settings will be added to your game settings, overwriting in case of conflicts."); + toolTip += + "
    " + tr("Loads INI settings") + ": " + "
    " + tr( + "There is an ini file connected to this plugin. Its settings will " + "be added to your game settings, overwriting in case of conflicts."); } if (esp.isLightFlagged && !esp.isLight) { - toolTip += "

    " + tr("This ESP is flagged as an ESL. " - "It will adhere to the ESP load order but the records will be loaded in ESL space."); + toolTip += + "

    " + tr( + "This ESP is flagged as an ESL. It will adhere to the ESP load " + "order but the records will be loaded in ESL space."); } } + + // additional info + auto itor = m_AdditionalInfo.find(esp.name.toLower()); + + if (itor != m_AdditionalInfo.end()) { + if (!itor->second.messages.isEmpty()) { + toolTip += "
      "; + + for (auto&& message : itor->second.messages) { + toolTip += "
    • " + message + "
    • "; + } + + toolTip += "
    "; + } + + // loot + toolTip += makeLootTooltip(itor->second.loot); + } + return toolTip; } +QString PluginList::makeLootTooltip(const Loot::Plugin& loot) const +{ + QString s; + + for (auto&& f : loot.incompatibilities) { + s += + "
  • " + tr("Incompatible with %1") + .arg(f.displayName.isEmpty() ? f.name : f.displayName) + + "
  • "; + } + + for (auto&& m : loot.missingMasters) { + s += "
  • " + tr("Depends on missing %1").arg(m) + "
  • "; + } + + for (auto&& m : loot.messages) { + s += "
  • "; + + switch (m.type) + { + case log::Warning: + s += tr("Warning") + ": "; + break; + + case log::Error: + s += tr("Error") + ": "; + break; + + case log::Info: // fall-through + case log::Debug: + default: + // nothing + break; + } + + s += m.text + "
  • "; + } + + for (auto&& d : loot.dirty) { + s += "
  • " + d.toString(false) + "
  • "; + } + + for (auto&& c : loot.clean) { + s += "
  • " + c.toString(true) + "
  • "; + } + + if (!s.isEmpty()) { + s = + "
    " + "
      " + + s + + "
    "; + } + + return s; +} + QVariant PluginList::iconData(const QModelIndex &modelIndex) const { int index = modelIndex.row(); QVariantList result; - QString nameLower = m_ESPs[index].name.toLower(); - if (m_ESPs[index].masterUnset.size() > 0) { + + const auto& esp = m_ESPs[index]; + const QString nameLower = esp.name.toLower(); + + auto infoItor = m_AdditionalInfo.find(nameLower); + + const AdditionalInfo* info = nullptr; + if (infoItor != m_AdditionalInfo.end()) { + info = &infoItor->second; + } + + if (isProblematic(esp, info)) { result.append(":/MO/gui/warning"); } + if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { result.append(":/MO/gui/locked"); } - auto bossInfoIter = m_AdditionalInfo.find(nameLower); - if (bossInfoIter != m_AdditionalInfo.end()) { - if (!bossInfoIter->second.messages.isEmpty()) { - result.append(":/MO/gui/information"); - } + + if (hasInfo(esp, info)) { + result.append(":/MO/gui/information"); } - if (m_ESPs[index].hasIni) { + + if (esp.hasIni) { result.append(":/MO/gui/attachment"); } - if (!m_ESPs[index].archives.empty()) { + + if (!esp.archives.empty()) { result.append(":/MO/gui/archive_conflict_neutral"); } - if (m_ESPs[index].isLightFlagged && !m_ESPs[index].isLight) { + + if (esp.isLightFlagged && !m_ESPs[index].isLight) { result.append(":/MO/gui/awaiting"); } + return result; } +bool PluginList::isProblematic(const ESPInfo& esp, const AdditionalInfo* info) const +{ + if (esp.masterUnset.size() > 0) { + return true; + } + + if (info) { + if (!info->loot.incompatibilities.empty()) { + return true; + } + + if (!info->loot.missingMasters.empty()) { + return true; + } + } + + return false; +} + +bool PluginList::hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const +{ + if (info) { + if (!info->messages.empty()) { + return true; + } + + if (!info->loot.messages.empty()) { + return true; + } + + if (!info->loot.dirty.empty()) { + return true; + } + } + + return false; +} + bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) { QString modName = modIndex.data().toString(); diff --git a/src/pluginlist.h b/src/pluginlist.h index 8b1ce90c..004b1590 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -394,6 +394,10 @@ private: QVariant alignmentData(const QModelIndex &modelIndex) const; QVariant tooltipData(const QModelIndex &modelIndex) const; QVariant iconData(const QModelIndex &modelIndex) const; + + QString makeLootTooltip(const Loot::Plugin& loot) const; + bool isProblematic(const ESPInfo& esp, const AdditionalInfo* info) const; + bool hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const; }; #pragma warning(pop) -- cgit v1.3.1 From f5476531ae39fdae8c3adc63d4c4a11f92600ff8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 24 Nov 2019 19:39:26 -0500 Subject: basic html loot report --- src/loot.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index c315056b..c73dcaa7 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -32,14 +32,13 @@ log::Levels levelFromLoot(lootcli::LogLevels level) } } - class LootDialog : public QDialog { public: LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : QDialog(parent), m_core(core), m_loot(loot), - m_label(nullptr), m_progress(nullptr), m_buttons(nullptr), - m_report(nullptr), m_output(nullptr), + m_label(nullptr), m_progress(nullptr), + m_report(nullptr), m_output(nullptr), m_buttons(nullptr), m_finished(false), m_cancelling(false) { createUI(); @@ -143,9 +142,10 @@ private: Loot& m_loot; QLabel* m_label; QProgressBar* m_progress; - QDialogButtonBox* m_buttons; + QTextEdit* m_messages; QPushButton* m_report; QPlainTextEdit* m_output; + QDialogButtonBox* m_buttons; bool m_finished; bool m_cancelling; @@ -164,6 +164,9 @@ private: m_progress = new QProgressBar; ly->addWidget(m_progress); + m_messages = new QTextEdit; + ly->addWidget(m_messages); + auto* more = createMoreUI(); ly->addWidget(more); @@ -253,10 +256,46 @@ private: addLineOutput(""); } - for (auto&& m : report.messages) { - log(m.type, m.text); + QString html; + + if (!report.messages.empty()) { + html += "
      "; + + for (auto&& m : report.messages) { + log(m.type, m.text); + + html += "
    • "; + + switch (m.type) + { + case log::Error: + { + html += "" + QObject::tr("Error") + ": "; + break; + } + + case log::Warning: + { + html += "" + QObject::tr("Warning") + ": "; + break; + } + + default: + { + break; + } + } + + html += m.text + "
    • "; + } + + html + "
    "; + } else { + html += QObject::tr("No messages."); } + m_messages->setHtml(html); + for (auto&& p : report.plugins) { m_core.pluginList()->addLootReport(p.name, p); } @@ -299,7 +338,7 @@ Loot::~Loot() { m_thread->wait(); - if (!m_outPath.isEmpty()) { + if (!m_outPath.isEmpty() && QFile::exists(m_outPath)) { const auto r = shell::Delete(m_outPath); if (!r) { -- cgit v1.3.1 From cd876a0f9ffd03c711812c2ade92836e5d6c0203 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 24 Nov 2019 20:11:50 -0500 Subject: split loot dialog, added ui file --- src/CMakeLists.txt | 10 +- src/loot.cpp | 271 +---------------------------------------------------- src/lootdialog.cpp | 222 +++++++++++++++++++++++++++++++++++++++++++ src/lootdialog.h | 47 ++++++++++ src/lootdialog.ui | 214 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 492 insertions(+), 272 deletions(-) create mode 100644 src/lootdialog.cpp create mode 100644 src/lootdialog.h create mode 100644 src/lootdialog.ui (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b74ea37..db3bda73 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -143,6 +143,7 @@ SET(organizer_SRCS processrunner.cpp uilocker.cpp loot.cpp + lootdialog.cpp shared/windows_error.cpp shared/error_report.cpp @@ -265,6 +266,7 @@ SET(organizer_HDRS processrunner.h uilocker.h loot.h + lootdialog.h json.h shared/windows_error.h @@ -392,6 +394,11 @@ set(executables editexecutablesdialog ) +set(loot + loot + lootdialog +) + set(modinfo modinfo modinfobackup @@ -468,7 +475,6 @@ set(utilities shared/util usvfsconnector shared/windows_error - loot json ) @@ -490,7 +496,7 @@ set(widgets ) set(src_filters - application core browser dialogs downloads env executables modinfo + application core browser dialogs downloads env executables loot modinfo modinfo\\dialog modlist plugins previews profiles settings settingsdialog utilities widgets ) diff --git a/src/loot.cpp b/src/loot.cpp index c73dcaa7..f0618b0b 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -1,4 +1,5 @@ #include "loot.h" +#include "lootdialog.h" #include "spawn.h" #include "organizercore.h" #include "json.h" @@ -32,276 +33,6 @@ log::Levels levelFromLoot(lootcli::LogLevels level) } } -class LootDialog : public QDialog -{ -public: - LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : - QDialog(parent), m_core(core), m_loot(loot), - m_label(nullptr), m_progress(nullptr), - m_report(nullptr), m_output(nullptr), m_buttons(nullptr), - m_finished(false), m_cancelling(false) - { - createUI(); - m_progress->setMaximum(0); - - QObject::connect( - &m_loot, &Loot::output, this, - [&](auto&& s){ addOutput(s); }, Qt::QueuedConnection); - - QObject::connect( - &m_loot, &Loot::progress, - this, [&](auto&& p){ setProgress(p); }, Qt::QueuedConnection); - - QObject::connect( - &m_loot, &Loot::log, this, - [&](auto&& lv, auto&& s){ log(lv, s); }, Qt::QueuedConnection); - - QObject::connect( - &m_loot, &Loot::finished, this, - [&]{ onFinished(); }, Qt::QueuedConnection); - } - - void setText(const QString& s) - { - m_label->setText(s); - } - - void setProgress(lootcli::Progress p) - { - setText(progressToString(p)); - - if (p == lootcli::Progress::Done) { - m_progress->setRange(0, 1); - m_progress->setValue(1); - } - } - - QString progressToString(lootcli::Progress p) - { - using P = lootcli::Progress; - - switch (p) - { - case P::CheckingMasterlistExistence: return tr("Checking masterlist existence"); - case P::UpdatingMasterlist: return tr("Updating masterlist"); - case P::LoadingLists: return tr("Loading lists"); - case P::ReadingPlugins: return tr("Reading plugins"); - case P::SortingPlugins: return tr("Sorting plugins"); - case P::WritingLoadorder: return tr("Writing loadorder.txt"); - case P::ParsingLootMessages: return tr("Parsing loot messages"); - case P::Done: return tr("Done"); - default: return QString("unknown progress %1").arg(static_cast(p)); - } - } - - void addOutput(const QString& s) - { - if (m_core.settings().diagnostics().lootLogLevel() > lootcli::LogLevels::Debug) { - return; - } - - const auto lines = s.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts); - - for (auto&& line : lines) { - if (line.isEmpty()) { - continue; - } - - addLineOutput(line); - } - } - - bool result() const - { - return m_loot.result(); - } - - void cancel() - { - if (!m_finished && !m_cancelling) { - addLineOutput(tr("Stopping LOOT...")); - m_loot.cancel(); - m_buttons->setEnabled(false); - m_cancelling = true; - } - } - - int exec() override - { - return QDialog::exec(); - } - - void openReport() - { - const auto path = m_loot.outPath(); - shell::Open(path); - } - -private: - OrganizerCore& m_core; - Loot& m_loot; - QLabel* m_label; - QProgressBar* m_progress; - QTextEdit* m_messages; - QPushButton* m_report; - QPlainTextEdit* m_output; - QDialogButtonBox* m_buttons; - bool m_finished; - bool m_cancelling; - - void createUI() - { - auto* root = new QWidget(this); - auto* ly = new QVBoxLayout(root); - - setLayout(new QVBoxLayout); - layout()->setContentsMargins(0, 0, 0, 0); - layout()->addWidget(root); - - m_label = new QLabel; - ly->addWidget(m_label); - - m_progress = new QProgressBar; - ly->addWidget(m_progress); - - m_messages = new QTextEdit; - ly->addWidget(m_messages); - - auto* more = createMoreUI(); - ly->addWidget(more); - - resize(700, 400); - } - - QWidget* createMoreUI() - { - auto* more = new QWidget; - auto* ly = new QVBoxLayout(more); - ly->setContentsMargins(0, 0, 0, 0); - - auto* buttons = new QHBoxLayout; - buttons->setContentsMargins(0, 0, 0, 0); - m_report = new QPushButton(tr("Open JSON report")); - m_report->setEnabled(false); - connect(m_report, &QPushButton::clicked, [&]{ openReport(); }); - buttons->addWidget(m_report); - buttons->addStretch(1); - ly->addLayout(buttons); - - m_output = new QPlainTextEdit; - ly->addWidget(m_output); - - m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel); - connect(m_buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); - ly->addWidget(m_buttons); - - return more; - } - - void closeEvent(QCloseEvent* e) override - { - if (m_finished) { - QDialog::closeEvent(e); - } else { - cancel(); - e->ignore(); - } - } - - void onButton(QAbstractButton* b) - { - if (m_buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - if (m_finished) { - close(); - } else { - cancel(); - } - } - } - - void addLineOutput(const QString& line) - { - m_output->appendPlainText(line); - } - - void onFinished() - { - m_finished = true; - - if (m_cancelling) { - close(); - } else { - handleReport(); - m_report->setEnabled(true); - m_buttons->setStandardButtons(QDialogButtonBox::Close); - } - } - - void log(log::Levels lv, const QString& s) - { - if (lv >= log::Levels::Warning) { - log::log(lv, "{}", s); - } - - if (m_core.settings().diagnostics().lootLogLevel() > lootcli::LogLevels::Debug) { - addLineOutput(QString("[%1] %2").arg(log::levelToString(lv)).arg(s)); - } - } - - void handleReport() - { - const auto& report = m_loot.report(); - - if (!report.messages.empty()) { - addLineOutput(""); - } - - QString html; - - if (!report.messages.empty()) { - html += "
      "; - - for (auto&& m : report.messages) { - log(m.type, m.text); - - html += "
    • "; - - switch (m.type) - { - case log::Error: - { - html += "" + QObject::tr("Error") + ": "; - break; - } - - case log::Warning: - { - html += "" + QObject::tr("Warning") + ": "; - break; - } - - default: - { - break; - } - } - - html += m.text + "
    • "; - } - - html + "
    "; - } else { - html += QObject::tr("No messages."); - } - - m_messages->setHtml(html); - - for (auto&& p : report.plugins) { - m_core.pluginList()->addLootReport(p.name, p); - } - } -}; - QString Loot::Dirty::toString(bool isClean) const { diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp new file mode 100644 index 00000000..db41959d --- /dev/null +++ b/src/lootdialog.cpp @@ -0,0 +1,222 @@ +#include "lootdialog.h" +#include "ui_lootdialog.h" +#include "loot.h" +#include "organizercore.h" +#include +#include + +using namespace MOBase; + +LootDialog::LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : + QDialog(parent), ui(new Ui::LootDialog), m_core(core), m_loot(loot), + m_finished(false), m_cancelling(false) +{ + createUI(); + + QObject::connect( + &m_loot, &Loot::output, this, + [&](auto&& s){ addOutput(s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::progress, + this, [&](auto&& p){ setProgress(p); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::log, this, + [&](auto&& lv, auto&& s){ log(lv, s); }, Qt::QueuedConnection); + + QObject::connect( + &m_loot, &Loot::finished, this, + [&]{ onFinished(); }, Qt::QueuedConnection); +} + +LootDialog::~LootDialog() = default; + +void LootDialog::setText(const QString& s) +{ + ui->progressText->setText(s); +} + +void LootDialog::setProgress(lootcli::Progress p) +{ + setText(progressToString(p)); + + if (p == lootcli::Progress::Done) { + ui->progressBar->setRange(0, 1); + ui->progressBar->setValue(1); + } +} + +QString LootDialog::progressToString(lootcli::Progress p) +{ + using P = lootcli::Progress; + + switch (p) + { + case P::CheckingMasterlistExistence: return tr("Checking masterlist existence"); + case P::UpdatingMasterlist: return tr("Updating masterlist"); + case P::LoadingLists: return tr("Loading lists"); + case P::ReadingPlugins: return tr("Reading plugins"); + case P::SortingPlugins: return tr("Sorting plugins"); + case P::WritingLoadorder: return tr("Writing loadorder.txt"); + case P::ParsingLootMessages: return tr("Parsing loot messages"); + case P::Done: return tr("Done"); + default: return QString("unknown progress %1").arg(static_cast(p)); + } +} + +void LootDialog::addOutput(const QString& s) +{ + if (m_core.settings().diagnostics().lootLogLevel() > lootcli::LogLevels::Debug) { + return; + } + + const auto lines = s.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts); + + for (auto&& line : lines) { + if (line.isEmpty()) { + continue; + } + + addLineOutput(line); + } +} + +bool LootDialog::result() const +{ + return m_loot.result(); +} + +void LootDialog::cancel() +{ + if (!m_finished && !m_cancelling) { + addLineOutput(tr("Stopping LOOT...")); + m_loot.cancel(); + ui->buttons->setEnabled(false); + m_cancelling = true; + } +} + +void LootDialog::openReport() +{ + const auto path = m_loot.outPath(); + shell::Open(path); +} + +void LootDialog::createUI() +{ + ui->setupUi(this); + ui->progressBar->setMaximum(0); + + ui->openJsonReport->setEnabled(false); + connect(ui->openJsonReport, &QPushButton::clicked, [&]{ openReport(); }); + + new ExpanderWidget(ui->details, ui->detailsPanel); + + connect(ui->buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); + + resize(480, 275); +} + +void LootDialog::closeEvent(QCloseEvent* e) +{ + if (m_finished) { + QDialog::closeEvent(e); + } else { + cancel(); + e->ignore(); + } +} + +void LootDialog::onButton(QAbstractButton* b) +{ + if (ui->buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { + if (m_finished) { + close(); + } else { + cancel(); + } + } +} + +void LootDialog::addLineOutput(const QString& line) +{ + ui->output->appendPlainText(line); +} + +void LootDialog::onFinished() +{ + m_finished = true; + + if (m_cancelling) { + close(); + } else { + handleReport(); + ui->openJsonReport->setEnabled(true); + ui->buttons->setStandardButtons(QDialogButtonBox::Close); + } +} + +void LootDialog::log(log::Levels lv, const QString& s) +{ + if (lv >= log::Levels::Warning) { + log::log(lv, "{}", s); + } + + if (m_core.settings().diagnostics().lootLogLevel() > lootcli::LogLevels::Debug) { + addLineOutput(QString("[%1] %2").arg(log::levelToString(lv)).arg(s)); + } +} + +void LootDialog::handleReport() +{ + const auto& report = m_loot.report(); + + if (!report.messages.empty()) { + addLineOutput(""); + } + + QString html; + + if (!report.messages.empty()) { + html += "
      "; + + for (auto&& m : report.messages) { + log(m.type, m.text); + + html += "
    • "; + + switch (m.type) + { + case log::Error: + { + html += "" + QObject::tr("Error") + ": "; + break; + } + + case log::Warning: + { + html += "" + QObject::tr("Warning") + ": "; + break; + } + + default: + { + break; + } + } + + html += m.text + "
    • "; + } + + html + "
    "; + } else { + html += QObject::tr("No messages."); + } + + ui->report->setHtml(html); + + for (auto&& p : report.plugins) { + m_core.pluginList()->addLootReport(p.name, p); + } +} diff --git a/src/lootdialog.h b/src/lootdialog.h new file mode 100644 index 00000000..e4647b5c --- /dev/null +++ b/src/lootdialog.h @@ -0,0 +1,47 @@ +#ifndef MODORGANIZER_LOOTDIALOG_H +#define MODORGANIZER_LOOTDIALOG_H + +#include +#include + +namespace Ui { class LootDialog; } + +class OrganizerCore; +class Loot; + +class LootDialog : public QDialog +{ +public: + LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot); + ~LootDialog(); + + void setText(const QString& s); + void setProgress(lootcli::Progress p); + + QString progressToString(lootcli::Progress p); + + void addOutput(const QString& s); + + bool result() const; + + void cancel(); + + void openReport(); + +private: + std::unique_ptr ui; + OrganizerCore& m_core; + Loot& m_loot; + bool m_finished; + bool m_cancelling; + + void createUI(); + void closeEvent(QCloseEvent* e) override; + void onButton(QAbstractButton* b); + void addLineOutput(const QString& line); + void onFinished(); + void log(MOBase::log::Levels lv, const QString& s); + void handleReport(); +}; + +#endif // MODORGANIZER_LOOTDIALOG_H diff --git a/src/lootdialog.ui b/src/lootdialog.ui new file mode 100644 index 00000000..7e10b1db --- /dev/null +++ b/src/lootdialog.ui @@ -0,0 +1,214 @@ + + + LootDialog + + + + 0 + 0 + 457 + 343 + + + + LOOT + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Progress + + + + + + + 24 + + + false + + + + + + + true + + + Qt::TextBrowserInteraction + + + LOOT Report + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Details + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Open JSON report + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttons + accepted() + LootDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttons + rejected() + LootDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + -- cgit v1.3.1 From 70ee786102c8436c7f8f9e8a5d2ea71035d8d572 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 24 Nov 2019 22:19:23 -0500 Subject: changed loot report to webengine with markdown support --- src/loot.cpp | 4 +- src/lootdialog.cpp | 74 +++++++++-- src/lootdialog.h | 31 +++++ src/lootdialog.ui | 49 ++++++-- src/resources/markdown.html | 299 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 434 insertions(+), 23 deletions(-) create mode 100644 src/resources/markdown.html (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index f0618b0b..5d75eeaa 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -67,7 +67,9 @@ Loot::Loot() Loot::~Loot() { - m_thread->wait(); + if (m_thread) { + m_thread->wait(); + } if (!m_outPath.isEmpty() && QFile::exists(m_outPath)) { const auto r = shell::Delete(m_outPath); diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index db41959d..9c7b482e 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -4,9 +4,44 @@ #include "organizercore.h" #include #include +#include using namespace MOBase; + +MarkdownDocument::MarkdownDocument(QObject* parent) + : QObject(parent) +{ +} + +void MarkdownDocument::setText(const QString& text) +{ + if (m_text == text) + return; + + m_text = text; + emit textChanged(m_text); +} + + +MarkdownPage::MarkdownPage(QObject* parent) + : QWebEnginePage(parent) +{ +} + +bool MarkdownPage::acceptNavigationRequest(const QUrl &url, NavigationType, bool) +{ + static const QStringList allowed = {"qrc", "data"}; + + if (!allowed.contains(url.scheme())) { + QDesktopServices::openUrl(url); + return false; + } + + return true; +} + + LootDialog::LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot) : QDialog(parent), ui(new Ui::LootDialog), m_core(core), m_loot(loot), m_finished(false), m_cancelling(false) @@ -108,6 +143,27 @@ void LootDialog::createUI() ui->setupUi(this); ui->progressBar->setMaximum(0); + auto* page = new MarkdownPage(this); + ui->report->setPage(page); + + auto* channel = new QWebChannel(this); + channel->registerObject("content", &m_report); + page->setWebChannel(channel); + + const QString path = QApplication::applicationDirPath() + "/resources/markdown.html"; + QFile f(path); + + if (f.open(QFile::ReadOnly)) { + const QString html = f.readAll(); + if (!html.isEmpty()) { + ui->report->setHtml(html); + } else { + log::error("failed to read '{}', {}", path, f.errorString()); + } + } else { + log::error("can't open '{}', {}", path, f.errorString()); + } + ui->openJsonReport->setEnabled(false); connect(ui->openJsonReport, &QPushButton::clicked, [&]{ openReport(); }); @@ -176,27 +232,25 @@ void LootDialog::handleReport() addLineOutput(""); } - QString html; + QString md; if (!report.messages.empty()) { - html += "
      "; - for (auto&& m : report.messages) { log(m.type, m.text); - html += "
    • "; + md += " - "; switch (m.type) { case log::Error: { - html += "" + QObject::tr("Error") + ": "; + md += "**" + QObject::tr("Error") + "**: "; break; } case log::Warning: { - html += "" + QObject::tr("Warning") + ": "; + md += "**" + QObject::tr("Warning") + "**: "; break; } @@ -206,15 +260,13 @@ void LootDialog::handleReport() } } - html += m.text + "
    • "; + md += m.text + "\n"; } - - html + "
    "; } else { - html += QObject::tr("No messages."); + md += QObject::tr("**No messages.**"); } - ui->report->setHtml(html); + m_report.setText(md); for (auto&& p : report.plugins) { m_core.pluginList()->addLootReport(p.name, p); diff --git a/src/lootdialog.h b/src/lootdialog.h index e4647b5c..df9e546d 100644 --- a/src/lootdialog.h +++ b/src/lootdialog.h @@ -9,6 +9,36 @@ namespace Ui { class LootDialog; } class OrganizerCore; class Loot; + +class MarkdownDocument : public QObject +{ + Q_OBJECT; + Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged FINAL); + +public: + explicit MarkdownDocument(QObject* parent=nullptr); + void setText(const QString& text); + +signals: + void textChanged(const QString &text); + +private: + QString m_text; +}; + + +class MarkdownPage : public QWebEnginePage +{ + Q_OBJECT; + +public: + explicit MarkdownPage(QObject* parent=nullptr); + +protected: + bool acceptNavigationRequest(const QUrl &url, NavigationType, bool) override; +}; + + class LootDialog : public QDialog { public: @@ -34,6 +64,7 @@ private: Loot& m_loot; bool m_finished; bool m_cancelling; + MarkdownDocument m_report; void createUI(); void closeEvent(QCloseEvent* e) override; diff --git a/src/lootdialog.ui b/src/lootdialog.ui index 7e10b1db..e366ce41 100644 --- a/src/lootdialog.ui +++ b/src/lootdialog.ui @@ -7,7 +7,7 @@ 0 0 457 - 343 + 600
    @@ -16,7 +16,7 @@ - + 0 @@ -31,7 +31,7 @@ - + 0 @@ -62,16 +62,36 @@ - - - true + + + QFrame::StyledPanel - - Qt::TextBrowserInteraction - - - LOOT Report + + QFrame::Sunken + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + about:blank + + + + +
    @@ -176,6 +196,13 @@
    + + + QWebEngineView + QWidget +
    QtWebEngineWidgets/QWebEngineView
    +
    +
    diff --git a/src/resources/markdown.html b/src/resources/markdown.html new file mode 100644 index 00000000..a09b2209 --- /dev/null +++ b/src/resources/markdown.html @@ -0,0 +1,299 @@ + + + + + + + + + +
    + + + \ No newline at end of file -- cgit v1.3.1 From 64304a6368cf8357bb04d1c0057aec637ebb479a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 24 Nov 2019 23:23:15 -0500 Subject: tweaked css, finished markdown report copy markdown.html to resources/ --- src/CMakeLists.txt | 4 +- src/loot.cpp | 139 ++++++++++++ src/loot.h | 13 +- src/lootdialog.cpp | 44 +--- src/resources/markdown.html | 528 ++++++++++++++++++++++---------------------- 5 files changed, 424 insertions(+), 304 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index db3bda73..5a510806 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -676,4 +676,6 @@ INSTALL( ) # qdds.dll needs installing manually as Qt no longer ships with it by default. -INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../qdds.dll DESTINATION bin/dlls/imageformats) \ No newline at end of file +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../qdds.dll DESTINATION bin/dlls/imageformats) + +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/resources/markdown.html DESTINATION bin/resources) diff --git a/src/loot.cpp b/src/loot.cpp index 5d75eeaa..2faf9c2e 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -34,6 +34,99 @@ log::Levels levelFromLoot(lootcli::LogLevels level) } +QString Loot::Report::toMarkdown() const +{ + QString s; + + if (!messages.empty()) { + s += "### " + QObject::tr("General messages") + "\n"; + + for (auto&& m : messages) { + s += " - " + m.toMarkdown() + "\n"; + } + } + + if (!plugins.empty()) { + if (!s.isEmpty()) { + s += "\n"; + } + + s += "### " + QObject::tr("Plugins") + "\n"; + + for (auto&& p : plugins) { + const auto ps = p.toMarkdown(); + if (!ps.isEmpty()) { + s += ps + "\n"; + } + } + } + + if (s.isEmpty()) { + s += "**" + QObject::tr("No messages.") + "**"; + } + + s += stats.toMarkdown(); + + return s; +} + +QString Loot::Stats::toMarkdown() const +{ + return QString("`stats: %1s, lootcli %2, loot %3`") + .arg(QString::number(time / 1000.0, 'f', 2)) + .arg(lootcliVersion) + .arg(lootVersion); +} + +QString Loot::Plugin::toMarkdown() const +{ + QString s; + + if (!incompatibilities.empty()) { + s += " - **" + QObject::tr("Incompatibilities") + ": "; + + QString fs; + for (auto&& f : incompatibilities) { + if (!fs.isEmpty()) { + fs += ", "; + } + + fs += f.displayName.isEmpty() ? f.name : f.displayName; + } + + s += fs + "**\n"; + } + + if (!missingMasters.empty()) { + s += " - **" + QObject::tr("Missing masters") + ": "; + + QString ms; + for (auto&& m : missingMasters) { + if (!ms.isEmpty()) { + ms += ", "; + } + + ms += m; + } + + s += ms + "**\n"; + } + + for (auto&& m : messages) { + s += " - " + m.toMarkdown() + "\n"; + } + + for (auto&& d : dirty) { + s += " - " + d.toMarkdown(false) + "\n"; + } + + if (!s.isEmpty()) { + s = "#### " + name + "\n" + s; + } + + return s; +} + QString Loot::Dirty::toString(bool isClean) const { if (isClean) { @@ -50,6 +143,11 @@ QString Loot::Dirty::toString(bool isClean) const return s; } +QString Loot::Dirty::toMarkdown(bool isClean) const +{ + return toString(isClean); +} + QString Loot::Dirty::cleaningString() const { return QObject::tr("%1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es).") @@ -59,6 +157,35 @@ QString Loot::Dirty::cleaningString() const .arg(deletedNavmesh); } +QString Loot::Message::toMarkdown() const +{ + QString s; + + switch (type) + { + case log::Error: + { + s += "**" + QObject::tr("Error") + "**: "; + break; + } + + case log::Warning: + { + s += "**" + QObject::tr("Warning") + "**: "; + break; + } + + default: + { + break; + } + } + + s += text; + + return s; +} + Loot::Loot() : m_thread(nullptr), m_cancel(false), m_result(false) @@ -348,6 +475,7 @@ Loot::Report Loot::createReport(const QJsonDocument& doc) const r.messages = reportMessages(getOpt(object, "messages")); r.plugins = reportPlugins(getOpt(object, "plugins")); + r.stats = reportStats(getWarn(object, "stats")); return r; } @@ -407,6 +535,17 @@ Loot::Plugin Loot::reportPlugin(const QJsonObject& plugin) const return p; } +Loot::Stats Loot::reportStats(const QJsonObject& stats) const +{ + Stats s; + + s.time = getWarn(stats, "time"); + s.lootcliVersion = getWarn(stats, "lootcliVersion"); + s.lootVersion = getWarn(stats, "lootVersion"); + + return s; +} + std::vector Loot::reportMessages(const QJsonArray& array) const { std::vector v; diff --git a/src/loot.h b/src/loot.h index 3a7c6aa9..30ef4b60 100644 --- a/src/loot.h +++ b/src/loot.h @@ -21,6 +21,8 @@ public: { MOBase::log::Levels type; QString text; + + QString toMarkdown() const; }; struct File @@ -39,6 +41,7 @@ public: QString info; QString toString(bool isClean) const; + QString toMarkdown(bool isClean) const; QString cleaningString() const; }; @@ -52,12 +55,17 @@ public: bool loadsArchive = false; bool isMaster = false; bool isLightMaster = false; + + QString toMarkdown() const; }; struct Stats { qint64 time = 0; - QString version; + QString lootcliVersion; + QString lootVersion; + + QString toMarkdown() const; }; struct Report @@ -65,6 +73,8 @@ public: std::vector messages; std::vector plugins; Stats stats; + + QString toMarkdown() const; }; @@ -107,6 +117,7 @@ private: Message reportMessage(const QJsonObject& message) const; std::vector reportPlugins(const QJsonArray& plugins) const; Loot::Plugin reportPlugin(const QJsonObject& plugin) const; + Loot::Stats reportStats(const QJsonObject& stats) const; std::vector reportMessages(const QJsonArray& array) const; std::vector reportFiles(const QJsonArray& array) const; diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index 9c7b482e..c7cdfecd 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -226,49 +226,19 @@ void LootDialog::log(log::Levels lv, const QString& s) void LootDialog::handleReport() { - const auto& report = m_loot.report(); + const auto& lootReport = m_loot.report(); - if (!report.messages.empty()) { + if (!lootReport.messages.empty()) { addLineOutput(""); } - QString md; - - if (!report.messages.empty()) { - for (auto&& m : report.messages) { - log(m.type, m.text); - - md += " - "; - - switch (m.type) - { - case log::Error: - { - md += "**" + QObject::tr("Error") + "**: "; - break; - } - - case log::Warning: - { - md += "**" + QObject::tr("Warning") + "**: "; - break; - } - - default: - { - break; - } - } - - md += m.text + "\n"; - } - } else { - md += QObject::tr("**No messages.**"); + for (auto&& m : lootReport.messages) { + log(m.type, m.text); } - m_report.setText(md); - - for (auto&& p : report.plugins) { + for (auto&& p : lootReport.plugins) { m_core.pluginList()->addLootReport(p.name, p); } + + m_report.setText(lootReport.toMarkdown()); } diff --git a/src/resources/markdown.html b/src/resources/markdown.html index a09b2209..1ecaf1b9 100644 --- a/src/resources/markdown.html +++ b/src/resources/markdown.html @@ -2,278 +2,276 @@ - - - -- cgit v1.3.1 From 48fcf9521f796bc3b6e536545877a9ea2e37360d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 24 Nov 2019 23:47:59 -0500 Subject: removed max-width, larger dialog removed messages in output now that there's a full report --- src/lootdialog.cpp | 10 +--------- src/resources/markdown.html | 5 ----- 2 files changed, 1 insertion(+), 14 deletions(-) (limited to 'src') diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index c7cdfecd..80664415 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -171,7 +171,7 @@ void LootDialog::createUI() connect(ui->buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); - resize(480, 275); + resize(650, 450); } void LootDialog::closeEvent(QCloseEvent* e) @@ -228,14 +228,6 @@ void LootDialog::handleReport() { const auto& lootReport = m_loot.report(); - if (!lootReport.messages.empty()) { - addLineOutput(""); - } - - for (auto&& m : lootReport.messages) { - log(m.type, m.text); - } - for (auto&& p : lootReport.plugins) { m_core.pluginList()->addLootReport(p.name, p); } diff --git a/src/resources/markdown.html b/src/resources/markdown.html index 1ecaf1b9..bd6d0f4d 100644 --- a/src/resources/markdown.html +++ b/src/resources/markdown.html @@ -18,7 +18,6 @@ font-family: Sans-serif; color: #F1F1F1; line-height: 1; - max-width: 960px; padding-left: 10px; padding-top: 0px; } @@ -72,11 +71,9 @@ p, ul, ol { font-size: 14px; line-height: 24px; - max-width: 540px; } pre { padding: 0px 24px; - max-width: 800px; white-space: pre-wrap; } code { @@ -93,7 +90,6 @@ border-left:.5em solid #eee; padding: 0 2em; margin-left:0; - max-width: 476px; } blockquote cite { font-size:14px; @@ -106,7 +102,6 @@ blockquote p { color: #666; - max-width: 460px; } hr { width: 540px; -- cgit v1.3.1 From 0e45044dbd724e9050bea00511585dc023afe144 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 25 Nov 2019 00:47:45 -0500 Subject: fixed cancel button debug logs, some cleanup --- src/loot.cpp | 47 +++++++++++++++++++++------- src/lootdialog.cpp | 92 ++++++++++++++++++++++++++++++++++-------------------- src/lootdialog.h | 11 +++---- 3 files changed, 98 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 2faf9c2e..9c8cf8c4 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -199,6 +199,7 @@ Loot::~Loot() } if (!m_outPath.isEmpty() && QFile::exists(m_outPath)) { + log::debug("deleting temporary loot report '{}'", m_outPath); const auto r = shell::Delete(m_outPath); if (!r) { @@ -211,6 +212,8 @@ Loot::~Loot() bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) { + log::debug("starting loot"); + m_outPath = QDir::temp().absoluteFilePath("lootreport.json"); const auto logLevel = core.settings().diagnostics().lootLogLevel(); @@ -271,8 +274,19 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) core.pluginList()->clearAdditionalInformation(); + log::debug("starting loot thread"); + m_thread.reset(QThread::create([&]{ - lootThread(); + try + { + lootThread(); + } + catch(...) + { + log::error("unhandled exception in loot thread"); + } + + log::debug("finishing loot thread"); emit finished(); })); @@ -283,7 +297,10 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) void Loot::cancel() { - m_cancel = true; + if (!m_cancel) { + log::debug("loot received cancel request"); + m_cancel = true; + } } bool Loot::result() const @@ -303,6 +320,8 @@ const Loot::Report& Loot::report() const void Loot::lootThread() { + ::SetThreadDescription(GetCurrentThread(), L"loot"); + try { m_result = false; @@ -321,10 +340,13 @@ bool Loot::waitForCompletion() { bool terminating = false; + log::debug("loot thread waiting for completion on lootcli"); + for (;;) { DWORD res = WaitForSingleObject(m_lootProcess.get(), 100); if (res == WAIT_OBJECT_0) { + log::debug("lootcli has completed"); // done break; } @@ -336,9 +358,13 @@ bool Loot::waitForCompletion() } if (m_cancel) { - // terminate and wait to finish + log::debug("terminating lootcli process"); ::TerminateProcess(m_lootProcess.get(), 1); + + log::debug("waiting for loocli process to terminate"); WaitForSingleObject(m_lootProcess.get(), INFINITE); + + log::debug("lootcli terminated"); return false; } @@ -396,6 +422,12 @@ void Loot::processStdout(const std::string &lootOut) emit output(QString::fromStdString(lootOut)); m_outputBuffer += lootOut; + if (m_outputBuffer.empty()) { + return; + } + + log::debug("loot: processing stdout ({} bytes)", m_outputBuffer.size()); + std::size_t start = 0; for (;;) { @@ -440,7 +472,7 @@ void Loot::processMessage(const lootcli::Message& m) void Loot::processOutputFile() { - log::info("parsing json output file at '{}'", m_outPath); + log::debug("parsing json output file at '{}'", m_outPath); QFile outFile(m_outPath); if (!outFile.open(QIODevice::ReadOnly)) { @@ -645,20 +677,13 @@ std::vector Loot::reportStringArray(const QJsonArray& array) const bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) { - //m_OrganizerCore.currentProfile()->writeModlistNow(); core.savePluginList(); - //Create a backup of the load orders w/ LOOT in name - //to make sure that any sorting is easily undo-able. - //Need to figure out how I want to do that. - try { Loot loot; LootDialog dialog(parent, core, loot); loot.start(parent, core, didUpdateMasterList); - - dialog.setText(QObject::tr("Please wait while LOOT is running")); dialog.exec(); return dialog.result(); diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index 80664415..43929c00 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -8,6 +8,29 @@ using namespace MOBase; +QString progressToString(lootcli::Progress p) +{ + using P = lootcli::Progress; + + static const std::map map = { + {P::CheckingMasterlistExistence, QObject::tr("Checking masterlist existence")}, + {P::UpdatingMasterlist, QObject::tr("Updating masterlist")}, + {P::LoadingLists, QObject::tr("Loading lists")}, + {P::ReadingPlugins, QObject::tr("Reading plugins")}, + {P::SortingPlugins, QObject::tr("Sorting plugins")}, + {P::WritingLoadorder, QObject::tr("Writing loadorder.txt")}, + {P::ParsingLootMessages, QObject::tr("Parsing loot messages")}, + {P::Done, QObject::tr("Done")} + }; + + auto itor = map.find(p); + if (itor == map.end()) { + return QString("unknown progress %1").arg(static_cast(p)); + } else { + return itor->second; + } +} + MarkdownDocument::MarkdownDocument(QObject* parent) : QObject(parent) @@ -74,7 +97,11 @@ void LootDialog::setText(const QString& s) void LootDialog::setProgress(lootcli::Progress p) { - setText(progressToString(p)); + // don't overwrite the "stopping loot" message even if lootcli generates a new + // progress message + if (!m_cancelling) { + setText(progressToString(p)); + } if (p == lootcli::Progress::Done) { ui->progressBar->setRange(0, 1); @@ -82,24 +109,6 @@ void LootDialog::setProgress(lootcli::Progress p) } } -QString LootDialog::progressToString(lootcli::Progress p) -{ - using P = lootcli::Progress; - - switch (p) - { - case P::CheckingMasterlistExistence: return tr("Checking masterlist existence"); - case P::UpdatingMasterlist: return tr("Updating masterlist"); - case P::LoadingLists: return tr("Loading lists"); - case P::ReadingPlugins: return tr("Reading plugins"); - case P::SortingPlugins: return tr("Sorting plugins"); - case P::WritingLoadorder: return tr("Writing loadorder.txt"); - case P::ParsingLootMessages: return tr("Parsing loot messages"); - case P::Done: return tr("Done"); - default: return QString("unknown progress %1").arg(static_cast(p)); - } -} - void LootDialog::addOutput(const QString& s) { if (m_core.settings().diagnostics().lootLogLevel() > lootcli::LogLevels::Debug) { @@ -125,8 +134,12 @@ bool LootDialog::result() const void LootDialog::cancel() { if (!m_finished && !m_cancelling) { - addLineOutput(tr("Stopping LOOT...")); + log::debug("loot dialog: cancelling"); m_loot.cancel(); + + setText(tr("Stopping LOOT...")); + addLineOutput("stopping loot"); + ui->buttons->setEnabled(false); m_cancelling = true; } @@ -138,6 +151,22 @@ void LootDialog::openReport() shell::Open(path); } +void LootDialog::accept() +{ + // no-op +} + +void LootDialog::reject() +{ + if (m_finished) { + log::debug("loot dialog reject: loot finished, closing"); + QDialog::reject(); + } else { + log::debug("loot dialog reject: not finished, cancelling"); + cancel(); + } +} + void LootDialog::createUI() { ui->setupUi(this); @@ -169,7 +198,7 @@ void LootDialog::createUI() new ExpanderWidget(ui->details, ui->detailsPanel); - connect(ui->buttons, &QDialogButtonBox::clicked, [&](auto* b){ onButton(b); }); + ui->buttons->setStandardButtons(QDialogButtonBox::Cancel); resize(650, 450); } @@ -177,24 +206,15 @@ void LootDialog::createUI() void LootDialog::closeEvent(QCloseEvent* e) { if (m_finished) { + log::debug("loot dialog close event: finished, closing"); QDialog::closeEvent(e); } else { + log::debug("loot dialog close event: not finished, cancelling"); cancel(); e->ignore(); } } -void LootDialog::onButton(QAbstractButton* b) -{ - if (ui->buttons->buttonRole(b) == QDialogButtonBox::RejectRole) { - if (m_finished) { - close(); - } else { - cancel(); - } - } -} - void LootDialog::addLineOutput(const QString& line) { ui->output->appendPlainText(line); @@ -202,12 +222,16 @@ void LootDialog::addLineOutput(const QString& line) void LootDialog::onFinished() { + log::debug("loot dialog: loot is finished"); + m_finished = true; if (m_cancelling) { + log::debug("loot dialog: was cancelling, closing"); close(); } else { - handleReport(); + log::debug("loot dialog: showing report"); + showReport(); ui->openJsonReport->setEnabled(true); ui->buttons->setStandardButtons(QDialogButtonBox::Close); } @@ -224,7 +248,7 @@ void LootDialog::log(log::Levels lv, const QString& s) } } -void LootDialog::handleReport() +void LootDialog::showReport() { const auto& lootReport = m_loot.report(); diff --git a/src/lootdialog.h b/src/lootdialog.h index df9e546d..fcdeb304 100644 --- a/src/lootdialog.h +++ b/src/lootdialog.h @@ -48,16 +48,14 @@ public: void setText(const QString& s); void setProgress(lootcli::Progress p); - QString progressToString(lootcli::Progress p); - void addOutput(const QString& s); - bool result() const; - void cancel(); - void openReport(); + void accept() override; + void reject() override; + private: std::unique_ptr ui; OrganizerCore& m_core; @@ -68,11 +66,10 @@ private: void createUI(); void closeEvent(QCloseEvent* e) override; - void onButton(QAbstractButton* b); void addLineOutput(const QString& line); void onFinished(); void log(MOBase::log::Levels lv, const QString& s); - void handleReport(); + void showReport(); }; #endif // MODORGANIZER_LOOTDIALOG_H -- cgit v1.3.1 From 0c69619dbe4fae24794b2539a331ca7ac66f1f93 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 25 Nov 2019 02:10:59 -0500 Subject: switched to named pipes --- src/loot.cpp | 178 ++++++++++++++++++++++++++++++++--------------------- src/loot.h | 6 +- src/lootdialog.cpp | 1 + 3 files changed, 115 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 9c8cf8c4..d9399ab6 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -9,6 +9,8 @@ using namespace MOBase; using namespace json; +static QString LootReportPath = QDir::temp().absoluteFilePath("lootreport.json"); + log::Levels levelFromLoot(lootcli::LogLevels level) { using LC = lootcli::LogLevels; @@ -198,14 +200,14 @@ Loot::~Loot() m_thread->wait(); } - if (!m_outPath.isEmpty() && QFile::exists(m_outPath)) { - log::debug("deleting temporary loot report '{}'", m_outPath); - const auto r = shell::Delete(m_outPath); + if (QFile::exists(LootReportPath)) { + log::debug("deleting temporary loot report '{}'", LootReportPath); + const auto r = shell::Delete(LootReportPath); if (!r) { log::error( "failed to remove temporary loot json report '{}': {}", - m_outPath, r.toString()); + LootReportPath, r.toString()); } } } @@ -214,8 +216,32 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) { log::debug("starting loot"); - m_outPath = QDir::temp().absoluteFilePath("lootreport.json"); + // creating pipe + env::HandlePtr out(createPipe()); + if (out.get() == INVALID_HANDLE_VALUE) { + return false; + } + + // vfs + core.prepareVFS(); + // spawning + if (!spawnLootcli(parent, core, didUpdateMasterList, out.get())) { + return false; + } + + // starting thread + log::debug("starting loot thread"); + m_thread.reset(QThread::create([&]{ lootThread(); })); + m_thread->start(); + + return true; +} + +bool Loot::spawnLootcli( + QWidget* parent, OrganizerCore& core, bool didUpdateMasterList, + HANDLE stdoutHandle) +{ const auto logLevel = core.settings().diagnostics().lootLogLevel(); QStringList parameters; @@ -224,45 +250,18 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) << "--logLevel" << QString::fromStdString(lootcli::logLevelToString(logLevel)) - << "--out" << QString("\"%1\"").arg(m_outPath); + << "--out" << QString("\"%1\"").arg(LootReportPath); if (didUpdateMasterList) { parameters << "--skipUpdateMasterlist"; } - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = nullptr; - - env::HandlePtr readPipe, writePipe; - - { - HANDLE read = INVALID_HANDLE_VALUE; - HANDLE write = INVALID_HANDLE_VALUE; - - if (!::CreatePipe(&read, &write, &secAttributes, 0)) { - log::error("failed to create stdout reroute"); - } - - readPipe.reset(read); - writePipe.reset(write); - - if (!::SetHandleInformation(read, HANDLE_FLAG_INHERIT, 0)) { - log::error("failed to correctly set up the stdout reroute"); - } - } - - core.prepareVFS(); - spawn::SpawnParameters sp; sp.binary = QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"); sp.arguments = parameters.join(" "); sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); sp.hooked = true; - sp.stdOut = writePipe.get(); - - m_stdout = std::move(readPipe); + sp.stdOut = stdoutHandle; HANDLE lootHandle = spawn::startBinary(parent, sp); if (lootHandle == INVALID_HANDLE_VALUE) { @@ -272,27 +271,64 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) m_lootProcess.reset(lootHandle); - core.pluginList()->clearAdditionalInformation(); + return true; +} + +HANDLE Loot::createPipe() +{ + static const wchar_t* PipeName = L"\\\\.\\pipe\\lootcli_pipe"; - log::debug("starting loot thread"); + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; - m_thread.reset(QThread::create([&]{ - try - { - lootThread(); + env::HandlePtr pipe; + + // creating pipe + { + HANDLE pipeHandle = ::CreateNamedPipe( + PipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, + 1, 50'000, 50'000, 0, &sa); + + if (pipeHandle == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + log::error("CreateNamedPipe failed, {}", formatSystemMessage(e)); + return INVALID_HANDLE_VALUE; } - catch(...) - { - log::error("unhandled exception in loot thread"); + + pipe.reset(pipeHandle); + } + + { + // duplicating the handle to read from it + HANDLE outputRead = INVALID_HANDLE_VALUE; + + const auto r = DuplicateHandle( + GetCurrentProcess(), pipe.get(), GetCurrentProcess(), &outputRead, + 0, TRUE, DUPLICATE_SAME_ACCESS); + + if (!r) { + const auto e = GetLastError(); + log::error("DuplicateHandle for pipe failed, {}", formatSystemMessage(e)); + return INVALID_HANDLE_VALUE; } - log::debug("finishing loot thread"); - emit finished(); - })); + m_stdout.reset(outputRead); + } - m_thread->start(); - return true; + // creating handle to pipe which is passed to CreateProcess() + HANDLE outputWrite = ::CreateFileW( + PipeName, FILE_WRITE_DATA|SYNCHRONIZE, 0, + &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (outputWrite == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + log::error("CreateFileW for pipe failed, {}", formatSystemMessage(e)); + return INVALID_HANDLE_VALUE; + } + + return outputWrite; } void Loot::cancel() @@ -310,7 +346,7 @@ bool Loot::result() const const QString& Loot::outPath() const { - return m_outPath; + return LootReportPath; } const Loot::Report& Loot::report() const @@ -322,7 +358,8 @@ void Loot::lootThread() { ::SetThreadDescription(GetCurrentThread(), L"loot"); - try { + try + { m_result = false; if (!waitForCompletion()) { @@ -331,9 +368,14 @@ void Loot::lootThread() m_result = true; processOutputFile(); - } catch (const std::exception &e) { - emit log(log::Levels::Error, tr("failed to run loot: %1").arg(e.what())); } + catch(...) + { + log::error("unhandled exception in loot thread"); + } + + log::debug("finishing loot thread"); + emit finished(); } bool Loot::waitForCompletion() @@ -396,25 +438,23 @@ bool Loot::waitForCompletion() std::string Loot::readFromPipe() { - static const int chunkSize = 128; - std::string result; + static const std::size_t bufferSize = 50'000; - char buffer[chunkSize + 1]; - buffer[chunkSize] = '\0'; + char buffer[bufferSize] = {}; - DWORD read = 1; - while (read > 0) { - if (!::ReadFile(m_stdout.get(), buffer, chunkSize, &read, nullptr)) { - break; - } - if (read > 0) { - result.append(buffer, read); - if (read < chunkSize) { - break; - } + DWORD bytesRead = 0; + if (::ReadFile(m_stdout.get(), buffer, bufferSize, &bytesRead, nullptr)) { + return {buffer, buffer + bytesRead}; + } else { + const auto e = GetLastError(); + + // broken pipe probably means lootcli is finished + if (e != ERROR_BROKEN_PIPE) { + log::error("{}", formatSystemMessage(e)); } + + return {}; } - return result; } void Loot::processStdout(const std::string &lootOut) @@ -472,9 +512,9 @@ void Loot::processMessage(const lootcli::Message& m) void Loot::processOutputFile() { - log::debug("parsing json output file at '{}'", m_outPath); + log::debug("parsing json output file at '{}'", LootReportPath); - QFile outFile(m_outPath); + QFile outFile(LootReportPath); if (!outFile.open(QIODevice::ReadOnly)) { emit log( MOBase::log::Error, diff --git a/src/loot.h b/src/loot.h index 30ef4b60..a67b9ed8 100644 --- a/src/loot.h +++ b/src/loot.h @@ -97,12 +97,16 @@ private: std::unique_ptr m_thread; std::atomic m_cancel; std::atomic m_result; - QString m_outPath; env::HandlePtr m_lootProcess; env::HandlePtr m_stdout; std::string m_outputBuffer; Report m_report; + HANDLE createPipe(); + bool spawnLootcli( + QWidget* parent, OrganizerCore& core, bool didUpdateMasterList, + HANDLE stdoutHandle); + std::string readFromPipe(); void lootThread(); diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index 43929c00..a8f73cdc 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -252,6 +252,7 @@ void LootDialog::showReport() { const auto& lootReport = m_loot.report(); + m_core.pluginList()->clearAdditionalInformation(); for (auto&& p : lootReport.plugins) { m_core.pluginList()->addLootReport(p.name, p); } -- cgit v1.3.1 From 23e917df454ba1588fcbe08adfa47422a443b9da Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 25 Nov 2019 03:08:27 -0500 Subject: switched to overlapped io so lootcli can still be terminated even if it's not writing anything to stdout --- src/loot.cpp | 295 ++++++++++++++++++++++++++++++++++++++++------------------- src/loot.h | 8 +- 2 files changed, 205 insertions(+), 98 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index d9399ab6..d2059d7c 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -10,6 +10,194 @@ using namespace MOBase; using namespace json; static QString LootReportPath = QDir::temp().absoluteFilePath("lootreport.json"); +static const DWORD PipeTimeout = 500; + + +class AsyncPipe +{ +public: + AsyncPipe() + : m_ioPending(false) + { + std::fill(std::begin(m_buffer), std::end(m_buffer), 0); + std::memset(&m_ov, 0, sizeof(m_ov)); + } + + env::HandlePtr create() + { + // creating pipe + env::HandlePtr out(createPipe()); + if (out.get() == INVALID_HANDLE_VALUE) { + return {}; + } + + HANDLE readEventHandle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + + if (readEventHandle == NULL) { + const auto e = GetLastError(); + log::error("CreateEvent failed for loot, {}", formatSystemMessage(e)); + return {}; + } + + m_ov.hEvent = readEventHandle; + m_readEvent.reset(readEventHandle); + + return out; + } + + std::string read() + { + if (m_ioPending) { + return checkPending(); + } else { + return tryRead(); + } + } + +private: + static const std::size_t bufferSize = 50'000; + + env::HandlePtr m_stdout; + env::HandlePtr m_readEvent; + char m_buffer[bufferSize]; + OVERLAPPED m_ov; + bool m_ioPending; + + HANDLE createPipe() + { + static const wchar_t* PipeName = L"\\\\.\\pipe\\lootcli_pipe"; + + SECURITY_ATTRIBUTES sa = {}; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + + env::HandlePtr pipe; + + // creating pipe + { + HANDLE pipeHandle = ::CreateNamedPipe( + PipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, + 1, 50'000, 50'000, PipeTimeout, &sa); + + if (pipeHandle == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + log::error("CreateNamedPipe failed, {}", formatSystemMessage(e)); + return INVALID_HANDLE_VALUE; + } + + pipe.reset(pipeHandle); + } + + { + // duplicating the handle to read from it + HANDLE outputRead = INVALID_HANDLE_VALUE; + + const auto r = DuplicateHandle( + GetCurrentProcess(), pipe.get(), GetCurrentProcess(), &outputRead, + 0, TRUE, DUPLICATE_SAME_ACCESS); + + if (!r) { + const auto e = GetLastError(); + log::error("DuplicateHandle for pipe failed, {}", formatSystemMessage(e)); + return INVALID_HANDLE_VALUE; + } + + m_stdout.reset(outputRead); + } + + + // creating handle to pipe which is passed to CreateProcess() + HANDLE outputWrite = ::CreateFileW( + PipeName, FILE_WRITE_DATA|SYNCHRONIZE, 0, + &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + + if (outputWrite == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + log::error("CreateFileW for pipe failed, {}", formatSystemMessage(e)); + return INVALID_HANDLE_VALUE; + } + + return outputWrite; + } + + std::string tryRead() + { + DWORD bytesRead = 0; + + if (!::ReadFile(m_stdout.get(), m_buffer, bufferSize, &bytesRead, &m_ov)) { + const auto e = GetLastError(); + + switch (e) + { + case ERROR_IO_PENDING: + { + m_ioPending = true; + break; + } + + case ERROR_BROKEN_PIPE: + { + // broken pipe probably means lootcli is finished + break; + } + + default: + { + log::error("{}", formatSystemMessage(e)); + break; + } + } + + return {}; + } + + return {m_buffer, m_buffer + bytesRead}; + } + + std::string checkPending() + { + DWORD bytesRead = 0; + + if (!::GetOverlappedResultEx(m_stdout.get(), &m_ov, &bytesRead, PipeTimeout, FALSE)) { + const auto e = GetLastError(); + + switch (e) + { + case ERROR_IO_INCOMPLETE: + { + break; + } + + case WAIT_TIMEOUT: + { + break; + } + + case ERROR_BROKEN_PIPE: + { + // broken pipe probably means lootcli is finished + break; + } + + default: + { + log::error("GetOverlappedResult failed, {}", formatSystemMessage(e)); + break; + } + } + + return {}; + } + + ::ResetEvent(m_readEvent.get()); + m_ioPending = false; + + return {m_buffer, m_buffer + bytesRead}; + } +}; + + log::Levels levelFromLoot(lootcli::LogLevels level) { @@ -216,9 +404,10 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) { log::debug("starting loot"); - // creating pipe - env::HandlePtr out(createPipe()); - if (out.get() == INVALID_HANDLE_VALUE) { + m_pipe.reset(new AsyncPipe); + + env::HandlePtr stdoutHandle = m_pipe->create(); + if (!stdoutHandle) { return false; } @@ -226,7 +415,7 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) core.prepareVFS(); // spawning - if (!spawnLootcli(parent, core, didUpdateMasterList, out.get())) { + if (!spawnLootcli(parent, core, didUpdateMasterList, std::move(stdoutHandle))) { return false; } @@ -240,7 +429,7 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) bool Loot::spawnLootcli( QWidget* parent, OrganizerCore& core, bool didUpdateMasterList, - HANDLE stdoutHandle) + env::HandlePtr stdoutHandle) { const auto logLevel = core.settings().diagnostics().lootLogLevel(); @@ -261,9 +450,10 @@ bool Loot::spawnLootcli( sp.arguments = parameters.join(" "); sp.currentDirectory.setPath(qApp->applicationDirPath() + "/loot"); sp.hooked = true; - sp.stdOut = stdoutHandle; + sp.stdOut = stdoutHandle.get(); HANDLE lootHandle = spawn::startBinary(parent, sp); + if (lootHandle == INVALID_HANDLE_VALUE) { emit log(log::Levels::Error, tr("failed to start loot")); return false; @@ -274,63 +464,6 @@ bool Loot::spawnLootcli( return true; } -HANDLE Loot::createPipe() -{ - static const wchar_t* PipeName = L"\\\\.\\pipe\\lootcli_pipe"; - - SECURITY_ATTRIBUTES sa = {}; - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; - - env::HandlePtr pipe; - - // creating pipe - { - HANDLE pipeHandle = ::CreateNamedPipe( - PipeName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, - 1, 50'000, 50'000, 0, &sa); - - if (pipeHandle == INVALID_HANDLE_VALUE) { - const auto e = GetLastError(); - log::error("CreateNamedPipe failed, {}", formatSystemMessage(e)); - return INVALID_HANDLE_VALUE; - } - - pipe.reset(pipeHandle); - } - - { - // duplicating the handle to read from it - HANDLE outputRead = INVALID_HANDLE_VALUE; - - const auto r = DuplicateHandle( - GetCurrentProcess(), pipe.get(), GetCurrentProcess(), &outputRead, - 0, TRUE, DUPLICATE_SAME_ACCESS); - - if (!r) { - const auto e = GetLastError(); - log::error("DuplicateHandle for pipe failed, {}", formatSystemMessage(e)); - return INVALID_HANDLE_VALUE; - } - - m_stdout.reset(outputRead); - } - - - // creating handle to pipe which is passed to CreateProcess() - HANDLE outputWrite = ::CreateFileW( - PipeName, FILE_WRITE_DATA|SYNCHRONIZE, 0, - &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); - - if (outputWrite == INVALID_HANDLE_VALUE) { - const auto e = GetLastError(); - log::error("CreateFileW for pipe failed, {}", formatSystemMessage(e)); - return INVALID_HANDLE_VALUE; - } - - return outputWrite; -} - void Loot::cancel() { if (!m_cancel) { @@ -362,12 +495,10 @@ void Loot::lootThread() { m_result = false; - if (!waitForCompletion()) { - return; + if (waitForCompletion()) { + m_result = true; + processOutputFile(); } - - m_result = true; - processOutputFile(); } catch(...) { @@ -378,6 +509,7 @@ void Loot::lootThread() emit finished(); } + bool Loot::waitForCompletion() { bool terminating = false; @@ -410,14 +542,14 @@ bool Loot::waitForCompletion() return false; } - processStdout(readFromPipe()); + processStdout(m_pipe->read()); } if (m_cancel) { return false; } - processStdout(readFromPipe()); + processStdout(m_pipe->read()); // checking exit code DWORD exitCode = 0; @@ -436,27 +568,6 @@ bool Loot::waitForCompletion() return true; } -std::string Loot::readFromPipe() -{ - static const std::size_t bufferSize = 50'000; - - char buffer[bufferSize] = {}; - - DWORD bytesRead = 0; - if (::ReadFile(m_stdout.get(), buffer, bufferSize, &bytesRead, nullptr)) { - return {buffer, buffer + bytesRead}; - } else { - const auto e = GetLastError(); - - // broken pipe probably means lootcli is finished - if (e != ERROR_BROKEN_PIPE) { - log::error("{}", formatSystemMessage(e)); - } - - return {}; - } -} - void Loot::processStdout(const std::string &lootOut) { emit output(QString::fromStdString(lootOut)); @@ -466,8 +577,6 @@ void Loot::processStdout(const std::string &lootOut) return; } - log::debug("loot: processing stdout ({} bytes)", m_outputBuffer.size()); - std::size_t start = 0; for (;;) { diff --git a/src/loot.h b/src/loot.h index a67b9ed8..7e5e01bd 100644 --- a/src/loot.h +++ b/src/loot.h @@ -11,6 +11,7 @@ Q_DECLARE_METATYPE(lootcli::Progress); Q_DECLARE_METATYPE(MOBase::log::Levels); class OrganizerCore; +class AsyncPipe; class Loot : public QObject { @@ -98,16 +99,13 @@ private: std::atomic m_cancel; std::atomic m_result; env::HandlePtr m_lootProcess; - env::HandlePtr m_stdout; + std::unique_ptr m_pipe; std::string m_outputBuffer; Report m_report; - HANDLE createPipe(); bool spawnLootcli( QWidget* parent, OrganizerCore& core, bool didUpdateMasterList, - HANDLE stdoutHandle); - - std::string readFromPipe(); + env::HandlePtr stdoutHandle); void lootThread(); bool waitForCompletion(); -- cgit v1.3.1 From bd191f3a71fbdcb706de9db5c29d615bfa13c174 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 25 Nov 2019 03:28:27 -0500 Subject: now passes --language to lootcli --- src/loot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index d2059d7c..11eb4f4e 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -439,7 +439,8 @@ bool Loot::spawnLootcli( << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) << "--logLevel" << QString::fromStdString(lootcli::logLevelToString(logLevel)) - << "--out" << QString("\"%1\"").arg(LootReportPath); + << "--out" << QString("\"%1\"").arg(LootReportPath) + << "--language" << core.settings().interface().language(); if (didUpdateMasterList) { parameters << "--skipUpdateMasterlist"; -- cgit v1.3.1 From f1b621d0babd33537cde97fc9d53e0dfa0ad5ea5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 25 Nov 2019 03:34:51 -0500 Subject: save/restore state for loot dialog --- src/lootdialog.cpp | 18 +++++++++++++++--- src/lootdialog.h | 3 +++ 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index a8f73cdc..9e269fef 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -3,7 +3,6 @@ #include "loot.h" #include "organizercore.h" #include -#include #include using namespace MOBase; @@ -151,6 +150,20 @@ void LootDialog::openReport() shell::Open(path); } +int LootDialog::exec() +{ + auto& s = m_core.settings(); + + GeometrySaver gs(s, this); + s.geometry().restoreState(&m_expander); + + const auto r = QDialog::exec(); + + s.geometry().saveState(&m_expander); + + return r; +} + void LootDialog::accept() { // no-op @@ -193,11 +206,10 @@ void LootDialog::createUI() log::error("can't open '{}', {}", path, f.errorString()); } + m_expander.set(ui->details, ui->detailsPanel); ui->openJsonReport->setEnabled(false); connect(ui->openJsonReport, &QPushButton::clicked, [&]{ openReport(); }); - new ExpanderWidget(ui->details, ui->detailsPanel); - ui->buttons->setStandardButtons(QDialogButtonBox::Cancel); resize(650, 450); diff --git a/src/lootdialog.h b/src/lootdialog.h index fcdeb304..bc8c01fb 100644 --- a/src/lootdialog.h +++ b/src/lootdialog.h @@ -3,6 +3,7 @@ #include #include +#include namespace Ui { class LootDialog; } @@ -53,11 +54,13 @@ public: void cancel(); void openReport(); + int exec() override; void accept() override; void reject() override; private: std::unique_ptr ui; + MOBase::ExpanderWidget m_expander; OrganizerCore& m_core; Loot& m_loot; bool m_finished; -- cgit v1.3.1 From 78ee23220f4b755515dfb391aeb3fbdb6d48f0d6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 25 Nov 2019 05:31:28 -0500 Subject: bumped to 2.2.2alpha7 use the broom icon for dirty plugins better handling of failing to spawn loot --- src/loot.cpp | 7 +++++-- src/lootdialog.cpp | 16 ++++++++++------ src/pluginlist.cpp | 10 +++++----- src/version.rc | 4 ++-- 4 files changed, 22 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 11eb4f4e..a7251965 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -252,7 +252,7 @@ QString Loot::Report::toMarkdown() const } if (s.isEmpty()) { - s += "**" + QObject::tr("No messages.") + "**"; + s += "**" + QObject::tr("No messages.") + "**\n"; } s += stats.toMarkdown(); @@ -833,7 +833,10 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) Loot loot; LootDialog dialog(parent, core, loot); - loot.start(parent, core, didUpdateMasterList); + if (!loot.start(parent, core, didUpdateMasterList)) { + return false; + } + dialog.exec(); return dialog.result(); diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index 9e269fef..ae3b1164 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -262,12 +262,16 @@ void LootDialog::log(log::Levels lv, const QString& s) void LootDialog::showReport() { - const auto& lootReport = m_loot.report(); + if (m_loot.result()) { + const auto& lootReport = m_loot.report(); - m_core.pluginList()->clearAdditionalInformation(); - for (auto&& p : lootReport.plugins) { - m_core.pluginList()->addLootReport(p.name, p); - } + m_core.pluginList()->clearAdditionalInformation(); + for (auto&& p : lootReport.plugins) { + m_core.pluginList()->addLootReport(p.name, p); + } - m_report.setText(lootReport.toMarkdown()); + m_report.setText(lootReport.toMarkdown()); + } else { + m_report.setText("**" + tr("Loot failed to run") + "**"); + } } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index e91d820d..3f2f4018 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1214,10 +1214,14 @@ QVariant PluginList::iconData(const QModelIndex &modelIndex) const result.append(":/MO/gui/archive_conflict_neutral"); } - if (esp.isLightFlagged && !m_ESPs[index].isLight) { + if (esp.isLightFlagged && !esp.isLight) { result.append(":/MO/gui/awaiting"); } + if (info && !info->loot.dirty.empty()) { + result.append(":/MO/gui/edit_clear"); + } + return result; } @@ -1250,10 +1254,6 @@ bool PluginList::hasInfo(const ESPInfo& esp, const AdditionalInfo* info) const if (!info->loot.messages.empty()) { return true; } - - if (!info->loot.dirty.empty()) { - return true; - } } return false; diff --git a/src/version.rc b/src/version.rc index be73324c..983c1a8f 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,2,5 -#define VER_FILEVERSION_STR "2.2.2alpha5\0" +#define VER_FILEVERSION 2,2,2,7 +#define VER_FILEVERSION_STR "2.2.2alpha7\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From bec112069344281cbf1c61ade2eb1b354780d074 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 25 Nov 2019 09:26:46 -0500 Subject: removed calls to GetOverlappedResultEx() and SetThreadDescription(), not available on windows 7 bumped to alpha7.1 --- src/loot.cpp | 12 +++++++++--- src/version.rc | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index a7251965..47a70596 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -159,7 +159,15 @@ private: { DWORD bytesRead = 0; - if (!::GetOverlappedResultEx(m_stdout.get(), &m_ov, &bytesRead, PipeTimeout, FALSE)) { + const auto r = WaitForSingleObject(m_readEvent.get(), PipeTimeout); + + if (r == WAIT_FAILED) { + const auto e = GetLastError(); + log::error("WaitForSingleObject in AsyncPipe failed, {}", formatSystemMessage(e)); + return {}; + } + + if (!::GetOverlappedResult(m_stdout.get(), &m_ov, &bytesRead, FALSE)) { const auto e = GetLastError(); switch (e) @@ -490,8 +498,6 @@ const Loot::Report& Loot::report() const void Loot::lootThread() { - ::SetThreadDescription(GetCurrentThread(), L"loot"); - try { m_result = false; diff --git a/src/version.rc b/src/version.rc index 983c1a8f..7dac7b3a 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2,7 -#define VER_FILEVERSION_STR "2.2.2alpha7\0" +#define VER_FILEVERSION_STR "2.2.2alpha7.1\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 2527d6aa87f36894c291512a14f12940a5100484 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 03:30:44 -0500 Subject: switched to using a job object to monitor for processes so child processes can also be captured --- src/envmodule.cpp | 180 ++++++++++++++++++++++++++++++++++++++++++++--- src/envmodule.h | 5 +- src/processrunner.cpp | 186 +++++++++++++++++++++++++++++++------------------ src/usvfsconnector.cpp | 8 ++- 4 files changed, 298 insertions(+), 81 deletions(-) (limited to 'src') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 09593e61..13831631 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -365,16 +365,13 @@ const QString& Process::name() const HandlePtr Process::openHandleForWait() const { - HandlePtr h(OpenProcess( - PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, m_pid)); - - if (!h) { - const auto e = GetLastError(); - log::error("can't get name of process {}, {}", m_pid, formatSystemMessage(e)); - return {}; - } + const auto rights = + PROCESS_QUERY_LIMITED_INFORMATION | // exit code, image name, etc. + SYNCHRONIZE | // wait functions + PROCESS_SET_QUOTA | PROCESS_TERMINATE; // add to job - return h; + // don't log errors, failure can happen if the process doesn't exist + return HandlePtr(OpenProcess(rights, FALSE, m_pid)); } // whether this process can be accessed; fails if the current process doesn't @@ -405,6 +402,11 @@ std::vector& Process::children() return m_children; } +const std::vector& Process::children() const +{ + return m_children; +} + std::vector getLoadedModules() { @@ -531,9 +533,10 @@ void findChildren(Process& parent, const std::vector& processes) } } -Process getProcessTree(HANDLE parent) + +Process getProcessTreeFromProcess(HANDLE h) { - const auto parentPID = ::GetProcessId(parent); + const auto parentPID = ::GetProcessId(h); const auto v = getRunningProcesses(); Process root; @@ -553,6 +556,161 @@ Process getProcessTree(HANDLE parent) return root; } + +std::vector processesInJob(HANDLE h) +{ + for (int tries=0; tries<5; ++tries) { + DWORD maxIds = 100; + + const DWORD idsSize = sizeof(ULONG_PTR) * maxIds; + const DWORD bufferSize = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + idsSize; + + MallocPtr buffer(std::malloc(bufferSize)); + auto* ids = static_cast(buffer.get()); + + const auto r = QueryInformationJobObject( + h, JobObjectBasicProcessIdList, ids, bufferSize, nullptr); + + if (!r) { + const auto e = GetLastError(); + log::error("failed to get process ids in job, {}", formatSystemMessage(e)); + return {}; + } + + if (ids->NumberOfProcessIdsInList >= ids->NumberOfAssignedProcesses) { + std::vector v; + for (DWORD i=0; iNumberOfProcessIdsInList; ++i) { + v.push_back(ids->ProcessIdList[i]); + } + + return v; + } + + // try again with a larger buffer + maxIds *= 2; + } + + log::error("failed to get processes in job, can't get a buffer large enough"); + return {}; +} + + +void findChildProcesses(Process& parent, std::vector& processes) +{ + // find all processes that are direct children of `parent` + auto itor = processes.begin(); + + while (itor != processes.end()) { + if (itor->ppid() == parent.pid()) { + parent.addChild(*itor); + itor = processes.erase(itor); + } else { + ++itor; + } + } + + // find all processes that are direct children of `parent`'s children + for (auto&& c : parent.children()) { + findChildProcesses(c, processes); + } +} + +Process getProcessTreeFromJob(HANDLE h) +{ + const auto ids = processesInJob(h); + if (ids.empty()) { + return {}; + } + + std::vector ps; + + forEachRunningProcess([&](auto&& entry) { + for (auto&& id : ids) { + if (entry.th32ProcessID == id) { + ps.push_back(Process( + entry.th32ProcessID, + entry.th32ParentProcessID, + QString::fromStdWString(entry.szExeFile))); + + break; + } + } + + return true; + }); + + Process root; + + { + // getting processes whose parent is not in the list + for (auto&& possibleRoot : ps) { + const auto ppid = possibleRoot.ppid(); + bool found = false; + + for (auto&& p : ps) { + if (p.pid() == ppid) { + found = true; + break; + } + } + + if (!found) { + // this is a root process + root.addChild(possibleRoot); + } + } + + // removing root processes from the list + auto newEnd = std::remove_if(ps.begin(), ps.end(), [&](auto&& p) { + for (auto&& rp : root.children()) { + if (rp.pid() == p.pid()) { + return true; + } + } + + return false; + }); + + ps.erase(newEnd, ps.end()); + } + + // at this point, `processes` should only contain processes that are direct + // or indirect children of the ones in `root` + + if (ps.empty()) { + // and that's all there is + return root; + } + + { + // recursively find children + for (auto&& r : root.children()) { + findChildProcesses(r, ps); + } + } + + return root; +} + +bool isJobHandle(HANDLE h) +{ + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION info = {}; + + const auto r = ::QueryInformationJobObject( + h, JobObjectBasicAccountingInformation, &info, sizeof(info), nullptr); + + return r; +} + +Process getProcessTree(HANDLE h) +{ + if (isJobHandle(h)) { + return getProcessTreeFromJob(h); + } else { + return getProcessTreeFromProcess(h); + } +} + QString getProcessName(DWORD pid) { HandlePtr h(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); diff --git a/src/envmodule.h b/src/envmodule.h index d152b840..c2829b32 100644 --- a/src/envmodule.h +++ b/src/envmodule.h @@ -136,6 +136,7 @@ public: void addChild(Process p); std::vector& children(); + const std::vector& children() const; private: DWORD m_pid; @@ -148,7 +149,9 @@ private: std::vector getRunningProcesses(); std::vector getLoadedModules(); -Process getProcessTree(HANDLE parent); +// works for both jobs and processes +// +Process getProcessTree(HANDLE h); QString getProcessName(DWORD pid); QString getProcessName(HANDLE process); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index cfddbb63..ce1ac681 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -106,21 +106,46 @@ QString toString(Interest i) } +struct InterestingProcess +{ + env::Process p; + Interest interest = Interest::None; + env::HandlePtr handle; +}; + + +InterestingProcess findRandomProcess(const env::Process& root) +{ + for (auto&& c : root.children()) { + env::HandlePtr h = c.openHandleForWait(); + if (h) { + return {c, Interest::Weak, std::move(h)}; + } + + auto r = findRandomProcess(c); + if (r.handle) { + return r; + } + } + + return {}; +} + // returns a process that's in the hidden list, or the top-level process if // they're all hidden; returns an invalid process if the list is empty // -std::pair findInterestingProcessInTrees( - std::vector& processes) +InterestingProcess findInterestingProcessInTrees(const env::Process& root) { - if (processes.empty()) { - return {{}, Interest::None}; - } - // Certain process names we wish to "hide" for aesthetic reason: - const std::vector hiddenList = { - QFileInfo(QCoreApplication::applicationFilePath()).fileName() + static const std::vector hiddenList = { + QFileInfo(QCoreApplication::applicationFilePath()).fileName(), + "conhost.exe" }; + if (root.children().empty()) { + return {}; + } + auto isHidden = [&](auto&& p) { for (auto h : hiddenList) { if (p.name().contains(h, Qt::CaseInsensitive)) { @@ -132,54 +157,61 @@ std::pair findInterestingProcessInTrees( }; - for (auto&& root : processes) { - if (!isHidden(root)) { - return {root, Interest::Strong}; + for (auto&& p : root.children()) { + if (!isHidden(p)) { + env::HandlePtr h = p.openHandleForWait(); + if (h) { + return {p, Interest::Strong, std::move(h)}; + } } - for (auto&& child : root.children()) { - if (!isHidden(child)) { - return {child, Interest::Strong}; - } + auto r = findInterestingProcessInTrees(p); + if (r.interest == Interest::Strong) { + return r; } } - // everything is hidden, just pick the first one - return {processes[0], Interest::Weak}; + // everything is hidden, just pick the first one that can be used + return findRandomProcess(root); } -// gets the most interesting process in the list -// -std::pair getInterestingProcess( - const std::vector& initialProcesses) +void dump(const env::Process& p, int indent) { - if (initialProcesses.empty()) { - log::debug("nothing to wait for"); - return {{}, Interest::None}; + log::debug( + "{}{}, pid={}, ppid={}", + std::string(indent * 4, ' '), p.name(), p.pid(), p.ppid()); + + for (auto&& c : p.children()) { + dump(c, indent + 1); } +} - std::vector processes; +void dump(const env::Process& root) +{ + log::debug("process tree:"); - // getting process trees for all processes - for (auto&& h : initialProcesses) { - auto tree = env::getProcessTree(h); - if (tree.isValid()) { - processes.push_back(tree); - } + for (auto&& p : root.children()) { + dump(p, 1); } +} - if (processes.empty()) { - // if the initial list wasn't empty but this one is, it means all the - // processes were already completed - log::debug("processes are already completed"); - return {{}, Interest::None}; +// gets the most interesting process in the list +// +InterestingProcess getInterestingProcess(HANDLE job) +{ + env::Process root = env::getProcessTree(job); + if (root.children().empty()) { + log::debug("nothing to wait for"); + return {}; } - const auto interest = findInterestingProcessInTrees(processes); - if (!interest.first.isValid()) { - // this shouldn't happen + dump(root); + + auto interest = findInterestingProcessInTrees(root); + if (!interest.handle) { + // this can happen if none of the processes can be opened log::debug("no interesting process to wait for"); - return {{}, Interest::None}; + return {}; } return interest; @@ -255,56 +287,52 @@ std::optional timedWait( } ProcessRunner::Results waitForProcessesThreadImpl( - const std::vector& initialProcesses, UILocker::Session& ls) + HANDLE job, UILocker::Session& ls) { using namespace std::chrono; - if (initialProcesses.empty()) { - // shouldn't happen - return ProcessRunner::Completed; - } - DWORD currentPID = 0; // if the interesting process that was found is weak (such as ModOrganizer.exe // when starting a program from within the Data directory), start with a short // wait and check for more interesting children - milliseconds wait(50); + const milliseconds defaultWait(50); + auto wait = defaultWait; for (;;) { - auto [p, interest] = getInterestingProcess(initialProcesses); - if (!p.isValid()) { + auto ip = getInterestingProcess(job); + if (!ip.handle) { // nothing to wait on return ProcessRunner::Completed; } // update the lock widget - ls.setInfo(p.pid(), p.name()); + ls.setInfo(ip.p.pid(), ip.p.name()); - // open the process - auto interestingHandle = p.openHandleForWait(); - if (!interestingHandle) { - return ProcessRunner::Error; - } - - if (p.pid() != currentPID) { + if (ip.p.pid() != currentPID) { // log any change in the process being waited for - currentPID = p.pid(); + currentPID = ip.p.pid(); log::debug( "waiting for completion on {} ({}), {} interest", - p.name(), p.pid(), toString(interest)); + ip.p.name(), ip.p.pid(), toString(ip.interest)); } - if (interest == Interest::Strong) { + if (ip.interest == Interest::Strong) { // don't bother with short wait, this is a good process to wait for wait = Infinite; } - const auto r = timedWait(interestingHandle.get(), p.pid(), ls, wait); + const auto r = timedWait(ip.handle.get(), ip.p.pid(), ls, wait); if (r) { - // the process has completed or returned an error - return *r; + if (*r == ProcessRunner::Results::Completed) { + // process completed, check another one, reset the wait time to find + // interesting processes + wait = defaultWait; + } else if (*r != ProcessRunner::Results::Running) { + // something's wrong, or the user unlocked the ui + return *r; + } } // exponentially increase the wait time between checks for interesting @@ -314,20 +342,44 @@ ProcessRunner::Results waitForProcessesThreadImpl( } void waitForProcessesThread( - ProcessRunner::Results& result, - const std::vector& initialProcesses, UILocker::Session& ls) + ProcessRunner::Results& result, HANDLE job, UILocker::Session& ls) { - result = waitForProcessesThreadImpl(initialProcesses, ls); + result = waitForProcessesThreadImpl(job, ls); ls.unlock(); } ProcessRunner::Results waitForProcesses( const std::vector& initialProcesses, UILocker::Session& ls) { + // using a job so any child process started by any of those processes can also + // be captured and monitored + env::HandlePtr job(CreateJobObjectW(nullptr, nullptr)); + if (!job) { + const auto e = GetLastError(); + + log::error( + "failed to create job to wait for processes, {}", + formatSystemMessage(e)); + + return ProcessRunner::Error; + } + + for (auto&& h : initialProcesses) { + if (!::AssignProcessToJobObject(job.get(), h)) { + const auto e = GetLastError(); + + log::error( + "can't assign process to job to wait for processes, {}", + formatSystemMessage(e)); + + // keep going + } + } + auto results = ProcessRunner::Running; auto* t = QThread::create( - waitForProcessesThread, std::ref(results), initialProcesses, std::ref(ls)); + waitForProcessesThread, std::ref(results), job.get(), std::ref(ls)); QEventLoop events; QObject::connect(t, &QThread::finished, [&]{ diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 3dba3efc..3c8c355b 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -280,13 +280,17 @@ std::vector getRunningUSVFSProcesses() const auto thisPid = GetCurrentProcessId(); std::vector v; + const auto rights = + PROCESS_QUERY_LIMITED_INFORMATION | // exit code, image name, etc. + SYNCHRONIZE | // wait functions + PROCESS_SET_QUOTA | PROCESS_TERMINATE; // add to job + for (auto&& pid : pids) { if (pid == thisPid) { continue; // obviously don't wait for MO process } - HANDLE handle = ::OpenProcess( - PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pid); + HANDLE handle = ::OpenProcess(rights, FALSE, pid); if (handle == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); -- cgit v1.3.1 From a99dd1d6ebf972888bb872441c23c93129466b41 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 03:36:24 -0500 Subject: fixed timing crash when trying to update the lock ui after it was closed --- src/uilocker.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src') diff --git a/src/uilocker.cpp b/src/uilocker.cpp index 01794d6b..8c4e0c2c 100644 --- a/src/uilocker.cpp +++ b/src/uilocker.cpp @@ -377,6 +377,8 @@ UILocker::~UILocker() unlock(s.get()); } } + + g_instance = nullptr; } UILocker& UILocker::instance() @@ -449,6 +451,12 @@ void UILocker::unlockCurrent() void UILocker::updateLabel() { + if (!m_ui) { + // this can happen if the lock overlay was destroyed while a cross-thread + // call for updateLabel() was in flight + return; + } + QStringList labels; for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { -- cgit v1.3.1 From 42fb0c6bfc0a9e2e16b7b82bd137235e2a1422a2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 03:50:15 -0500 Subject: ignore loot reports about disabled plugins --- src/loot.cpp | 48 ++++++++++++++++++++++++++++++++---------------- src/loot.h | 8 ++++---- 2 files changed, 36 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index 47a70596..a86cdcff 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -385,8 +385,8 @@ QString Loot::Message::toMarkdown() const } -Loot::Loot() - : m_thread(nullptr), m_cancel(false), m_result(false) +Loot::Loot(OrganizerCore& core) + : m_core(core), m_thread(nullptr), m_cancel(false), m_result(false) { } @@ -408,7 +408,7 @@ Loot::~Loot() } } -bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) +bool Loot::start(QWidget* parent, bool didUpdateMasterList) { log::debug("starting loot"); @@ -420,10 +420,10 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) } // vfs - core.prepareVFS(); + m_core.prepareVFS(); // spawning - if (!spawnLootcli(parent, core, didUpdateMasterList, std::move(stdoutHandle))) { + if (!spawnLootcli(parent, didUpdateMasterList, std::move(stdoutHandle))) { return false; } @@ -436,19 +436,29 @@ bool Loot::start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) } bool Loot::spawnLootcli( - QWidget* parent, OrganizerCore& core, bool didUpdateMasterList, - env::HandlePtr stdoutHandle) + QWidget* parent, bool didUpdateMasterList, env::HandlePtr stdoutHandle) { - const auto logLevel = core.settings().diagnostics().lootLogLevel(); + const auto logLevel = m_core.settings().diagnostics().lootLogLevel(); QStringList parameters; parameters - << "--game" << core.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(core.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(core.profilePath()) - << "--logLevel" << QString::fromStdString(lootcli::logLevelToString(logLevel)) - << "--out" << QString("\"%1\"").arg(LootReportPath) - << "--language" << core.settings().interface().language(); + << "--game" + << m_core.managedGame()->gameShortName() + + << "--gamePath" + << QString("\"%1\"").arg(m_core.managedGame()->gameDirectory().absolutePath()) + + << "--pluginListPath" + << QString("\"%1/loadorder.txt\"").arg(m_core.profilePath()) + + << "--logLevel" + << QString::fromStdString(lootcli::logLevelToString(logLevel)) + + << "--out" + << QString("\"%1\"").arg(LootReportPath) + + << "--language" + << m_core.settings().interface().language(); if (didUpdateMasterList) { parameters << "--skipUpdateMasterlist"; @@ -696,6 +706,12 @@ Loot::Plugin Loot::reportPlugin(const QJsonObject& plugin) const return {}; } + // ignore disabled plugins; lootcli doesn't know if a plugin is enabled or not + // and will report information on any plugin that's in the filesystem + if (!m_core.pluginList()->isEnabled(p.name)) { + return {}; + } + if (plugin.contains("incompatibilities")) { p.incompatibilities = reportFiles(getOpt(plugin, "incompatibilities")); } @@ -836,10 +852,10 @@ bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList) core.savePluginList(); try { - Loot loot; + Loot loot(core); LootDialog dialog(parent, core, loot); - if (!loot.start(parent, core, didUpdateMasterList)) { + if (!loot.start(parent, didUpdateMasterList)) { return false; } diff --git a/src/loot.h b/src/loot.h index 7e5e01bd..4ec06d6f 100644 --- a/src/loot.h +++ b/src/loot.h @@ -79,10 +79,10 @@ public: }; - Loot(); + Loot(OrganizerCore& core); ~Loot(); - bool start(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); + bool start(QWidget* parent, bool didUpdateMasterList); void cancel(); bool result() const; const QString& outPath() const; @@ -95,6 +95,7 @@ signals: void finished(); private: + OrganizerCore& m_core; std::unique_ptr m_thread; std::atomic m_cancel; std::atomic m_result; @@ -104,8 +105,7 @@ private: Report m_report; bool spawnLootcli( - QWidget* parent, OrganizerCore& core, bool didUpdateMasterList, - env::HandlePtr stdoutHandle); + QWidget* parent, bool didUpdateMasterList, env::HandlePtr stdoutHandle); void lootThread(); bool waitForCompletion(); -- cgit v1.3.1 From 5fb785aceecf32d6012bad044027572e02d22a9a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 04:05:11 -0500 Subject: bumped to 2.2.2-alpha8 --- src/version.rc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 7dac7b3a..963d1f11 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,2,7 -#define VER_FILEVERSION_STR "2.2.2alpha7.1\0" +#define VER_FILEVERSION 2,2,2,8 +#define VER_FILEVERSION_STR "2.2.2alpha8\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 255f96ce7e27124f1bed071564e67a2e4a25c8aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 05:47:11 -0500 Subject: only log crash dumps message on startup --- src/main.cpp | 14 ++++++++++++++ src/organizercore.cpp | 6 ------ 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 02347ee3..41ce0eb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -554,6 +554,20 @@ int runApplication(MOApplication &application, SingleInstance &instance, return 1; } + { + // log if there are any dmp files + const auto hasCrashDumps = + !QDir(QString::fromStdWString(organizer.crashDumpsPath())) + .entryList({"*.dmp"}, QDir::Files) + .empty(); + + if (hasCrashDumps) { + log::debug( + "there are crash dumps in '{}'", + QString::fromStdWString(organizer.crashDumpsPath())); + } + } + log::debug("initializing plugins"); PluginContainer pluginContainer(&organizer); pluginContainer.loadPlugins(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 9ceb149e..c585ba09 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -484,12 +484,6 @@ bool OrganizerCore::cycleDiagnostics() removeOldFiles(path, "*.dmp", maxDumps, QDir::Time|QDir::Reversed); } - // log if there are any files left - const auto files = QDir(path).entryList({"*.dmp"}, QDir::Files); - if (!files.isEmpty()) { - log::debug("there are crash dumps in '{}'", path); - } - return true; } -- cgit v1.3.1 From e67381b4b8731d80a4f11fb441d46424b571a659 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 06:14:13 -0500 Subject: log timezone --- src/env.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 4 ++++ 2 files changed, 48 insertions(+) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 507607d1..a23e65a4 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -104,10 +104,54 @@ const Metrics& Environment::metrics() const return *m_metrics; } +QString Environment::timezone() const +{ + TIME_ZONE_INFORMATION tz = {}; + + const auto r = GetTimeZoneInformation(&tz); + if (r == TIME_ZONE_ID_INVALID) { + const auto e = GetLastError(); + log::error("failed to get timezone, {}", formatSystemMessage(e)); + return "unknown"; + } + + auto offsetString = [](int o) { + return + QString("%1%2:%3") + .arg(o < 0 ? "" : "+") + .arg(QString::number(o / 60), 2, QChar::fromLatin1('0')) + .arg(QString::number(o % 60), 2, QChar::fromLatin1('0')); + }; + + const auto stdName = QString::fromWCharArray(tz.StandardName); + const auto stdOffset = -(tz.Bias + tz.StandardBias); + const auto std = QString("%1, %2") + .arg(stdName) + .arg(offsetString(stdOffset)); + + const auto dstName = QString::fromWCharArray(tz.DaylightName); + const auto dstOffset = -(tz.Bias + tz.DaylightBias); + const auto dst = QString("%1, %2") + .arg(dstName) + .arg(offsetString(dstOffset)); + + QString s; + + if (r == TIME_ZONE_ID_DAYLIGHT) { + s = dst + " (dst is active, std is " + std + ")"; + } else { + s = std + " (std is active, dst is " + dst + ")"; + } + + return s; +} + void Environment::dump(const Settings& s) const { log::debug("windows: {}", windowsInfo().toString()); + log::debug("time zone: {}", timezone()); + if (windowsInfo().compatibilityMode()) { log::warn("MO seems to be running in compatibility mode"); } diff --git a/src/env.h b/src/env.h index f95d1013..f8b1eb70 100644 --- a/src/env.h +++ b/src/env.h @@ -147,6 +147,10 @@ public: // const Metrics& metrics() const; + // timezone + // + QString timezone() const; + // logs the environment // void dump(const Settings& s) const; -- cgit v1.3.1 From 7e1403dd28ec5141e7f4ca3aa6b0bb6e8b0375b0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 06:18:13 -0500 Subject: simplified security products: don't log guids, remove duplicate entries --- src/env.cpp | 14 ++++++++++++-- src/envsecurity.cpp | 7 ------- 2 files changed, 12 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index a23e65a4..b3f9525f 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -157,8 +157,18 @@ void Environment::dump(const Settings& s) const } log::debug("security products:"); - for (const auto& sp : securityProducts()) { - log::debug(" . {}", sp.toString()); + + { + // ignore products with identical names, some AVs register themselves with + // the same names and provider, but different guids + std::set productNames; + for (const auto& sp : securityProducts()) { + productNames.insert(sp.toString()); + } + + for (auto&& name : productNames) { + log::debug(" . {}", name); + } } log::debug("modules loaded in process:"); diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 6d62728b..87db98ce 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -207,13 +207,6 @@ QString SecurityProduct::toString() const s += ", definitions outdated"; } - // all products have a guid, but the windows firewall is not actually a real - // one from wmi, it's queried independently in getWindowsFirewall() and has a - // null guid, so just don't log it - if (!m_guid.isNull()) { - s += ", " + m_guid.toString(QUuid::QUuid::WithoutBraces); - } - return s; } -- cgit v1.3.1 From 637188a58bd99e6355ced6c296533c362fb8efd6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 06:21:16 -0500 Subject: don't log md5 for any system file --- src/envmodule.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 13831631..8d348b5e 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -295,10 +295,19 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const QString Module::getMD5() const { - if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { - // don't calculate md5 for system files, it's not really relevant and - // it takes a while - return {}; + static const std::set ignore = { + "\\windows\\", + "\\program files\\", + "\\program files (x86)\\", + "\\programdata\\" + }; + + // don't calculate md5 for system files, it's not really relevant and + // it takes a while + for (auto&& i : ignore) { + if (m_path.contains(i, Qt::CaseInsensitive)) { + return {}; + } } // opening the file -- cgit v1.3.1 From 08bf03854d0688cd8dbbfed7d596888dbedfcb4b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 06:27:56 -0500 Subject: removed useless logging about servers --- src/downloadmanager.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index c84d4bd4..3143a22e 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -996,8 +996,8 @@ void DownloadManager::queryInfoMd5(int index) QCryptographicHash hash(QCryptographicHash::Md5); const qint64 progressStep = 10 * 1024 * 1024; QProgressDialog progress(tr("Hashing download file '%1'").arg(info->m_FileName), - tr("Cancel"), - 0, + tr("Cancel"), + 0, downloadFile.size() / progressStep); progress.setWindowModality(Qt::WindowModal); progress.setMinimumDuration(1000); @@ -1606,7 +1606,7 @@ void DownloadManager::nxmFilesAvailable(QString, int, QVariant userData, QVarian emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); } else { SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); - std::sort(files.begin(), files.end(), [](const QVariant& lhs, const QVariant& rhs) + std::sort(files.begin(), files.end(), [](const QVariant& lhs, const QVariant& rhs) {return lhs.toMap()["uploaded_timestamp"].toInt() > rhs.toMap()["uploaded_timestamp"].toInt();}); for (QVariant file : files) { QVariantMap fileInfo = file.toMap(); @@ -1692,7 +1692,6 @@ static int evaluateFileInfoMap( } if (!found) { - log::error("server '{}' not found while sorting by preference", name); return 0; } -- cgit v1.3.1 From 2c2c47c21502db6471fe7d727f942c2400b150c1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 07:06:20 -0500 Subject: log new modules being loaded after startup --- src/env.cpp | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 24 ++++++++++ src/main.cpp | 4 ++ 3 files changed, 175 insertions(+) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index b3f9525f..51631607 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -56,6 +56,55 @@ Console::~Console() } +ModuleNotification::ModuleNotification(std::function f) + : m_cookie(nullptr), m_f(std::move(f)) +{ +} + +ModuleNotification::~ModuleNotification() +{ + if (!m_cookie) { + return; + } + + typedef NTSTATUS NTAPI LdrUnregisterDllNotificationType( + PVOID Cookie + ); + + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + log::error("failed to load ntdll.dll while unregistering for module notifications"); + return; + } + + auto* LdrUnregisterDllNotification = reinterpret_cast( + GetProcAddress(ntdll.get(), "LdrUnregisterDllNotification")); + + if (!LdrUnregisterDllNotification) { + log::error("LdrUnregisterDllNotification not found in ntdll.dll"); + return; + } + + const auto r = LdrUnregisterDllNotification(m_cookie); + if (r != 0) { + log::error("failed to unregister for module notifications, error {}", r); + } +} + +void ModuleNotification::setCookie(void* c) +{ + m_cookie = c; +} + +void ModuleNotification::fire(const Module& m) +{ + if (m_f) { + m_f(m); + } +} + + Environment::Environment() { } @@ -146,6 +195,104 @@ QString Environment::timezone() const return s; } +std::unique_ptr Environment::onModuleLoaded( + std::function f) +{ + typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } UNICODE_STRING, *PUNICODE_STRING; + + typedef const PUNICODE_STRING PCUNICODE_STRING; + + typedef struct _LDR_DLL_LOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + PCUNICODE_STRING FullDllName; //The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. + PVOID DllBase; //A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; //The size of the DLL image, in bytes. + } LDR_DLL_LOADED_NOTIFICATION_DATA, *PLDR_DLL_LOADED_NOTIFICATION_DATA; + + typedef struct _LDR_DLL_UNLOADED_NOTIFICATION_DATA { + ULONG Flags; //Reserved. + PCUNICODE_STRING FullDllName; //The full path name of the DLL module. + PCUNICODE_STRING BaseDllName; //The base file name of the DLL module. + PVOID DllBase; //A pointer to the base address for the DLL in memory. + ULONG SizeOfImage; //The size of the DLL image, in bytes. + } LDR_DLL_UNLOADED_NOTIFICATION_DATA, *PLDR_DLL_UNLOADED_NOTIFICATION_DATA; + + typedef union _LDR_DLL_NOTIFICATION_DATA { + LDR_DLL_LOADED_NOTIFICATION_DATA Loaded; + LDR_DLL_UNLOADED_NOTIFICATION_DATA Unloaded; + } LDR_DLL_NOTIFICATION_DATA, *PLDR_DLL_NOTIFICATION_DATA; + + typedef VOID CALLBACK LDR_DLL_NOTIFICATION_FUNCTION( + ULONG NotificationReason, + const PLDR_DLL_NOTIFICATION_DATA NotificationData, + PVOID Context + ); + + typedef LDR_DLL_NOTIFICATION_FUNCTION* PLDR_DLL_NOTIFICATION_FUNCTION; + + typedef NTSTATUS NTAPI LdrRegisterDllNotificationType( + ULONG Flags, + PLDR_DLL_NOTIFICATION_FUNCTION NotificationFunction, + PVOID Context, + PVOID *Cookie + ); + + const ULONG LDR_DLL_NOTIFICATION_REASON_LOADED = 1; + const ULONG LDR_DLL_NOTIFICATION_REASON_UNLOADED = 2; + + + // loading ntdll.dll, the function will be found with GetProcAddress() + LibraryPtr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + log::error("failed to load ntdll.dll while registering for module notifications"); + return {}; + } + + auto* LdrRegisterDllNotification = reinterpret_cast( + GetProcAddress(ntdll.get(), "LdrRegisterDllNotification")); + + if (!LdrRegisterDllNotification) { + log::error("LdrRegisterDllNotification not found in ntdll.dll"); + return {}; + } + + + auto context = std::make_unique(f); + void* cookie = nullptr; + + auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) { + if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) { + const Module m( + QString::fromWCharArray( + data->Loaded.FullDllName->Buffer, + data->Loaded.FullDllName->Length / sizeof(wchar_t)), + data->Loaded.SizeOfImage); + + if (context) { + static_cast(context)->fire(m); + } + } + }; + + const auto r = LdrRegisterDllNotification( + 0, OnDllLoaded, context.get(), &cookie); + + if (r != 0) { + log::error("failed to register for module notifications, error {}", r); + return {}; + } + + context->setCookie(cookie); + + return context; +} + void Environment::dump(const Settings& s) const { log::debug("windows: {}", windowsInfo().toString()); diff --git a/src/env.h b/src/env.h index f8b1eb70..946cb13b 100644 --- a/src/env.h +++ b/src/env.h @@ -119,6 +119,27 @@ private: }; +class ModuleNotification +{ +public: + ModuleNotification(std::function f); + ~ModuleNotification(); + + ModuleNotification(const ModuleNotification&) = delete; + ModuleNotification& operator=(const ModuleNotification&) = delete; + + ModuleNotification(ModuleNotification&&) = default; + ModuleNotification& operator=(ModuleNotification&&) = default; + + void setCookie(void* c); + void fire(const Module& m); + +private: + void* m_cookie; + std::function m_f; +}; + + // represents the process's environment // class Environment @@ -151,6 +172,9 @@ public: // QString timezone() const; + std::unique_ptr onModuleLoaded( + std::function f); + // logs the environment // void dump(const Settings& s) const; diff --git a/src/main.cpp b/src/main.cpp index 41ce0eb5..6e112479 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -547,6 +547,10 @@ int runApplication(MOApplication &application, SingleInstance &instance, settings.dump(); sanityChecks(env); + const auto moduleNotification = env.onModuleLoaded([](auto&& m) { + log::debug("loaded module {}", m.toString()); + }); + log::debug("initializing core"); OrganizerCore organizer(settings); if (!organizer.bootstrap()) { -- cgit v1.3.1 From c3068ce50ab6e25e21452c2ea2f83086ddf555d5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 08:39:22 -0500 Subject: added rivatuner to sanity checks now also checks modules loaded after startup fixed crash on w7 when checking some modules --- src/env.cpp | 44 ++++++++++++++++++++++++++++++-------------- src/env.h | 11 ++++++++--- src/main.cpp | 4 +++- src/sanitychecks.cpp | 37 ++++++++++++++++++++++--------------- 4 files changed, 63 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 51631607..f9507dc1 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -56,8 +56,8 @@ Console::~Console() } -ModuleNotification::ModuleNotification(std::function f) - : m_cookie(nullptr), m_f(std::move(f)) +ModuleNotification::ModuleNotification(QObject* o, std::function f) + : m_cookie(nullptr), m_object(o), m_f(std::move(f)) { } @@ -97,10 +97,26 @@ void ModuleNotification::setCookie(void* c) m_cookie = c; } -void ModuleNotification::fire(const Module& m) +void ModuleNotification::fire(QString path, std::size_t fileSize) { + if (m_loaded.contains(path)) { + // don't notify if it's been loaded before + } + + m_loaded.insert(path); + + // constructing a Module will query the version info of the file, which seems + // to generate an access violation for at least plugin_python.dll on Windows 7 + // + // it's not clear what the problem is, but making sure this is deferred until + // _after_ the dll is loaded seems to fix it + // + // so this queues the callback in the main thread + if (m_f) { - m_f(m); + QMetaObject::invokeMethod(m_object, [path, fileSize, f=m_f] { + f(Module(path, fileSize)); + }, Qt::QueuedConnection); } } @@ -196,7 +212,7 @@ QString Environment::timezone() const } std::unique_ptr Environment::onModuleLoaded( - std::function f) + QObject* o, std::function f) { typedef struct _UNICODE_STRING { USHORT Length; @@ -263,19 +279,19 @@ std::unique_ptr Environment::onModuleLoaded( } - auto context = std::make_unique(f); + auto context = std::make_unique(o, f); void* cookie = nullptr; auto OnDllLoaded = [](ULONG reason, const PLDR_DLL_NOTIFICATION_DATA data, void* context) { if (reason == LDR_DLL_NOTIFICATION_REASON_LOADED) { - const Module m( - QString::fromWCharArray( - data->Loaded.FullDllName->Buffer, - data->Loaded.FullDllName->Length / sizeof(wchar_t)), - data->Loaded.SizeOfImage); - - if (context) { - static_cast(context)->fire(m); + if (data && data->Loaded.FullDllName) { + if (context) { + static_cast(context)->fire( + QString::fromWCharArray( + data->Loaded.FullDllName->Buffer, + data->Loaded.FullDllName->Length / sizeof(wchar_t)), + data->Loaded.SizeOfImage); + } } } }; diff --git a/src/env.h b/src/env.h index 946cb13b..dc0fd864 100644 --- a/src/env.h +++ b/src/env.h @@ -122,7 +122,7 @@ private: class ModuleNotification { public: - ModuleNotification(std::function f); + ModuleNotification(QObject* o, std::function f); ~ModuleNotification(); ModuleNotification(const ModuleNotification&) = delete; @@ -132,10 +132,12 @@ public: ModuleNotification& operator=(ModuleNotification&&) = default; void setCookie(void* c); - void fire(const Module& m); + void fire(QString path, std::size_t fileSize); private: void* m_cookie; + QObject* m_object; + std::set m_loaded; std::function m_f; }; @@ -172,8 +174,11 @@ public: // QString timezone() const; + // will call `f` on the same thread `o` is running on every time a module + // is loaded in the process + // std::unique_ptr onModuleLoaded( - std::function f); + QObject* o, std::function f); // logs the environment // diff --git a/src/main.cpp b/src/main.cpp index 6e112479..3cccf365 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,6 +93,7 @@ using namespace MOShared; void sanityChecks(const env::Environment& env); +int checkIncompatibleModule(const env::Module& m); bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); @@ -547,8 +548,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, settings.dump(); sanityChecks(env); - const auto moduleNotification = env.onModuleLoaded([](auto&& m) { + const auto moduleNotification = env.onModuleLoaded(qApp, [](auto&& m) { log::debug("loaded module {}", m.toString()); + checkIncompatibleModule(m); }); log::debug("initializing core"); diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 3b4185a7..495795f2 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -202,29 +202,36 @@ int checkMissingFiles() return n; } -bool checkNahimic(const env::Environment& e) +int checkIncompatibleModule(const env::Module& m) { - // Nahimic seems to interfere mostly with dialogs, like the mod info dialog: - // it renders dialogs fully white and makes it impossible to interact with - // them + // these dlls seems to interfere mostly with dialogs, like the mod info + // dialog: it renders dialogs fully white and makes it impossible to interact + // with them // - // NahimicOSD.dll is usually loaded on startup, but there has been some - // reports where it got loaded later, so this check is not entirely accurate + // the dlls is usually loaded on startup, but there has been some reports + // where it got loaded later, so this is also called every time a new module + // is loaded into this process - for (auto&& m : e.loadedModules()) { - const QFileInfo file(m.path()); + static const std::map names = { + {"NahimicOSD.dll", "Nahimic"}, + {"RTSSHooks64.dll", "RivaTuner Statistics Server"} + }; + + const QFileInfo file(m.path()); + int n = 0; - if (file.fileName().compare("NahimicOSD.dll", Qt::CaseInsensitive) == 0) { + for (auto&& p : names) { + if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) { log::warn( - "NahimicOSD.dll is loaded. Nahimic is known to cause issues with " + "{} is loaded. This program is known to cause issues with " "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it."); + "uninstalling it. ({})", p.second, file.absoluteFilePath()); - return true; + ++n; } } - return false; + return n; } int checkIncompatibilities(const env::Environment& e) @@ -233,8 +240,8 @@ int checkIncompatibilities(const env::Environment& e) int n = 0; - if (checkNahimic(e)) { - ++n; + for (auto&& m : e.loadedModules()) { + n += checkIncompatibleModule(m); } return n; -- cgit v1.3.1 From 9194bfa16bb78c10bc4e23abd26ba16c33956794 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 08:59:04 -0500 Subject: added option to hide confirmation when switching instances --- src/mainwindow.cpp | 16 ++++++++++------ src/settings.cpp | 10 ++++++++++ src/settings.h | 5 +++++ src/settingsdialog.ui | 7 +++++++ src/settingsdialoggeneral.cpp | 2 ++ 5 files changed, 34 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9ea554a2..844456a6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6139,14 +6139,18 @@ void MainWindow::on_actionNotifications_triggered() void MainWindow::on_actionChange_Game_triggered() { - const auto r = QMessageBox::question( - this, tr("Are you sure?"), tr("This will restart MO, continue?"), - QMessageBox::Yes | QMessageBox::Cancel); + if (m_OrganizerCore.settings().interface().showChangeGameConfirmation()) { + const auto r = QMessageBox::question( + this, tr("Are you sure?"), tr("This will restart MO, continue?"), + QMessageBox::Yes | QMessageBox::Cancel); - if (r == QMessageBox::Yes) { - InstanceManager::instance().clearCurrentInstance(); - ExitModOrganizer(Exit::Restart); + if (r != QMessageBox::Yes) { + return; + } } + + InstanceManager::instance().clearCurrentInstance(); + ExitModOrganizer(Exit::Restart); } void MainWindow::setCategoryListVisible(bool visible) diff --git a/src/settings.cpp b/src/settings.cpp index b533b400..e1e5c2da 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1961,6 +1961,16 @@ void InterfaceSettings::setTutorialCompleted(const QString& windowName, bool b) set(m_Settings, "CompletedWindowTutorials", windowName, b); } +bool InterfaceSettings::showChangeGameConfirmation() const +{ + return get(m_Settings, "Settings", "show_change_game_confirmation", true); +} + +void InterfaceSettings::setShowChangeGameConfirmation(bool b) const +{ + set(m_Settings, "Settings", "show_change_game_confirmation", b); +} + DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) : m_Settings(settings) diff --git a/src/settings.h b/src/settings.h index d71fabf4..870e0fc4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -605,6 +605,11 @@ public: bool isTutorialCompleted(const QString& windowName) const; void setTutorialCompleted(const QString& windowName, bool b=true); + // whether to show the confirmation when switching instances + // + bool showChangeGameConfirmation() const; + void setShowChangeGameConfirmation(bool b) const; + private: QSettings& m_Settings; }; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index b88c8b71..e63ca692 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -108,6 +108,13 @@ + + + + Show confirmation when changing instance + + + diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index e21fc5d0..b0e64305 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -17,6 +17,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->colorTable->load(s); ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); + ui->changeGameConfirmation->setChecked(settings().interface().showChangeGameConfirmation()); ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); ui->checkForUpdates->setChecked(settings().checkForUpdates()); @@ -59,6 +60,7 @@ void GeneralSettingsTab::update() ui->colorTable->commitColors(); settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); + settings().interface().setShowChangeGameConfirmation(ui->changeGameConfirmation->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); -- cgit v1.3.1 From cde6137ca586239b36da59d46d7a1d3e55ed43ce Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 08:59:15 -0500 Subject: added loot to missing files check --- src/sanitychecks.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 495795f2..ef57a503 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -177,9 +177,14 @@ int checkMissingFiles() { // files that are likely to be eaten static const QStringList files({ - "helper.exe", "nxmhandler.exe", - "usvfs_proxy_x64.exe", "usvfs_proxy_x86.exe", - "usvfs_x64.dll", "usvfs_x86.dll" + "helper.exe", + "nxmhandler.exe", + "usvfs_proxy_x64.exe", + "usvfs_proxy_x86.exe", + "usvfs_x64.dll", + "usvfs_x86.dll", + "loot/loot.dll", + "loot/lootcli.exe" }); log::debug(" . missing files"); -- cgit v1.3.1 From c65fbcdb374f688cc92080643669349766181f80 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 09:07:43 -0500 Subject: ignore some instance folders like "cache" and "qtwebengine" --- src/instancemanager.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index ec3c1add..a9da30d9 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -259,7 +259,22 @@ QString InstanceManager::instancePath() const QStringList InstanceManager::instances() const { - return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot); + const std::set ignore = { + "cache", "qtwebengine", + }; + + const auto dirs = QDir(instancePath()) + .entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QStringList list; + + for (auto&& d : dirs) { + if (!ignore.contains(QFileInfo(d).fileName().toLower())) { + list.push_back(d); + } + } + + return list; } -- cgit v1.3.1 From 4db719a3bb0c80db8720456885489a72f4edb05b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 09:19:36 -0500 Subject: don't log EPT_S_NOT_REGISTERED errors, treat it as firewall disabled --- src/envsecurity.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/envsecurity.cpp b/src/envsecurity.cpp index 87db98ce..6f4826e7 100644 --- a/src/envsecurity.cpp +++ b/src/envsecurity.cpp @@ -383,7 +383,18 @@ std::optional getWindowsFirewall() hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); if (FAILED(hr)) { - log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr)); + // EPT_S_NOT_REGISTERED is "There are no more endpoints available from the + // endpoint mapper", which seems to happen sometimes on Windows 7 when the + // firewall has been disabled, so treat it as such and don't log it + // + // however the user reported the error was actually 0x800706d9, not just + // 0x6d9 (1753, what EPT_S_NOT_REGISTERED is defined to), so this is + // testing for both because it's not clear which it is and nobody can + // reproduce it + if (hr != EPT_S_NOT_REGISTERED && hr != 0x800706d9) { + log::error("get_FirewallEnabled failed, {}", formatSystemMessage(hr)); + } + return {}; } } -- cgit v1.3.1 From f85478a5b40ba609a0b36a2095e2b7fd3588857f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 10:00:37 -0500 Subject: fix sort in overwrite, remember settings remove useless header in archives list --- src/mainwindow.cpp | 1 + src/overwriteinfodialog.cpp | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 844456a6..79604c9a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -277,6 +277,7 @@ MainWindow::MainWindow(Settings &settings ui->espList->installEventFilter(m_OrganizerCore.pluginList()); ui->bsaList->setLocalMoveOnly(true); + ui->bsaList->setHeaderHidden(true); initDownloadView(); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 078bcfc9..b2f2f1f9 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -85,6 +85,7 @@ OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) ui->filesView->setModel(m_FileSystemModel); ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); ui->filesView->setColumnWidth(0, 250); + ui->filesView->sortByColumn(0, Qt::AscendingOrder); m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); m_RenameAction = new QAction(tr("&Rename"), ui->filesView); @@ -106,13 +107,21 @@ OverwriteInfoDialog::~OverwriteInfoDialog() void OverwriteInfoDialog::showEvent(QShowEvent* e) { - Settings::instance().geometry().restoreGeometry(this); + const auto& s = Settings::instance(); + + s.geometry().restoreGeometry(this); + s.geometry().restoreState(ui->filesView->header()); + QDialog::showEvent(e); } void OverwriteInfoDialog::done(int r) { - Settings::instance().geometry().saveGeometry(this); + auto& s = Settings::instance(); + + s.geometry().saveGeometry(this); + s.geometry().saveState(ui->filesView->header()); + QDialog::done(r); } -- cgit v1.3.1 From b8e1a45840a72e1e959f1fad2f00015a72500161 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 10:05:48 -0500 Subject: sortable filetree, remember settings --- src/modinfodialog.ui | 3 +++ src/modinfodialogfiletree.cpp | 11 +++++++++++ src/modinfodialogfiletree.h | 2 ++ 3 files changed, 16 insertions(+) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 13a245f1..59263e1c 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -1258,6 +1258,9 @@ p, li { white-space: pre-wrap; } true + + true + diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 71ea9210..ca007c1c 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -21,6 +21,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) m_fs->setReadOnly(false); ui->filetree->setModel(m_fs); ui->filetree->setColumnWidth(0, 300); + ui->filetree->sortByColumn(0, Qt::AscendingOrder); m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); @@ -55,6 +56,16 @@ void FileTreeTab::clear() setHasData(true); } +void FileTreeTab::saveState(Settings& s) +{ + s.geometry().saveState(ui->filetree->header()); +} + +void FileTreeTab::restoreState(const Settings& s) +{ + s.geometry().restoreState(ui->filetree->header()); +} + void FileTreeTab::update() { const auto rootPath = mod().absolutePath(); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 42773899..494a7e14 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -12,6 +12,8 @@ public: FileTreeTab(ModInfoDialogTabContext cx); void clear() override; + void saveState(Settings& s); + void restoreState(const Settings& s); void update() override; bool deleteRequested() override; -- cgit v1.3.1 From 9519bd62193ac1233e8087c4c143689557288c34 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 10:31:26 -0500 Subject: added open mod info to data tab context menu --- src/mainwindow.cpp | 23 +++++++++++++++++++++++ src/mainwindow.h | 1 + 2 files changed, 24 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 79604c9a..06e22e4e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5428,6 +5428,27 @@ void MainWindow::openDataOriginExplorer_clicked() shell::Explore(fullPath); } +void MainWindow::openDataModInfo_clicked() +{ + if (m_ContextItem == nullptr) { + return; + } + + const auto originID = m_ContextItem->data(1, Qt::UserRole + 1).toInt(); + const auto& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + const auto& name = QString::fromStdWString(origin.getName()); + + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (modInfo) { + displayModInformation(modInfo, index, ModInfoTabIDs::None); + } +} + void MainWindow::updateAvailable() { ui->actionUpdate->setEnabled(true); @@ -5473,6 +5494,8 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked())); } + menu.addAction("Open Mod Info", this, SLOT(openDataModInfo_clicked())); + // offer to hide/unhide file, but not for files from archives if (!isArchive) { if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 7c2ee1eb..0c96c15d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -448,6 +448,7 @@ private slots: void hideFile(); void unhideFile(); void openDataOriginExplorer_clicked(); + void openDataModInfo_clicked(); // pluginlist context menu void enableSelectedPlugins_clicked(); -- cgit v1.3.1 From a87874497a3fbe7b3ac646479d4ba51883f5acc6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 26 Nov 2019 16:04:21 -0500 Subject: cwd was MO instead of binary if non was specified in the exe settings --- src/processrunner.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/processrunner.cpp b/src/processrunner.cpp index ce1ac681..89777072 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -520,9 +520,9 @@ ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe) forcedLibraries = profile->determineForcedLibraries(exe.title()); } - QDir currentDirectory = exe.workingDirectory(); + QString currentDirectory = exe.workingDirectory(); if (currentDirectory.isEmpty()) { - currentDirectory.setPath(exe.binaryInfo().absolutePath()); + currentDirectory = exe.binaryInfo().absolutePath(); } setBinary(exe.binaryInfo()); -- cgit v1.3.1 From a5bd29f1117ec56632138640d508afad0232e5ed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 11:25:53 -0500 Subject: only default sort file trees if no setting was saved --- src/modinfodialogfiletree.cpp | 5 +++-- src/overwriteinfodialog.cpp | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index ca007c1c..79ed4cba 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -21,7 +21,6 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) m_fs->setReadOnly(false); ui->filetree->setModel(m_fs); ui->filetree->setColumnWidth(0, 300); - ui->filetree->sortByColumn(0, Qt::AscendingOrder); m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); @@ -63,7 +62,9 @@ void FileTreeTab::saveState(Settings& s) void FileTreeTab::restoreState(const Settings& s) { - s.geometry().restoreState(ui->filetree->header()); + if (!s.geometry().restoreState(ui->filetree->header())) { + ui->filetree->sortByColumn(0, Qt::AscendingOrder); + } } void FileTreeTab::update() diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index b2f2f1f9..ab726fb3 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -85,7 +85,6 @@ OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) ui->filesView->setModel(m_FileSystemModel); ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); ui->filesView->setColumnWidth(0, 250); - ui->filesView->sortByColumn(0, Qt::AscendingOrder); m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); m_RenameAction = new QAction(tr("&Rename"), ui->filesView); @@ -110,7 +109,10 @@ void OverwriteInfoDialog::showEvent(QShowEvent* e) const auto& s = Settings::instance(); s.geometry().restoreGeometry(this); - s.geometry().restoreState(ui->filesView->header()); + + if (!s.geometry().restoreState(ui->filesView->header())) { + ui->filesView->sortByColumn(0, Qt::AscendingOrder); + } QDialog::showEvent(e); } -- cgit v1.3.1 From 21cddd0ad3cef165c3860c2031b4cd00802c499d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 12:05:11 -0500 Subject: changed mod name of unmanaged files from "data" to "" in the data tab and for the root item in the archives tab log error when trying to open mod info for a managed file but the mod isn't found --- src/mainwindow.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 06e22e4e..e134d64a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -190,6 +190,11 @@ const QSize SmallToolbarSize(24, 24); const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); +QString UnmanagedModName() +{ + return QObject::tr(""); +} + bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); @@ -1661,9 +1666,13 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director QString fileName = ToQString(current->getName()); QStringList columns(fileName); FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - QString source("data"); - unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); - if (modIndex != UINT_MAX) { + + QString source; + const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); + + if (modIndex == UINT_MAX) { + source = UnmanagedModName(); + } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); source = modInfo->name(); } @@ -2046,12 +2055,17 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString int originID = iter->second->data(1, Qt::UserRole).toInt(); FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - QString modName("data"); - unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); - if (modIndex != UINT_MAX) { + + QString modName; + const unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); + + if (modIndex == UINT_MAX) { + modName = UnmanagedModName(); + } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); modName = modInfo->name(); } + QList items = ui->bsaList->findItems(modName, Qt::MatchFixedString); QTreeWidgetItem * subItem = nullptr; if (items.length() > 0) { @@ -5435,11 +5449,17 @@ void MainWindow::openDataModInfo_clicked() } const auto originID = m_ContextItem->data(1, Qt::UserRole + 1).toInt(); + if (originID == 0) { + // unmanaged + return; + } + const auto& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); const auto& name = QString::fromStdWString(origin.getName()); unsigned int index = ModInfo::getIndex(name); if (index == UINT_MAX) { + log::error("can't open mod info, mod '{}' not found", name); return; } -- cgit v1.3.1 From 70e66802a2d46a1ffdac7528b134c9d7741a8797 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 14:10:50 -0500 Subject: changed labels that have links to new LinkLabel, removed hardcoded colors changed the link colors on dark themes to something saner --- src/aboutdialog.ui | 11 +++++++++-- src/mainwindow.ui | 9 +++++++-- src/settingsdialog.ui | 7 ++++++- src/stylesheets/Night Eyes.qss | 4 ++++ src/stylesheets/Paper Dark by 6788.qss | 4 ++++ src/stylesheets/Transparent-Style-101-Green.qss | 5 +++++ src/stylesheets/Transparent-Style-BOS.qss | 4 ++++ src/stylesheets/Transparent-Style-Skyrim.qss | 4 ++++ src/stylesheets/dark.qss | 6 +++++- src/stylesheets/dracula.qss | 4 ++++ src/stylesheets/skyrim.qss | 3 +++ src/stylesheets/vs15 Dark-Green.qss | 3 +++ src/stylesheets/vs15 Dark-Orange.qss | 3 +++ src/stylesheets/vs15 Dark-Purple.qss | 3 +++ src/stylesheets/vs15 Dark-Red.qss | 3 +++ src/stylesheets/vs15 Dark-Yellow.qss | 3 +++ src/stylesheets/vs15 Dark.qss | 3 +++ 17 files changed, 73 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 415ce0a7..424a80f9 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -158,9 +158,9 @@ - + - <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> + <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer">GitHub</a>.</p></body></html> @@ -536,6 +536,13 @@ + + + LinkLabel + QLabel +
    linklabel.h
    +
    +
    diff --git a/src/mainwindow.ui b/src/mainwindow.ui index b8aeeeb5..723b42fe 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -941,12 +941,12 @@ p, li { white-space: pre-wrap; } - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> + <html><head/><body><p>Currently detected archives. (<a href="#">What is an archive?</a>)</p></body></html> true @@ -1773,6 +1773,11 @@ p, li { white-space: pre-wrap; } QStatusBar
    statusbar.h
    + + LinkLabel + QLabel +
    linklabel.h
    +
    diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index e63ca692..c5a1d2ef 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -1410,7 +1410,7 @@ programs you are intentionally running.
    - + Hint: right click link and copy link location @@ -1535,6 +1535,11 @@ programs you are intentionally running. QTableWidget
    colortable.h
    + + LinkLabel + QLabel +
    linklabel.h
    +
    tabWidget diff --git a/src/stylesheets/Night Eyes.qss b/src/stylesheets/Night Eyes.qss index 0430a8c8..14f7434e 100644 --- a/src/stylesheets/Night Eyes.qss +++ b/src/stylesheets/Night Eyes.qss @@ -49,6 +49,10 @@ QAbstractScrollArea::corner margin: 0px -2px -2px 0px; } +LinkLabel +{ + qproperty-linkColor: #3399FF; +} /* Toolbar -------------------------------------------------------------------- */ diff --git a/src/stylesheets/Paper Dark by 6788.qss b/src/stylesheets/Paper Dark by 6788.qss index 77086c15..6043ae6b 100644 --- a/src/stylesheets/Paper Dark by 6788.qss +++ b/src/stylesheets/Paper Dark by 6788.qss @@ -66,6 +66,10 @@ QSplitter { width: 8px; } +LinkLabel { + qproperty-linkColor: #3399FF; +} + /* Toolbar */ QToolBar { diff --git a/src/stylesheets/Transparent-Style-101-Green.qss b/src/stylesheets/Transparent-Style-101-Green.qss index a4ed2623..3bbdb5db 100644 --- a/src/stylesheets/Transparent-Style-101-Green.qss +++ b/src/stylesheets/Transparent-Style-101-Green.qss @@ -485,6 +485,11 @@ QStatusBar::item {border: None;} font-family: Source Sans Pro; font-size: 14px; } + +LinkLabel { + qproperty-linkColor: #3399FF; +} + QAbstractItemView { color: #cccccc; font-family: Source Sans Pro; diff --git a/src/stylesheets/Transparent-Style-BOS.qss b/src/stylesheets/Transparent-Style-BOS.qss index efad0859..33eed327 100644 --- a/src/stylesheets/Transparent-Style-BOS.qss +++ b/src/stylesheets/Transparent-Style-BOS.qss @@ -743,3 +743,7 @@ QStatusBar::item {border: None;} border-bottom:1px solid #9A9A00; */ } + +LinkLabel { + qproperty-linkColor: #3399FF; +} diff --git a/src/stylesheets/Transparent-Style-Skyrim.qss b/src/stylesheets/Transparent-Style-Skyrim.qss index 89e36c74..308580a5 100644 --- a/src/stylesheets/Transparent-Style-Skyrim.qss +++ b/src/stylesheets/Transparent-Style-Skyrim.qss @@ -743,3 +743,7 @@ QStatusBar::item {border: None;} border-bottom:1px solid #9A9A00; */ } + +LinkLabel { + qproperty-linkColor: #3399FF; +} diff --git a/src/stylesheets/dark.qss b/src/stylesheets/dark.qss index 22cd598c..9d11109d 100644 --- a/src/stylesheets/dark.qss +++ b/src/stylesheets/dark.qss @@ -380,4 +380,8 @@ DownloadListWidget[downloadView=standard]::item { DownloadListWidget[downloadView=compact]::item { padding: 4px; -} \ No newline at end of file +} + +LinkLabel { + qproperty-linkColor: #3399FF; +} diff --git a/src/stylesheets/dracula.qss b/src/stylesheets/dracula.qss index 2a7fbf9e..537ff083 100644 --- a/src/stylesheets/dracula.qss +++ b/src/stylesheets/dracula.qss @@ -30,6 +30,10 @@ QCheckBox, QGroupBox { selection-color: #bbbbbb; } +LinkLabel { + qproperty-linkColor: #3399FF; +} + /* * GroupBox and CheckBox */ diff --git a/src/stylesheets/skyrim.qss b/src/stylesheets/skyrim.qss index 2da5154d..a516f5ff 100644 --- a/src/stylesheets/skyrim.qss +++ b/src/stylesheets/skyrim.qss @@ -113,6 +113,9 @@ QListView::item { QLabel { background-color: transparent; } +LinkLabel { + qproperty-linkColor: #3399FF; } + /* Font size */ QLabel, QTextEdit, diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 88e7651f..6d95c6cc 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -94,6 +94,9 @@ QTreeView::branch:selected { QLabel { background-color: transparent; } +LinkLabel { + qproperty-linkColor: #3399FF; } + /* Left Pane & File Trees #QTreeView, #QListView*/ QTreeView::branch:closed:has-children { image: url(./vs15/branch-closed.png); } diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index 488da3c4..2dd27df4 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -95,6 +95,9 @@ QTreeView::branch:selected { QLabel { background-color: transparent; } +LinkLabel { + qproperty-linkColor: #3399FF; } + /* Left Pane & File Trees #QTreeView, #QListView*/ QTreeView::branch:closed:has-children { image: url(./vs15/branch-closed.png); } diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index 24c8705a..116aaa7d 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -95,6 +95,9 @@ QTreeView::branch:selected { QLabel { background-color: transparent; } +LinkLabel { + qproperty-linkColor: #3399FF; } + /* Left Pane & File Trees #QTreeView, #QListView*/ QTreeView::branch:closed:has-children { image: url(./vs15/branch-closed.png); } diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 0c0e21a8..60f565a1 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -95,6 +95,9 @@ QTreeView::branch:selected { QLabel { background-color: transparent; } +LinkLabel { + qproperty-linkColor: #3399FF; } + /* Left Pane & File Trees #QTreeView, #QListView*/ QTreeView::branch:closed:has-children { image: url(./vs15/branch-closed.png); } diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index 2cf1cb2e..bfaa4c94 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -95,6 +95,9 @@ QTreeView::branch:selected { QLabel { background-color: transparent; } +LinkLabel { + qproperty-linkColor: #3399FF; } + /* Left Pane & File Trees #QTreeView, #QListView*/ QTreeView::branch:closed:has-children { image: url(./vs15/branch-closed.png); } diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index a5781d72..1d27be17 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -94,6 +94,9 @@ QTreeView::branch:selected { QLabel { background-color: transparent; } +LinkLabel { + qproperty-linkColor: #3399FF; } + /* Left Pane & File Trees #QTreeView, #QListView*/ QTreeView::branch:closed:has-children { image: url(./vs15/branch-closed.png); } -- cgit v1.3.1 From 376f835edd3ebd778eed4b1250808188904be93c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 14:23:11 -0500 Subject: added transifex link in the settings --- src/settingsdialog.ui | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index c5a1d2ef..ad7e2ab3 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -75,14 +75,14 @@
    - + Style - + Visual theme of the user interface. @@ -92,6 +92,16 @@ + + + + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> + + + true + + +
    -- cgit v1.3.1 From 7f2d0e672205b85425cff488aa41ba0117abd191 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 15:15:27 -0500 Subject: fix crash when changing categories on mods when the "no categories" filter is selected --- src/modlist.cpp | 27 +++++++++++++++++++++++++++ src/modlist.h | 1 + 2 files changed, 28 insertions(+) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index c5bc37e9..b7a9b0a1 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -61,6 +61,7 @@ ModList::ModList(PluginContainer *pluginContainer, QObject *parent) , m_Profile(nullptr) , m_NexusInterface(nullptr) , m_Modified(false) + , m_InNotifyChange(false) , m_FontMetrics(QFont()) , m_DropOnItems(false) , m_PluginContainer(pluginContainer) @@ -1195,6 +1196,32 @@ bool ModList::removeRows(int row, int count, const QModelIndex &parent) void ModList::notifyChange(int rowStart, int rowEnd) { + // this function can emit dataChanged(), which can eventually recurse back + // here; for example: + // + // - a filter is active in the mod list, such as "no categories" + // - mods are selected and a category is set on them + // - these mods get updated here and disappear from the list because they're + // not in "no categories" anymore + // - dataChanged() is emitted + // - it's picked up in MainWindow::modlistSelectionsChanged() because the + // selected mods are gone + // - it calls setOverwriteMarkers(), which calls notifyChange() again and + // ends up here + // - dataChanged() is emitted again + // + // at this point, MO crashes because dataChanged() is not reentrant: it's in + // the middle of modifying internal data and crashes when trying to change an + // internal vector + // + // long story short, this prevents reentrancy + if (m_InNotifyChange) { + return; + } + + m_InNotifyChange = true; + Guard g([&]{ m_InNotifyChange = false; }); + if (rowStart < 0) { m_Overwrite.clear(); m_Overwritten.clear(); diff --git a/src/modlist.h b/src/modlist.h index 5ce32f6e..631401c0 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -341,6 +341,7 @@ private: std::set m_RequestIDs; mutable bool m_Modified; + bool m_InNotifyChange; QFontMetrics m_FontMetrics; -- cgit v1.3.1 From 05759c4abcae0e54c7c6fb3aac43b55ed490b6f4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 16:03:42 -0500 Subject: added checks on startup for directories in program files --- src/main.cpp | 3 ++ src/sanitychecks.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 3cccf365..1f6c57d1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,6 +94,7 @@ using namespace MOShared; void sanityChecks(const env::Environment& env); int checkIncompatibleModule(const env::Module& m); +int checkPathsForSanity(MOBase::IPluginGame& game, const Settings& s); bool createAndMakeWritable(const std::wstring &subPath) { QString const dataPath = qApp->property("dataPath").toString(); @@ -593,6 +594,8 @@ int runApplication(MOApplication &application, SingleInstance &instance, return 1; } + checkPathsForSanity(*game, settings); + if (splashPath.startsWith(':')) { // currently using MO splash, see if the plugin contains one QString pluginSplash diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index ef57a503..a767e8f9 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -1,6 +1,9 @@ #include "env.h" #include "envmodule.h" +#include "settings.h" +#include #include +#include using namespace MOBase; @@ -252,6 +255,83 @@ int checkIncompatibilities(const env::Environment& e) return n; } +std::vector> getSystemDirectories() +{ + // folder ids and display names for logging + const std::vector> systemFolderIDs = { + {FOLDERID_ProgramFiles, "Program Files"}, + {FOLDERID_ProgramFilesX86, "Program Files"} + }; + + std::vector> systemDirs; + + for (auto&& p : systemFolderIDs) { + try + { + const auto dir = MOBase::getKnownFolder(p.first); + + auto path = QDir::toNativeSeparators(dir.absolutePath()).toLower(); + if (!path.endsWith("\\")) { + path += "\\"; + } + + systemDirs.push_back({path, p.second}); + } + catch(std::exception&) + { + // ignore + } + } + + return systemDirs; +} + +int checkProtected(const QDir& d, const QString& what) +{ + static const auto systemDirs = getSystemDirectories(); + + const auto path = QDir::toNativeSeparators(d.absolutePath()).toLower(); + + log::debug(" . {}: {}", what, path); + + for (auto&& sd : systemDirs) { + if (path.startsWith(sd.first)) { + log::warn( + "{} is in {}; this may cause issues because it's a protected " + "system folder", + what, sd.second); + + log::debug("path '{}' starts with '{}'", path, sd.first); + + return 1; + } + } + + return 0; +} + +int checkPathsForSanity(IPluginGame& game, const Settings& s) +{ + log::debug("checking paths"); + + int n = 0; + + n += checkProtected(game.gameDirectory(), "the game"); + n += checkProtected(QApplication::applicationDirPath(), "Mod Organizer"); + + if (checkProtected(s.paths().base(), "the instance base directory")) { + ++n; + } else { + n += checkProtected(s.paths().downloads(), "the downloads directory"); + n += checkProtected(s.paths().mods(), "the mods directory"); + n += checkProtected(s.paths().cache(), "the cache directory"); + n += checkProtected(s.paths().profiles(), "the profiles directory"); + n += checkProtected(s.paths().overwrite(), "the overwrite directory"); + } + + return n; +} + void sanityChecks(const env::Environment& e) { log::debug("running sanity checks..."); -- cgit v1.3.1 From d47fc5c973b5b17289c4915e911c4412956361ea Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 16:37:58 -0500 Subject: allow relative paths for binaries in the executables settings added SSAudioOSD.dll to checks --- src/editexecutablesdialog.cpp | 2 +- src/executableslist.cpp | 6 +++--- src/sanitychecks.cpp | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 32b31357..1c804a7c 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -327,7 +327,7 @@ void EditExecutablesDialog::clearEdits() void EditExecutablesDialog::setEdits(const Executable& e) { ui->title->setText(e.title()); - ui->binary->setText(QDir::toNativeSeparators(e.binaryInfo().absoluteFilePath())); + ui->binary->setText(QDir::toNativeSeparators(e.binaryInfo().filePath())); ui->workingDirectory->setText(QDir::toNativeSeparators(e.workingDirectory())); ui->arguments->setText(e.arguments()); ui->overwriteSteamAppID->setChecked(!e.steamAppID().isEmpty()); diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 1be34b53..75f29e8f 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -120,7 +120,7 @@ void ExecutablesList::store(Settings& s) map["toolbar"] = item.isShownOnToolbar(); map["ownicon"] = item.usesOwnIcon(); map["hide"] = item.hide(); - map["binary"] = item.binaryInfo().absoluteFilePath(); + map["binary"] = item.binaryInfo().filePath(); map["arguments"] = item.arguments(); map["workingDirectory"] = item.workingDirectory(); map["steamAppID"] = item.steamAppID(); @@ -358,7 +358,7 @@ void ExecutablesList::dump() const " steam ID: {}\n" " directory: {}\n" " flags: {} ({})", - e.title(), e.binaryInfo().absoluteFilePath(), e.arguments(), + e.title(), e.binaryInfo().filePath(), e.arguments(), e.steamAppID(), e.workingDirectory(), flags.join("|"), e.flags()); } } @@ -374,7 +374,7 @@ Executable::Executable(const MOBase::ExecutableInfo& info, Flags flags) : m_binaryInfo(info.binary()), m_arguments(info.arguments().join(" ")), m_steamAppID(info.steamAppID()), - m_workingDirectory(info.workingDirectory().absolutePath()), + m_workingDirectory(info.workingDirectory().path()), m_flags(flags) { } diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index a767e8f9..330735ed 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -222,7 +222,8 @@ int checkIncompatibleModule(const env::Module& m) static const std::map names = { {"NahimicOSD.dll", "Nahimic"}, - {"RTSSHooks64.dll", "RivaTuner Statistics Server"} + {"RTSSHooks64.dll", "RivaTuner Statistics Server"}, + {"SSAudioOSD.dll", "SteelSeries Audio"} }; const QFileInfo file(m.path()); -- cgit v1.3.1 From d5e38fca6b3a8c7bf90c5a3d8ec779752a22c61d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:05:20 -0500 Subject: added not filter, not functional yet fixed no mods being displayed for OR with no conditions --- src/mainwindow.cpp | 5 +++++ src/mainwindow.h | 1 + src/mainwindow.ui | 7 +++++++ src/modlistsortproxy.cpp | 17 ++++++++++++----- src/modlistsortproxy.h | 4 ++-- 5 files changed, 27 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e134d64a..21606fa9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6552,6 +6552,11 @@ void MainWindow::on_categoriesOrBtn_toggled(bool checked) } } +void MainWindow::on_categoriesNotBtn_toggled(bool checked) +{ + m_ModListSortProxy->setFilterNot(checked); +} + void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { QToolTip::showText(QCursor::pos(), diff --git a/src/mainwindow.h b/src/mainwindow.h index 0c96c15d..cbf45635 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -659,6 +659,7 @@ private slots: // ui slots void on_saveModsButton_clicked(); void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); + void on_categoriesNotBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); void storeSettings(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 723b42fe..cd9cbc4b 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -152,6 +152,13 @@ + + + + Not + + + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index a9ff6463..805e77f4 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -36,10 +36,9 @@ using namespace MOBase; ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) : QSortFilterProxyModel(parent) , m_Profile(profile) - , m_CategoryFilter() - , m_CurrentFilter() , m_FilterActive(false) , m_FilterMode(FILTER_AND) + , m_FilterNot(false) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter // but I don't know why. This should be necessary @@ -312,8 +311,8 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons if (!info->hasFlag(ModInfo::FLAG_INVALID)) return false; } break; case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { - if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && - !info->hasFlag(ModInfo::FLAG_BACKUP) && + if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && + !info->hasFlag(ModInfo::FLAG_BACKUP) && !info->hasFlag(ModInfo::FLAG_SEPARATOR) && !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return false; } break; @@ -381,7 +380,7 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const if (info->hasContent(static_cast(content))) return true; } - return false; + return m_CategoryFilter.empty() && m_ContentFilter.empty(); } bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const @@ -487,6 +486,14 @@ void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) } } +void ModListSortProxy::setFilterNot(bool b) +{ + if (b != m_FilterNot) { + m_FilterNot = b; + this->invalidate(); + } +} + bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const { if (m_Profile == nullptr) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 5fe8d9d6..2e3e5709 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -85,6 +85,7 @@ public: bool isFilterActive() const { return m_FilterActive; } void setFilterMode(FilterMode mode); + void setFilterNot(bool b); /** * @brief tests if the specified index has child nodes @@ -129,9 +130,7 @@ private slots: void postDataChanged(); private: - Profile *m_Profile; - std::vector m_CategoryFilter; std::vector m_ContentFilter; std::bitset m_EnabledColumns; @@ -139,6 +138,7 @@ private: bool m_FilterActive; FilterMode m_FilterMode; + bool m_FilterNot; std::vector m_PreChangeFilters; -- cgit v1.3.1 From ecaf75c4531a79b1bdfe65eb60f257ac04422956 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:14:44 -0500 Subject: refactored matching into one function instead of repeating them for OR and AND --- src/modlistsortproxy.cpp | 154 +++++++++++++++++++++-------------------------- src/modlistsortproxy.h | 2 + 2 files changed, 72 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 805e77f4..0984d415 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -278,52 +278,15 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - switch (*iter) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { - if (!enabled && !info->alwaysEnabled() && !info->hasFlag(ModInfo::FLAG_SEPARATOR)) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { - if (enabled || info->alwaysEnabled()) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { - if (!info->updateAvailable() && !info->downgradeAvailable()) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { - if (info->getCategories().size() > 0) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { - if (!hasConflictFlag(info->getFlags())) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { - ModInfo::EEndorsedState state = info->endorsedState(); - if (state != ModInfo::ENDORSED_FALSE) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_BACKUP: { - if (!info->hasFlag(ModInfo::FLAG_BACKUP)) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { - if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { - if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: { - if (!info->hasFlag(ModInfo::FLAG_INVALID)) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { - if (!(info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && - !info->hasFlag(ModInfo::FLAG_BACKUP) && - !info->hasFlag(ModInfo::FLAG_SEPARATOR) && - !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return false; - } break; - default: { - if (!info->categorySet(*iter)) return false; - } break; + if (!categoryMatchesMod(info, enabled, *iter)) { + return false; } } foreach (int content, m_ContentFilter) { - if (!info->hasContent(static_cast(content))) return false; + if (!contentMatchesMod(info, enabled, content)) { + return false; + } } return true; @@ -332,57 +295,80 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - switch (*iter) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { - if (enabled || info->alwaysEnabled()) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { - if (!enabled && !info->alwaysEnabled()) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { - if (info->updateAvailable() || info->downgradeAvailable()) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { - if (info->getCategories().size() == 0) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { - if (hasConflictFlag(info->getFlags())) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { - ModInfo::EEndorsedState state = info->endorsedState(); - if ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_BACKUP: { - if (info->hasFlag(ModInfo::FLAG_BACKUP)) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { - if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { - if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: { - if (info->hasFlag(ModInfo::FLAG_INVALID)) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { - if ((info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && - !info->hasFlag(ModInfo::FLAG_BACKUP) && - !info->hasFlag(ModInfo::FLAG_SEPARATOR) && - !info->hasFlag(ModInfo::FLAG_OVERWRITE))) return true; - } break; - default: { - if (info->categorySet(*iter)) return true; - } break; + if (categoryMatchesMod(info, enabled, *iter)) { + return true; } } foreach (int content, m_ContentFilter) { - if (info->hasContent(static_cast(content))) return true; + if (contentMatchesMod(info, enabled, content)) { + return true; + } } return m_CategoryFilter.empty() && m_ContentFilter.empty(); } +bool ModListSortProxy::categoryMatchesMod( + ModInfo::Ptr info, bool enabled, int category) const +{ + switch (category) + { + case CategoryFactory::CATEGORY_SPECIAL_CHECKED: + return (enabled || info->alwaysEnabled()); + + case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: + return (!enabled && !info->alwaysEnabled()); + + case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: + return (info->updateAvailable() || info->downgradeAvailable()); + + case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: + return (info->getCategories().size() == 0); + + case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: + return (hasConflictFlag(info->getFlags())); + + case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: + { + ModInfo::EEndorsedState state = info->endorsedState(); + return ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)); + } + + case CategoryFactory::CATEGORY_SPECIAL_BACKUP: + return (info->hasFlag(ModInfo::FLAG_BACKUP)); + + case CategoryFactory::CATEGORY_SPECIAL_MANAGED: + return (!info->hasFlag(ModInfo::FLAG_FOREIGN)); + + case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: + return (info->hasFlag(ModInfo::FLAG_FOREIGN)); + + case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: + return (info->hasFlag(ModInfo::FLAG_INVALID)); + + case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: + { + return ( + info->getNexusID() == -1 && + !info->hasFlag(ModInfo::FLAG_FOREIGN) && + !info->hasFlag(ModInfo::FLAG_BACKUP) && + !info->hasFlag(ModInfo::FLAG_SEPARATOR) && + !info->hasFlag(ModInfo::FLAG_OVERWRITE)); + } + + default: + { + return (info->categorySet(category)); + } + } +} + +bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const +{ + return info->hasContent(static_cast(content)); +} + bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { if (!m_CurrentFilter.isEmpty()) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 2e3e5709..17888ae6 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -142,6 +142,8 @@ private: std::vector m_PreChangeFilters; + bool categoryMatchesMod(ModInfo::Ptr info, bool enabled, int category) const; + bool contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const; }; #endif // MODLISTSORTPROXY_H -- cgit v1.3.1 From 17452071c9b72a48498e7578d65b9b52729f914f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:30:30 -0500 Subject: added separators filter changed notendorsed filter to include anything else than true --- src/mainwindow.cpp | 5 +++++ src/mainwindow.h | 1 + src/mainwindow.ui | 7 +++++++ src/modlistsortproxy.cpp | 20 ++++++++++++++++++-- src/modlistsortproxy.h | 2 ++ 5 files changed, 33 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 21606fa9..d5636ab9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6557,6 +6557,11 @@ void MainWindow::on_categoriesNotBtn_toggled(bool checked) m_ModListSortProxy->setFilterNot(checked); } +void MainWindow::on_categoriesSeparators_toggled(bool checked) +{ + m_ModListSortProxy->setFilterSeparators(checked); +} + void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { QToolTip::showText(QCursor::pos(), diff --git a/src/mainwindow.h b/src/mainwindow.h index cbf45635..c99c724b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -660,6 +660,7 @@ private slots: // ui slots void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_categoriesNotBtn_toggled(bool checked); + void on_categoriesSeparators_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); void storeSettings(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index cd9cbc4b..9648a586 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -159,6 +159,13 @@ + + + + Separators + + + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 0984d415..ddae675c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -39,6 +39,7 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) , m_FilterActive(false) , m_FilterMode(FILTER_AND) , m_FilterNot(false) + , m_FilterSeparators(false) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter // but I don't know why. This should be necessary @@ -278,6 +279,10 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + return false; + } + if (!categoryMatchesMod(info, enabled, *iter)) { return false; } @@ -295,6 +300,10 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + return false; + } + if (categoryMatchesMod(info, enabled, *iter)) { return true; } @@ -332,7 +341,7 @@ bool ModListSortProxy::categoryMatchesMod( case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { ModInfo::EEndorsedState state = info->endorsedState(); - return ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)); + return (state != ModInfo::ENDORSED_TRUE); } case CategoryFactory::CATEGORY_SPECIAL_BACKUP: @@ -353,7 +362,6 @@ bool ModListSortProxy::categoryMatchesMod( info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && !info->hasFlag(ModInfo::FLAG_BACKUP) && - !info->hasFlag(ModInfo::FLAG_SEPARATOR) && !info->hasFlag(ModInfo::FLAG_OVERWRITE)); } @@ -480,6 +488,14 @@ void ModListSortProxy::setFilterNot(bool b) } } +void ModListSortProxy::setFilterSeparators(bool b) +{ + if (b != m_FilterSeparators) { + m_FilterSeparators = b; + this->invalidate(); + } +} + bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const { if (m_Profile == nullptr) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 17888ae6..2ebfbcf0 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -86,6 +86,7 @@ public: void setFilterMode(FilterMode mode); void setFilterNot(bool b); + void setFilterSeparators(bool b); /** * @brief tests if the specified index has child nodes @@ -139,6 +140,7 @@ private: bool m_FilterActive; FilterMode m_FilterMode; bool m_FilterNot; + bool m_FilterSeparators; std::vector m_PreChangeFilters; -- cgit v1.3.1 From 9134ae6111f0d357428b8a15abc26a727fe465a4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:41:04 -0500 Subject: implemented not filter --- src/modlistsortproxy.cpp | 99 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index ddae675c..d2a5e258 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -278,11 +278,11 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { - return false; - } + if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + return false; + } + for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { if (!categoryMatchesMod(info, enabled, *iter)) { return false; } @@ -299,82 +299,137 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { - return false; - } + if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + return false; + } + for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { if (categoryMatchesMod(info, enabled, *iter)) { return true; } } + if (!m_CategoryFilter.empty()) { + // nothing matched + return false; + } + foreach (int content, m_ContentFilter) { if (contentMatchesMod(info, enabled, content)) { return true; } } - return m_CategoryFilter.empty() && m_ContentFilter.empty(); + if (!m_ContentFilter.empty()) { + // nothing matched + return false; + } + + return true; } bool ModListSortProxy::categoryMatchesMod( ModInfo::Ptr info, bool enabled, int category) const { + bool b = false; + switch (category) { case CategoryFactory::CATEGORY_SPECIAL_CHECKED: - return (enabled || info->alwaysEnabled()); + { + b = (enabled || info->alwaysEnabled()); + break; + } case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: - return (!enabled && !info->alwaysEnabled()); + { + b = (!enabled && !info->alwaysEnabled()); + break; + } case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: - return (info->updateAvailable() || info->downgradeAvailable()); + { + b = (info->updateAvailable() || info->downgradeAvailable()); + break; + } case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: - return (info->getCategories().size() == 0); + { + b = (info->getCategories().size() == 0); + break; + } case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: - return (hasConflictFlag(info->getFlags())); + { + b = (hasConflictFlag(info->getFlags())); + break; + } case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { ModInfo::EEndorsedState state = info->endorsedState(); - return (state != ModInfo::ENDORSED_TRUE); + b = (state != ModInfo::ENDORSED_TRUE); + break; } case CategoryFactory::CATEGORY_SPECIAL_BACKUP: - return (info->hasFlag(ModInfo::FLAG_BACKUP)); + { + b = (info->hasFlag(ModInfo::FLAG_BACKUP)); + break; + } case CategoryFactory::CATEGORY_SPECIAL_MANAGED: - return (!info->hasFlag(ModInfo::FLAG_FOREIGN)); + { + b = (!info->hasFlag(ModInfo::FLAG_FOREIGN)); + break; + } case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: - return (info->hasFlag(ModInfo::FLAG_FOREIGN)); + { + b = (info->hasFlag(ModInfo::FLAG_FOREIGN)); + break; + } case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: - return (info->hasFlag(ModInfo::FLAG_INVALID)); + { + b = (info->hasFlag(ModInfo::FLAG_INVALID)); + break; + } case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: { - return ( + b = ( info->getNexusID() == -1 && !info->hasFlag(ModInfo::FLAG_FOREIGN) && !info->hasFlag(ModInfo::FLAG_BACKUP) && !info->hasFlag(ModInfo::FLAG_OVERWRITE)); + + break; } default: { - return (info->categorySet(category)); + b = (info->categorySet(category)); + break; } } + + if (m_FilterNot) { + b = !b; + } + + return b; } bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const { - return info->hasContent(static_cast(content)); + bool b = info->hasContent(static_cast(content)); + + if (m_FilterNot) { + b = !b; + } + + return b; } bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const -- cgit v1.3.1 From 06bb9870dd267215fac1d585c56a1ae0712f140c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:45:52 -0500 Subject: fixed tooltips --- src/mainwindow.ui | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 9648a586..47da0c38 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -132,7 +132,7 @@ - If checked, only mods that match all selected categories are displayed. + Display mods that match all selected categories. And @@ -145,7 +145,7 @@ - If checked, all mods that match at least one of the selected categories are displayed. + Display mods that match at least one of the selected categories Or @@ -154,6 +154,9 @@ + + Invert each selected category + Not @@ -161,6 +164,9 @@ + + Include separators + Separators -- cgit v1.3.1 From 8ea346183fa432e4a16e604ae2fa03366c01c6af Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 17:53:26 -0500 Subject: renamed "Categories" frame to "Filters" --- src/mainwindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 47da0c38..7e11c70e 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -47,7 +47,7 @@ - Categories + Filters -- cgit v1.3.1 From 4bbdbb000fd5051fe80b5dca21dda60910284333 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 27 Nov 2019 19:14:37 -0500 Subject: split filter list --- src/CMakeLists.txt | 3 + src/filterlist.cpp | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/filterlist.h | 42 ++++++++++++ src/mainwindow.cpp | 177 ++++--------------------------------------------- src/mainwindow.h | 12 +--- 5 files changed, 249 insertions(+), 174 deletions(-) create mode 100644 src/filterlist.cpp create mode 100644 src/filterlist.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e3a42409..a46908ef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -144,6 +144,7 @@ SET(organizer_SRCS uilocker.cpp loot.cpp lootdialog.cpp + filterlist.cpp shared/windows_error.cpp shared/error_report.cpp @@ -268,6 +269,7 @@ SET(organizer_HDRS loot.h lootdialog.h json.h + filterlist.h shared/windows_error.h shared/error_report.h @@ -320,6 +322,7 @@ SET(organizer_RCS source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)") set(application + filterlist iuserinterface main mainwindow diff --git a/src/filterlist.cpp b/src/filterlist.cpp new file mode 100644 index 00000000..8ef4b62d --- /dev/null +++ b/src/filterlist.cpp @@ -0,0 +1,189 @@ +#include "filterlist.h" +#include "ui_mainwindow.h" +#include "categories.h" +#include "categoriesdialog.h" +#include + +using namespace MOBase; + +FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) + : ui(ui), m_factory(factory) +{ + QObject::connect( + ui->categoriesList, &QTreeWidget::customContextMenuRequested, + [&](auto&& pos){ onContextMenu(pos); }); + + QObject::connect( + ui->categoriesList, &QTreeWidget::itemSelectionChanged, + [&]{ onSelection(); }); +} + +QTreeWidgetItem* FilterList::addFilterItem( + QTreeWidgetItem *root, const QString &name, int categoryID, + ModListSortProxy::FilterType type) +{ + QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); + item->setData(0, Qt::ToolTipRole, name); + item->setData(0, Qt::UserRole, categoryID); + item->setData(0, Qt::UserRole + 1, type); + if (root != nullptr) { + root->addChild(item); + } else { + ui->categoriesList->addTopLevelItem(item); + } + return item; +} + +void FilterList::addContentFilters() +{ + for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { + addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); + } +} + +void FilterList::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) +{ + for (unsigned int i = 1; + i < static_cast(m_factory.numCategories()); ++i) { + if ((m_factory.getParentID(i) == targetID)) { + int categoryID = m_factory.getCategoryID(i); + if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { + QTreeWidgetItem *item = + addFilterItem(root, m_factory.getCategoryName(i), + categoryID, ModListSortProxy::TYPE_CATEGORY); + if (m_factory.hasChildren(i)) { + addCategoryFilters(item, categoriesUsed, categoryID); + } + } + } + } +} + +void FilterList::refresh() +{ + QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + + QVariant currentIndexName = ui->modList->currentIndex().data(); + ui->modList->setCurrentIndex(QModelIndex()); + + QStringList selectedItems; + for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) { + selectedItems.append(item->text(0)); + } + + ui->categoriesList->clear(); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_BACKUP, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); + + addContentFilters(); + std::set categoriesUsed; + for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); + for (int categoryID : modInfo->getCategories()) { + int currentID = categoryID; + std::set cycleTest; + // also add parents so they show up in the tree + while (currentID != 0) { + categoriesUsed.insert(currentID); + if (!cycleTest.insert(currentID).second) { + log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); + break; + } + currentID = m_factory.getParentID(m_factory.getCategoryIndex(currentID)); + } + } + } + + addCategoryFilters(nullptr, categoriesUsed, 0); + + for (const QString &item : selectedItems) { + QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); + if (matches.size() > 0) { + matches.at(0)->setSelected(true); + } + } + ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + QModelIndexList matchList; + if (currentIndexName.isValid()) { + matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); + } + + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } +} + +void FilterList::setSelection(std::vector categories) +{ + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } + } +} + +void FilterList::clearSelection() +{ + ui->categoriesList->clearSelection(); +} + +void FilterList::onSelection() +{ + QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); + std::vector categories; + std::vector content; + for (const QModelIndex &index : indices) { + int filterType = index.data(Qt::UserRole + 1).toInt(); + if ((filterType == ModListSortProxy::TYPE_CATEGORY) + || (filterType == ModListSortProxy::TYPE_SPECIAL)) { + int categoryId = index.data(Qt::UserRole).toInt(); + if (categoryId != CategoryFactory::CATEGORY_NONE) { + categories.push_back(categoryId); + } + } else if (filterType == ModListSortProxy::TYPE_CONTENT) { + int contentId = index.data(Qt::UserRole).toInt(); + content.push_back(contentId); + } + } + + emit changed(categories, content); + + ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0); + + if (indices.count() == 0) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else if (indices.count() > 1) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else { + ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); + } + ui->modList->reset(); +} + +void FilterList::onContextMenu(const QPoint &pos) +{ + QMenu menu; + menu.addAction(tr("Edit Categories..."), [&]{ editCategories(); }); + menu.addAction(tr("Deselect filter"), [&]{ clearSelection(); }); + + menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos)); +} + +void FilterList::editCategories() +{ + CategoriesDialog dialog(qApp->activeWindow()); + + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} diff --git a/src/filterlist.h b/src/filterlist.h new file mode 100644 index 00000000..ca74021a --- /dev/null +++ b/src/filterlist.h @@ -0,0 +1,42 @@ +#ifndef MODORGANIZER_CATEGORIESLIST_INCLUDED +#define MODORGANIZER_CATEGORIESLIST_INCLUDED + +#include "modlistsortproxy.h" +#include + +namespace Ui { class MainWindow; }; +class CategoryFactory; + +class FilterList : public QObject +{ + Q_OBJECT; + +public: + FilterList(Ui::MainWindow* ui, CategoryFactory& factory); + + void setSelection(std::vector categories); + void clearSelection(); + void refresh(); + +signals: + void changed(std::vector categories, std::vector content); + +private: + Ui::MainWindow* ui; + CategoryFactory& m_factory; + + void onContextMenu(const QPoint &pos); + void onSelection(); + + void editCategories(); + + QTreeWidgetItem* addFilterItem( + QTreeWidgetItem *root, const QString &name, int categoryID, + ModListSortProxy::FilterType type); + + void addContentFilters(); + void addCategoryFilters( + QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); +}; + +#endif // MODORGANIZER_CATEGORIESLIST_INCLUDED diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d5636ab9..b63f9211 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -75,6 +75,7 @@ along with Mod Organizer. If not, see . #include "appconfig.h" #include "eventfilter.h" #include "statusbar.h" +#include "filterlist.h" #include #include #include @@ -260,6 +261,11 @@ MainWindow::MainWindow(Settings &settings languageChange(settings.interface().language()); m_CategoryFactory.loadCategories(); + m_Filters.reset(new FilterList(ui, m_CategoryFactory)); + connect(m_Filters.get(), &FilterList::changed, [&](auto&& cats, auto&& content) { + m_ModListSortProxy->setCategoryFilter(cats); + m_ModListSortProxy->setContentFilter(content); + }); ui->logList->setCore(m_OrganizerCore); @@ -1223,7 +1229,7 @@ void MainWindow::showEvent(QShowEvent *event) if (!m_WasVisible) { readSettings(); - refreshFilters(); + m_Filters->refresh(); // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which @@ -2646,108 +2652,6 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName } } -QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type) -{ - QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); - item->setData(0, Qt::ToolTipRole, name); - item->setData(0, Qt::UserRole, categoryID); - item->setData(0, Qt::UserRole + 1, type); - if (root != nullptr) { - root->addChild(item); - } else { - ui->categoriesList->addTopLevelItem(item); - } - return item; -} - -void MainWindow::addContentFilters() -{ - for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { - addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); - } -} - -void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) -{ - for (unsigned int i = 1; - i < static_cast(m_CategoryFactory.numCategories()); ++i) { - if ((m_CategoryFactory.getParentID(i) == targetID)) { - int categoryID = m_CategoryFactory.getCategoryID(i); - if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { - QTreeWidgetItem *item = - addFilterItem(root, m_CategoryFactory.getCategoryName(i), - categoryID, ModListSortProxy::TYPE_CATEGORY); - if (m_CategoryFactory.hasChildren(i)) { - addCategoryFilters(item, categoriesUsed, categoryID); - } - } - } - } -} - -void MainWindow::refreshFilters() -{ - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - QVariant currentIndexName = ui->modList->currentIndex().data(); - ui->modList->setCurrentIndex(QModelIndex()); - - QStringList selectedItems; - for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) { - selectedItems.append(item->text(0)); - } - - ui->categoriesList->clear(); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_BACKUP, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); - - addContentFilters(); - std::set categoriesUsed; - for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); - for (int categoryID : modInfo->getCategories()) { - int currentID = categoryID; - std::set cycleTest; - // also add parents so they show up in the tree - while (currentID != 0) { - categoriesUsed.insert(currentID); - if (!cycleTest.insert(currentID).second) { - log::warn("cycle in categories: {}", SetJoin(cycleTest, ", ")); - break; - } - currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); - } - } - } - - addCategoryFilters(nullptr, categoriesUsed, 0); - - for (const QString &item : selectedItems) { - QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); - if (matches.size() > 0) { - matches.at(0)->setSelected(true); - } - } - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); - QModelIndexList matchList; - if (currentIndexName.isValid()) { - matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); - } - - if (matchList.size() > 0) { - ui->modList->setCurrentIndex(matchList.at(0)); - } -} - void MainWindow::renameMod_clicked() { @@ -4121,7 +4025,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - refreshFilters(); + m_Filters->refresh(); } void MainWindow::replaceCategories_MenuHandler() { @@ -4165,7 +4069,7 @@ void MainWindow::replaceCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - refreshFilters(); + m_Filters->refresh(); } void MainWindow::saveArchiveList() @@ -4217,12 +4121,7 @@ void MainWindow::checkModsForUpdates() if (updatesAvailable || checkingModsForUpdate) { m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } + m_Filters->setSelection({CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE}); } } @@ -4848,40 +4747,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) } -void MainWindow::on_categoriesList_itemSelectionChanged() -{ - QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); - std::vector categories; - std::vector content; - for (const QModelIndex &index : indices) { - int filterType = index.data(Qt::UserRole + 1).toInt(); - if ((filterType == ModListSortProxy::TYPE_CATEGORY) - || (filterType == ModListSortProxy::TYPE_SPECIAL)) { - int categoryId = index.data(Qt::UserRole).toInt(); - if (categoryId != CategoryFactory::CATEGORY_NONE) { - categories.push_back(categoryId); - } - } else if (filterType == ModListSortProxy::TYPE_CONTENT) { - int contentId = index.data(Qt::UserRole).toInt(); - content.push_back(contentId); - } - } - - m_ModListSortProxy->setCategoryFilter(categories); - m_ModListSortProxy->setContentFilter(content); - ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0); - - if (indices.count() == 0) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else if (indices.count() > 1) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else { - ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); - } - ui->modList->reset(); -} - - void MainWindow::deleteSavegame_clicked() { SaveGameInfo const *info = m_OrganizerCore.managedGame()->feature(); @@ -5071,7 +4936,7 @@ void MainWindow::on_actionSettings_triggered() instManager->setDownloadDirectory(settings.paths().downloads()); fixCategories(); - refreshFilters(); + m_Filters->refresh(); if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); @@ -6213,27 +6078,9 @@ void MainWindow::on_displayCategoriesBtn_toggled(bool checked) setCategoryListVisible(checked); } -void MainWindow::editCategories() -{ - CategoriesDialog dialog(this); - - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - void MainWindow::deselectFilters() { - ui->categoriesList->clearSelection(); -} - -void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint &pos) -{ - QMenu menu; - menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); - menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); - - menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos)); + m_Filters->clearSelection(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index c99c724b..04ba7a35 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see . class Executable; class CategoryFactory; class OrganizerCore; +class FilterList; class PluginListSortProxy; namespace BSA { class Archive; } @@ -235,8 +236,6 @@ private: void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); - void refreshFilters(); - /** * Sets category selections from menu; for multiple mods, this will only apply * the changes made in the menu (which is the delta between the current menu selection and the reference mod) @@ -266,10 +265,6 @@ private: size_t checkForProblems(); - QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type); - void addContentFilters(); - void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); - void setCategoryListVisible(bool visible); void displaySaveGameInfo(QListWidgetItem *newItem); @@ -327,6 +322,8 @@ private: MOBase::TutorialControl m_Tutorial; + std::unique_ptr m_Filters; + int m_OldProfileIndex; std::vector m_ModNameList; // the mod-list to go with the directory structure @@ -515,7 +512,6 @@ private slots: void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); - void editCategories(); void deselectFilters(); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); @@ -630,7 +626,6 @@ private slots: // ui slots void on_clearFiltersButton_clicked(); void on_btnRefreshData_clicked(); void on_btnRefreshDownloads_clicked(); - void on_categoriesList_customContextMenuRequested(const QPoint &pos); void on_conflictsCheckBox_toggled(bool checked); void on_showArchiveDataCheckBox_toggled(bool checked); void on_dataTree_customContextMenuRequested(const QPoint &pos); @@ -647,7 +642,6 @@ private slots: // ui slots void on_espList_customContextMenuRequested(const QPoint &pos); void on_displayCategoriesBtn_toggled(bool checked); void on_groupCombo_currentIndexChanged(int index); - void on_categoriesList_itemSelectionChanged(); void on_linkButton_pressed(); void on_showHiddenBox_toggled(bool checked); void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); -- cgit v1.3.1 From 93318a1474031035da5e61ad199171cad5803c2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 29 Nov 2019 23:50:33 -0500 Subject: moved all remaining filter stuff to FilterList renamed some widgets --- src/categories.cpp | 34 ++++++++++++++ src/categories.h | 2 + src/filterlist.cpp | 135 +++++++++++++++++++++++++++++++---------------------- src/filterlist.h | 6 ++- src/mainwindow.cpp | 107 +++++++++++++++++++++++++++++------------- src/mainwindow.h | 7 ++- src/mainwindow.ui | 12 ++--- 7 files changed, 204 insertions(+), 99 deletions(-) (limited to 'src') diff --git a/src/categories.cpp b/src/categories.cpp index 12b18998..082b4fbc 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -320,6 +320,40 @@ QString CategoryFactory::getCategoryName(unsigned int index) const return m_Categories[index].m_Name; } +QString CategoryFactory::getSpecialCategoryName(int type) const +{ + switch (type) + { + case CATEGORY_SPECIAL_CHECKED: return QObject::tr(""); + case CATEGORY_SPECIAL_UNCHECKED: return QObject::tr(""); + case CATEGORY_SPECIAL_UPDATEAVAILABLE: return QObject::tr(""); + case CATEGORY_SPECIAL_NOCATEGORY: return QObject::tr(""); + case CATEGORY_SPECIAL_CONFLICT: return QObject::tr(""); + case CATEGORY_SPECIAL_NOTENDORSED: return QObject::tr(""); + case CATEGORY_SPECIAL_BACKUP: return QObject::tr(""); + case CATEGORY_SPECIAL_MANAGED: return QObject::tr(""); + case CATEGORY_SPECIAL_UNMANAGED: return QObject::tr(""); + case CATEGORY_SPECIAL_NOGAMEDATA: return QObject::tr(""); + case CATEGORY_SPECIAL_NONEXUSID: return QObject::tr(""); + default: return {}; + } +} + +QString CategoryFactory::getCategoryNameByID(int id) const +{ + auto itor = m_IDMap.find(id); + + if (itor == m_IDMap.end()) { + return getSpecialCategoryName(id); + } else { + const auto index = itor->second; + if (index >= m_Categories.size()) { + return {}; + } + + return m_Categories[index].m_Name; + } +} int CategoryFactory::getCategoryID(unsigned int index) const { diff --git a/src/categories.h b/src/categories.h index 67fee3e7..2041ce1f 100644 --- a/src/categories.h +++ b/src/categories.h @@ -144,6 +144,8 @@ public: * @return QString name of the category **/ QString getCategoryName(unsigned int index) const; + QString getSpecialCategoryName(int type) const; + QString getCategoryNameByID(int id) const; /** * @brief look up the id of a category by its index diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 8ef4b62d..5562736d 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -9,13 +9,33 @@ using namespace MOBase; FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) : ui(ui), m_factory(factory) { - QObject::connect( - ui->categoriesList, &QTreeWidget::customContextMenuRequested, + connect( + ui->filters, &QTreeWidget::customContextMenuRequested, [&](auto&& pos){ onContextMenu(pos); }); - QObject::connect( - ui->categoriesList, &QTreeWidget::itemSelectionChanged, + connect( + ui->filters, &QTreeWidget::itemSelectionChanged, [&]{ onSelection(); }); + + connect( + ui->filtersClear, &QPushButton::clicked, + [&]{ clearSelection(); }); + + connect( + ui->filtersAnd, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); + + connect( + ui->filtersOr, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); + + connect( + ui->filtersNot, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); + + connect( + ui->filtersSeparators, &QCheckBox::toggled, + [&]{ onCriteriaChanged(); }); } QTreeWidgetItem* FilterList::addFilterItem( @@ -29,7 +49,7 @@ QTreeWidgetItem* FilterList::addFilterItem( if (root != nullptr) { root->addChild(item); } else { - ui->categoriesList->addTopLevelItem(item); + ui->filters->addTopLevelItem(item); } return item; } @@ -37,7 +57,9 @@ QTreeWidgetItem* FilterList::addFilterItem( void FilterList::addContentFilters() { for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { - addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); + addFilterItem( + nullptr, tr("").arg(ModInfo::getContentTypeName(i)), + i, ModListSortProxy::TYPE_CONTENT); } } @@ -59,32 +81,37 @@ void FilterList::addCategoryFilters(QTreeWidgetItem *root, const std::set & } } -void FilterList::refresh() +void FilterList::addSpecialFilterItem(int type) { - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - QVariant currentIndexName = ui->modList->currentIndex().data(); - ui->modList->setCurrentIndex(QModelIndex()); + addFilterItem( + nullptr, m_factory.getSpecialCategoryName(type), + type, ModListSortProxy::TYPE_SPECIAL); +} +void FilterList::refresh() +{ QStringList selectedItems; - for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) { + for (QTreeWidgetItem *item : ui->filters->selectedItems()) { selectedItems.append(item->text(0)); } - ui->categoriesList->clear(); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_BACKUP, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NONEXUSID, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA, ModListSortProxy::TYPE_SPECIAL); + ui->filters->clear(); + + using F = CategoryFactory; + addSpecialFilterItem(F::CATEGORY_SPECIAL_CHECKED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_UNCHECKED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); + addSpecialFilterItem(F::CATEGORY_SPECIAL_BACKUP); + addSpecialFilterItem(F::CATEGORY_SPECIAL_MANAGED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_UNMANAGED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NOCATEGORY); + addSpecialFilterItem(F::CATEGORY_SPECIAL_CONFLICT); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NOTENDORSED); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NONEXUSID); + addSpecialFilterItem(F::CATEGORY_SPECIAL_NOGAMEDATA); addContentFilters(); + std::set categoriesUsed; for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); @@ -106,27 +133,20 @@ void FilterList::refresh() addCategoryFilters(nullptr, categoriesUsed, 0); for (const QString &item : selectedItems) { - QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); + QList matches = ui->filters->findItems( + item, Qt::MatchFixedString | Qt::MatchRecursive); + if (matches.size() > 0) { matches.at(0)->setSelected(true); } } - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); - QModelIndexList matchList; - if (currentIndexName.isValid()) { - matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); - } - - if (matchList.size() > 0) { - ui->modList->setCurrentIndex(matchList.at(0)); - } } void FilterList::setSelection(std::vector categories) { - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { + if (ui->filters->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); break; } } @@ -134,40 +154,32 @@ void FilterList::setSelection(std::vector categories) void FilterList::clearSelection() { - ui->categoriesList->clearSelection(); + ui->filters->clearSelection(); } void FilterList::onSelection() { - QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); + QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); std::vector categories; std::vector content; + for (const QModelIndex &index : indices) { - int filterType = index.data(Qt::UserRole + 1).toInt(); - if ((filterType == ModListSortProxy::TYPE_CATEGORY) - || (filterType == ModListSortProxy::TYPE_SPECIAL)) { - int categoryId = index.data(Qt::UserRole).toInt(); + const int filterType = index.data(Qt::UserRole + 1).toInt(); + + if ((filterType == ModListSortProxy::TYPE_CATEGORY) || (filterType == ModListSortProxy::TYPE_SPECIAL)) { + const int categoryId = index.data(Qt::UserRole).toInt(); if (categoryId != CategoryFactory::CATEGORY_NONE) { categories.push_back(categoryId); } } else if (filterType == ModListSortProxy::TYPE_CONTENT) { - int contentId = index.data(Qt::UserRole).toInt(); + const int contentId = index.data(Qt::UserRole).toInt(); content.push_back(contentId); } } - emit changed(categories, content); - - ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0); + ui->filtersClear->setEnabled(categories.size() > 0 || content.size() >0); - if (indices.count() == 0) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else if (indices.count() > 1) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else { - ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); - } - ui->modList->reset(); + emit filtersChanged(categories, content); } void FilterList::onContextMenu(const QPoint &pos) @@ -176,7 +188,7 @@ void FilterList::onContextMenu(const QPoint &pos) menu.addAction(tr("Edit Categories..."), [&]{ editCategories(); }); menu.addAction(tr("Deselect filter"), [&]{ clearSelection(); }); - menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos)); + menu.exec(ui->filters->viewport()->mapToGlobal(pos)); } void FilterList::editCategories() @@ -187,3 +199,14 @@ void FilterList::editCategories() dialog.commitChanges(); } } + +void FilterList::onCriteriaChanged() +{ + const auto mode = ui->filtersAnd->isChecked() ? + ModListSortProxy::FILTER_AND : ModListSortProxy::FILTER_OR; + + const bool inverse = ui->filtersNot->isChecked(); + const bool separators = ui->filtersSeparators->isChecked(); + + emit criteriaChanged(mode, inverse, separators); +} diff --git a/src/filterlist.h b/src/filterlist.h index ca74021a..85982392 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -19,7 +19,8 @@ public: void refresh(); signals: - void changed(std::vector categories, std::vector content); + void filtersChanged(std::vector categories, std::vector content); + void criteriaChanged(ModListSortProxy::FilterMode mode, bool inverse, bool separators); private: Ui::MainWindow* ui; @@ -27,6 +28,7 @@ private: void onContextMenu(const QPoint &pos); void onSelection(); + void onCriteriaChanged(); void editCategories(); @@ -37,6 +39,8 @@ private: void addContentFilters(); void addCategoryFilters( QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); + void addSpecialFilterItem(int type); + }; #endif // MODORGANIZER_CATEGORIESLIST_INCLUDED diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b63f9211..1319d906 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -262,10 +262,14 @@ MainWindow::MainWindow(Settings &settings m_CategoryFactory.loadCategories(); m_Filters.reset(new FilterList(ui, m_CategoryFactory)); - connect(m_Filters.get(), &FilterList::changed, [&](auto&& cats, auto&& content) { - m_ModListSortProxy->setCategoryFilter(cats); - m_ModListSortProxy->setContentFilter(content); - }); + + connect( + m_Filters.get(), &FilterList::filtersChanged, + [&](auto&& cats, auto&& content) { onFilters(cats, content); }); + + connect( + m_Filters.get(), &FilterList::criteriaChanged, + [&](auto mode, bool inv, bool sep) { onFiltersCriteria(mode, inv, sep); }); ui->logList->setCore(m_OrganizerCore); @@ -1229,7 +1233,7 @@ void MainWindow::showEvent(QShowEvent *event) if (!m_WasVisible) { readSettings(); - m_Filters->refresh(); + refreshFilters(); // this needs to be connected here instead of in the constructor because the // actual changing of the stylesheet is done by MOApplication, which @@ -4025,7 +4029,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - m_Filters->refresh(); + refreshFilters(); } void MainWindow::replaceCategories_MenuHandler() { @@ -4069,7 +4073,7 @@ void MainWindow::replaceCategories_MenuHandler() { m_OrganizerCore.modList()->notifyChange(m_ContextRow); } - m_Filters->refresh(); + refreshFilters(); } void MainWindow::saveArchiveList() @@ -4936,7 +4940,7 @@ void MainWindow::on_actionSettings_triggered() instManager->setDownloadDirectory(settings.paths().downloads()); fixCategories(); - m_Filters->refresh(); + refreshFilters(); if (settings.paths().profiles() != oldProfilesDirectory) { refreshProfiles(); @@ -6083,6 +6087,69 @@ void MainWindow::deselectFilters() m_Filters->clearSelection(); } +void MainWindow::refreshFilters() +{ + QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + + QVariant currentIndexName = ui->modList->currentIndex().data(); + ui->modList->setCurrentIndex(QModelIndex()); + + m_Filters->refresh(); + + ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + + QModelIndexList matchList; + if (currentIndexName.isValid()) { + matchList = ui->modList->model()->match( + ui->modList->model()->index(0, 0), + Qt::DisplayRole, + currentIndexName); + } + + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } +} + +void MainWindow::onFilters( + const std::vector& categories, const std::vector& content) +{ + m_ModListSortProxy->setCategoryFilter(categories); + m_ModListSortProxy->setContentFilter(content); + + QString label = "?"; + + if ((categories.size() + content.size()) > 1) { + label = tr(""); + } else if (!categories.empty()) { + const int c = categories[0]; + label = m_CategoryFactory.getCategoryNameByID(c); + if (label.isEmpty()) { + log::error("category '{}' not found", c); + } + } else if (!content.empty()) { + const int c = content[0]; + try { + label = ModInfo::getContentTypeName(c); + } + catch(std::exception&) { + log::error("content filter '{}' not found", c); + } + } else { + label = ""; + } + + ui->currentCategoryLabel->setText(label); + ui->modList->reset(); +} + +void MainWindow::onFiltersCriteria( + ModListSortProxy::FilterMode mode, bool inverse, bool separators) +{ + m_ModListSortProxy->setFilterMode(mode); + m_ModListSortProxy->setFilterNot(inverse); + m_ModListSortProxy->setFilterSeparators(separators); +} void MainWindow::updateESPLock(bool locked) { @@ -6385,30 +6452,6 @@ void MainWindow::on_restoreModsButton_clicked() } } -void MainWindow::on_categoriesAndBtn_toggled(bool checked) -{ - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND); - } -} - -void MainWindow::on_categoriesOrBtn_toggled(bool checked) -{ - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR); - } -} - -void MainWindow::on_categoriesNotBtn_toggled(bool checked) -{ - m_ModListSortProxy->setFilterNot(checked); -} - -void MainWindow::on_categoriesSeparators_toggled(bool checked) -{ - m_ModListSortProxy->setFilterSeparators(checked); -} - void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { QToolTip::showText(QCursor::pos(), diff --git a/src/mainwindow.h b/src/mainwindow.h index 04ba7a35..9837378b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -513,6 +513,9 @@ private slots: void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); void deselectFilters(); + void refreshFilters(); + void onFilters(const std::vector& categories, const std::vector& content); + void onFiltersCriteria(ModListSortProxy::FilterMode mode, bool inverse, bool separators); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); @@ -651,10 +654,6 @@ private slots: // ui slots void on_restoreButton_clicked(); void on_restoreModsButton_clicked(); void on_saveModsButton_clicked(); - void on_categoriesAndBtn_toggled(bool checked); - void on_categoriesOrBtn_toggled(bool checked); - void on_categoriesNotBtn_toggled(bool checked); - void on_categoriesSeparators_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); void storeSettings(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 7e11c70e..ed35f783 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -66,7 +66,7 @@ 1 - + 120 @@ -96,7 +96,7 @@ - + false @@ -130,7 +130,7 @@ - + Display mods that match all selected categories. @@ -143,7 +143,7 @@ - + Display mods that match at least one of the selected categories @@ -153,7 +153,7 @@ - + Invert each selected category @@ -163,7 +163,7 @@ - + Include separators -- cgit v1.3.1 From e99dfe153c62f914ada0605430305fca81a332a9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 01:53:09 -0500 Subject: renamed filters to criteria merged categories and content, they can be distinguished using the type added a not flag for criteria, not used yet --- src/filterlist.cpp | 116 +++++++++++++++++++++++++-------------------- src/filterlist.h | 14 +++--- src/mainwindow.cpp | 50 +++++++++----------- src/mainwindow.h | 4 +- src/mainwindow.ui | 38 +++++++-------- src/modlistsortproxy.cpp | 120 +++++++++++++++++------------------------------ src/modlistsortproxy.h | 42 +++++++++++------ 7 files changed, 182 insertions(+), 202 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 5562736d..9e5437b3 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -6,6 +6,9 @@ using namespace MOBase; +const int CategoryIDRole = Qt::UserRole; +const int CategoryTypeRole = Qt::UserRole + 1; + FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) : ui(ui), m_factory(factory) { @@ -29,61 +32,76 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) ui->filtersOr, &QCheckBox::toggled, [&]{ onCriteriaChanged(); }); - connect( - ui->filtersNot, &QCheckBox::toggled, - [&]{ onCriteriaChanged(); }); - connect( ui->filtersSeparators, &QCheckBox::toggled, [&]{ onCriteriaChanged(); }); + + ui->filters->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->filters->header()->resizeSection(1, 50); } -QTreeWidgetItem* FilterList::addFilterItem( +QTreeWidgetItem* FilterList::addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, - ModListSortProxy::FilterType type) + ModListSortProxy::CriteriaType type) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); + item->setData(0, Qt::ToolTipRole, name); - item->setData(0, Qt::UserRole, categoryID); - item->setData(0, Qt::UserRole + 1, type); + item->setData(0, CategoryIDRole, categoryID); + item->setData(0, CategoryTypeRole, type); + if (root != nullptr) { root->addChild(item); } else { ui->filters->addTopLevelItem(item); } + + auto* w = new QWidget; + w->setStyleSheet("background-color: rgba(0,0,0,0)"); + + auto* ly = new QVBoxLayout(w); + ly->setAlignment(Qt::AlignCenter); + ly->setContentsMargins(0, 0, 0, 0); + + auto* cb = new QCheckBox; + connect(cb, &QCheckBox::toggled, [&]{ onSelection(); }); + ly->addWidget(cb); + + ui->filters->setItemWidget(item, 1, w); + return item; } -void FilterList::addContentFilters() +void FilterList::addContentCriteria() { for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { - addFilterItem( + addCriteriaItem( nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); } } -void FilterList::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) +void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) { - for (unsigned int i = 1; - i < static_cast(m_factory.numCategories()); ++i) { - if ((m_factory.getParentID(i) == targetID)) { + const auto count = static_cast(m_factory.numCategories()); + for (unsigned int i = 1; i < count; ++i) { + if (m_factory.getParentID(i) == targetID) { int categoryID = m_factory.getCategoryID(i); if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { QTreeWidgetItem *item = - addFilterItem(root, m_factory.getCategoryName(i), + addCriteriaItem(root, m_factory.getCategoryName(i), categoryID, ModListSortProxy::TYPE_CATEGORY); if (m_factory.hasChildren(i)) { - addCategoryFilters(item, categoriesUsed, categoryID); + addCategoryCriteria(item, categoriesUsed, categoryID); } } } } } -void FilterList::addSpecialFilterItem(int type) +void FilterList::addSpecialCriteria(int type) { - addFilterItem( + addCriteriaItem( nullptr, m_factory.getSpecialCategoryName(type), type, ModListSortProxy::TYPE_SPECIAL); } @@ -98,19 +116,19 @@ void FilterList::refresh() ui->filters->clear(); using F = CategoryFactory; - addSpecialFilterItem(F::CATEGORY_SPECIAL_CHECKED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_UNCHECKED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); - addSpecialFilterItem(F::CATEGORY_SPECIAL_BACKUP); - addSpecialFilterItem(F::CATEGORY_SPECIAL_MANAGED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_UNMANAGED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NOCATEGORY); - addSpecialFilterItem(F::CATEGORY_SPECIAL_CONFLICT); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NOTENDORSED); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NONEXUSID); - addSpecialFilterItem(F::CATEGORY_SPECIAL_NOGAMEDATA); - - addContentFilters(); + addSpecialCriteria(F::CATEGORY_SPECIAL_CHECKED); + addSpecialCriteria(F::CATEGORY_SPECIAL_UNCHECKED); + addSpecialCriteria(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); + addSpecialCriteria(F::CATEGORY_SPECIAL_BACKUP); + addSpecialCriteria(F::CATEGORY_SPECIAL_MANAGED); + addSpecialCriteria(F::CATEGORY_SPECIAL_UNMANAGED); + addSpecialCriteria(F::CATEGORY_SPECIAL_NOCATEGORY); + addSpecialCriteria(F::CATEGORY_SPECIAL_CONFLICT); + addSpecialCriteria(F::CATEGORY_SPECIAL_NOTENDORSED); + addSpecialCriteria(F::CATEGORY_SPECIAL_NONEXUSID); + addSpecialCriteria(F::CATEGORY_SPECIAL_NOGAMEDATA); + + addContentCriteria(); std::set categoriesUsed; for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { @@ -130,7 +148,7 @@ void FilterList::refresh() } } - addCategoryFilters(nullptr, categoriesUsed, 0); + addCategoryCriteria(nullptr, categoriesUsed, 0); for (const QString &item : selectedItems) { QList matches = ui->filters->findItems( @@ -145,7 +163,7 @@ void FilterList::refresh() void FilterList::setSelection(std::vector categories) { for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { - if (ui->filters->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + if (ui->filters->topLevelItem(i)->data(0, CategoryIDRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); break; } @@ -159,27 +177,24 @@ void FilterList::clearSelection() void FilterList::onSelection() { - QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); - std::vector categories; - std::vector content; + const QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); + std::vector criteria; - for (const QModelIndex &index : indices) { - const int filterType = index.data(Qt::UserRole + 1).toInt(); + for (auto* item: ui->filters->selectedItems()) { + const auto type = static_cast( + item->data(0, CategoryTypeRole).toInt()); - if ((filterType == ModListSortProxy::TYPE_CATEGORY) || (filterType == ModListSortProxy::TYPE_SPECIAL)) { - const int categoryId = index.data(Qt::UserRole).toInt(); - if (categoryId != CategoryFactory::CATEGORY_NONE) { - categories.push_back(categoryId); - } - } else if (filterType == ModListSortProxy::TYPE_CONTENT) { - const int contentId = index.data(Qt::UserRole).toInt(); - content.push_back(contentId); - } + const int id = item->data(0, CategoryIDRole).toInt(); + + auto* cb = static_cast(ui->filters->itemWidget(item, 1)); + const bool inverse = cb->isChecked(); + + criteria.push_back({type, id, inverse}); } - ui->filtersClear->setEnabled(categories.size() > 0 || content.size() >0); + ui->filtersClear->setEnabled(!criteria.empty()); - emit filtersChanged(categories, content); + emit criteriaChanged(criteria); } void FilterList::onContextMenu(const QPoint &pos) @@ -205,8 +220,7 @@ void FilterList::onCriteriaChanged() const auto mode = ui->filtersAnd->isChecked() ? ModListSortProxy::FILTER_AND : ModListSortProxy::FILTER_OR; - const bool inverse = ui->filtersNot->isChecked(); const bool separators = ui->filtersSeparators->isChecked(); - emit criteriaChanged(mode, inverse, separators); + emit optionsChanged(mode, separators); } diff --git a/src/filterlist.h b/src/filterlist.h index 85982392..418989e7 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -19,8 +19,8 @@ public: void refresh(); signals: - void filtersChanged(std::vector categories, std::vector content); - void criteriaChanged(ModListSortProxy::FilterMode mode, bool inverse, bool separators); + void criteriaChanged(std::vector criteria); + void optionsChanged(ModListSortProxy::FilterMode mode, bool separators); private: Ui::MainWindow* ui; @@ -32,14 +32,14 @@ private: void editCategories(); - QTreeWidgetItem* addFilterItem( + QTreeWidgetItem* addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, - ModListSortProxy::FilterType type); + ModListSortProxy::CriteriaType type); - void addContentFilters(); - void addCategoryFilters( + void addContentCriteria(); + void addCategoryCriteria( QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); - void addSpecialFilterItem(int type); + void addSpecialCriteria(int type); }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1319d906..03d61bc6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -264,12 +264,12 @@ MainWindow::MainWindow(Settings &settings m_Filters.reset(new FilterList(ui, m_CategoryFactory)); connect( - m_Filters.get(), &FilterList::filtersChanged, - [&](auto&& cats, auto&& content) { onFilters(cats, content); }); + m_Filters.get(), &FilterList::criteriaChanged, + [&](auto&& v) { onFiltersCriteria(v); }); connect( - m_Filters.get(), &FilterList::criteriaChanged, - [&](auto mode, bool inv, bool sep) { onFiltersCriteria(mode, inv, sep); }); + m_Filters.get(), &FilterList::optionsChanged, + [&](auto mode, bool sep) { onFiltersOptions(mode, sep); }); ui->logList->setCore(m_OrganizerCore); @@ -4124,7 +4124,12 @@ void MainWindow::checkModsForUpdates() } if (updatesAvailable || checkingModsForUpdate) { - m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + m_ModListSortProxy->setCriteria({{ + ModListSortProxy::TYPE_SPECIAL, + CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, + false} + }); + m_Filters->setSelection({CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE}); } } @@ -6111,44 +6116,31 @@ void MainWindow::refreshFilters() } } -void MainWindow::onFilters( - const std::vector& categories, const std::vector& content) +void MainWindow::onFiltersCriteria(const std::vector& criteria) { - m_ModListSortProxy->setCategoryFilter(categories); - m_ModListSortProxy->setContentFilter(content); + m_ModListSortProxy->setCriteria(criteria); QString label = "?"; - if ((categories.size() + content.size()) > 1) { - label = tr(""); - } else if (!categories.empty()) { - const int c = categories[0]; - label = m_CategoryFactory.getCategoryNameByID(c); + if (criteria.empty()) { + label = ""; + } else if (criteria.size() == 1) { + const auto& c = criteria[0]; + label = m_CategoryFactory.getCategoryNameByID(c.id); if (label.isEmpty()) { - log::error("category '{}' not found", c); - } - } else if (!content.empty()) { - const int c = content[0]; - try { - label = ModInfo::getContentTypeName(c); - } - catch(std::exception&) { - log::error("content filter '{}' not found", c); + log::error("category '{}' not found", c.id); } } else { - label = ""; + label = tr(""); } ui->currentCategoryLabel->setText(label); ui->modList->reset(); } -void MainWindow::onFiltersCriteria( - ModListSortProxy::FilterMode mode, bool inverse, bool separators) +void MainWindow::onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators) { - m_ModListSortProxy->setFilterMode(mode); - m_ModListSortProxy->setFilterNot(inverse); - m_ModListSortProxy->setFilterSeparators(separators); + m_ModListSortProxy->setOptions(mode, separators); } void MainWindow::updateESPLock(bool locked) diff --git a/src/mainwindow.h b/src/mainwindow.h index 9837378b..0b559300 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -514,8 +514,8 @@ private slots: void deselectFilters(); void refreshFilters(); - void onFilters(const std::vector& categories, const std::vector& content); - void onFiltersCriteria(ModListSortProxy::FilterMode mode, bool inverse, bool separators); + void onFiltersCriteria(const std::vector& filters); + void onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index ed35f783..7cc7dca4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -85,12 +85,26 @@ true + + false + + true + + + true + + false - 1 + Category + + + + + Invert @@ -152,16 +166,6 @@ - - - - Invert each selected category - - - Not - - - @@ -351,9 +355,6 @@ p, li { white-space: pre-wrap; } Qt::CustomContextMenu - - List of available mods. - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. @@ -448,14 +449,7 @@ p, li { white-space: pre-wrap; } - - - - 8 - true - - - + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index d2a5e258..646401b9 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -38,7 +38,6 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) , m_Profile(profile) , m_FilterActive(false) , m_FilterMode(FILTER_AND) - , m_FilterNot(false) , m_FilterSeparators(false) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter @@ -52,26 +51,20 @@ void ModListSortProxy::setProfile(Profile *profile) void ModListSortProxy::updateFilterActive() { - m_FilterActive = ((m_CategoryFilter.size() > 0) - || (m_ContentFilter.size() > 0) - || !m_CurrentFilter.isEmpty()); + m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); emit filterActive(m_FilterActive); } -void ModListSortProxy::setCategoryFilter(const std::vector &categories) +void ModListSortProxy::setCriteria(const std::vector& criteria) { - //avoid refreshing the filter unless we are checking all mods for update. - if (categories != m_CategoryFilter || (!categories.empty() && categories.at(0) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)) { - m_CategoryFilter = categories; - updateFilterActive(); - invalidate(); - } -} - -void ModListSortProxy::setContentFilter(const std::vector &content) -{ - if (content != m_ContentFilter) { - m_ContentFilter = content; + // avoid refreshing the filter unless we are checking all mods for update. + const bool changed = (criteria != m_Criteria); + const bool isForUpdates = ( + !criteria.empty() && + criteria[0].id == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE); + + if (changed || isForUpdates) { + m_Criteria = criteria; updateFilterActive(); invalidate(); } @@ -248,12 +241,10 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, return lt; } -void ModListSortProxy::updateFilter(const QString &filter) +void ModListSortProxy::updateFilter(const QString& filter) { - m_CurrentFilter = filter; + m_Filter = filter; updateFilterActive(); - // using invalidateFilter here should be enough but that crashes the application? WTF? - // invalidateFilter(); invalidate(); } @@ -282,14 +273,8 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons return false; } - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - if (!categoryMatchesMod(info, enabled, *iter)) { - return false; - } - } - - foreach (int content, m_ContentFilter) { - if (!contentMatchesMod(info, enabled, content)) { + for (auto&& c : m_Criteria) { + if (!criteriaMatchesMod(info, enabled, c)) { return false; } } @@ -303,29 +288,35 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const return false; } - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - if (categoryMatchesMod(info, enabled, *iter)) { + for (auto&& c : m_Criteria) { + if (criteriaMatchesMod(info, enabled, c)) { return true; } } - if (!m_CategoryFilter.empty()) { + if (!m_Criteria.empty()) { // nothing matched return false; } - foreach (int content, m_ContentFilter) { - if (contentMatchesMod(info, enabled, content)) { - return true; - } - } + return true; +} - if (!m_ContentFilter.empty()) { - // nothing matched - return false; - } +bool ModListSortProxy::criteriaMatchesMod( + ModInfo::Ptr info, bool enabled, const Criteria& c) const +{ + switch (c.type) + { + case TYPE_SPECIAL: // fall-through + case TYPE_CATEGORY: + return categoryMatchesMod(info, enabled, c.id); - return true; + case TYPE_CONTENT: + return contentMatchesMod(info, enabled, c.id); + + default: + return false; + } } bool ModListSortProxy::categoryMatchesMod( @@ -414,29 +405,19 @@ bool ModListSortProxy::categoryMatchesMod( } } - if (m_FilterNot) { - b = !b; - } - return b; } bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const { - bool b = info->hasContent(static_cast(content)); - - if (m_FilterNot) { - b = !b; - } - - return b; + return info->hasContent(static_cast(content)); } bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { - if (!m_CurrentFilter.isEmpty()) { + if (!m_Filter.isEmpty()) { bool display = false; - QString filterCopy = QString(m_CurrentFilter); + QString filterCopy = QString(m_Filter); filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); QStringList ORList = filterCopy.split(";", QString::SkipEmptyParts); @@ -527,26 +508,11 @@ void ModListSortProxy::setColumnVisible(int column, bool visible) m_EnabledColumns[column] = visible; } -void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) +void ModListSortProxy::setOptions(ModListSortProxy::FilterMode mode, bool separators) { - if (m_FilterMode != mode) { + if (m_FilterMode != mode || separators != m_FilterSeparators) { m_FilterMode = mode; - this->invalidate(); - } -} - -void ModListSortProxy::setFilterNot(bool b) -{ - if (b != m_FilterNot) { - m_FilterNot = b; - this->invalidate(); - } -} - -void ModListSortProxy::setFilterSeparators(bool b) -{ - if (b != m_FilterSeparators) { - m_FilterSeparators = b; + m_FilterSeparators = separators; this->invalidate(); } } @@ -623,8 +589,8 @@ void ModListSortProxy::aboutToChangeData() // (at least with some Qt versions) // this may be related to the fact that the item being edited may disappear from the view as a // result of the edit - m_PreChangeFilters = categoryFilter(); - setCategoryFilter(std::vector()); + m_PreChangeCriteria = m_Criteria; + setCriteria({}); } void ModListSortProxy::postDataChanged() @@ -633,8 +599,8 @@ void ModListSortProxy::postDataChanged() // or at least the view continues to think it's being edited. As a result no new editor can be // opened QTimer::singleShot(10, [this] () { - setCategoryFilter(m_PreChangeFilters); - m_PreChangeFilters.clear(); + setCriteria(m_PreChangeCriteria); + m_PreChangeCriteria.clear(); }); } diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 2ebfbcf0..5aeaccce 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -37,22 +37,38 @@ public: FILTER_OR }; - enum FilterType { + enum CriteriaType { TYPE_SPECIAL, TYPE_CATEGORY, TYPE_CONTENT }; + struct Criteria + { + CriteriaType type; + int id; + bool inverse; + + bool operator==(const Criteria& other) const + { + return + (type == other.type) && + (id == other.id) && + (inverse == other.inverse); + } + + bool operator!=(const Criteria& other) const + { + return !(*this == other); + } + }; + public: explicit ModListSortProxy(Profile *profile, QObject *parent = 0); void setProfile(Profile *profile); - void setCategoryFilter(const std::vector &categories); - std::vector categoryFilter() const { return m_CategoryFilter; } - - void setContentFilter(const std::vector &content); virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, @@ -84,9 +100,8 @@ public: */ bool isFilterActive() const { return m_FilterActive; } - void setFilterMode(FilterMode mode); - void setFilterNot(bool b); - void setFilterSeparators(bool b); + void setCriteria(const std::vector& criteria); + void setOptions(FilterMode mode, bool separators); /** * @brief tests if the specified index has child nodes @@ -131,19 +146,18 @@ private slots: void postDataChanged(); private: - Profile *m_Profile; - std::vector m_CategoryFilter; - std::vector m_ContentFilter; + Profile* m_Profile; + std::vector m_Criteria; + QString m_Filter; std::bitset m_EnabledColumns; - QString m_CurrentFilter; bool m_FilterActive; FilterMode m_FilterMode; - bool m_FilterNot; bool m_FilterSeparators; - std::vector m_PreChangeFilters; + std::vector m_PreChangeCriteria; + bool criteriaMatchesMod(ModInfo::Ptr info, bool enabled, const Criteria& c) const; bool categoryMatchesMod(ModInfo::Ptr info, bool enabled, int category) const; bool contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const; }; -- cgit v1.3.1 From ed14d5510d932362f8e232496b824729e096d3cf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 02:20:18 -0500 Subject: implemented not flag moved stuff to CriteriaItem fixed jumbled names in getSpecialCategoryName() --- src/categories.cpp | 16 ++++---- src/filterlist.cpp | 102 +++++++++++++++++++++++++++++++++-------------- src/filterlist.h | 2 + src/modlistsortproxy.cpp | 23 +++++++++-- 4 files changed, 102 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/categories.cpp b/src/categories.cpp index 082b4fbc..b75efefa 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -327,14 +327,14 @@ QString CategoryFactory::getSpecialCategoryName(int type) const case CATEGORY_SPECIAL_CHECKED: return QObject::tr(""); case CATEGORY_SPECIAL_UNCHECKED: return QObject::tr(""); case CATEGORY_SPECIAL_UPDATEAVAILABLE: return QObject::tr(""); - case CATEGORY_SPECIAL_NOCATEGORY: return QObject::tr(""); - case CATEGORY_SPECIAL_CONFLICT: return QObject::tr(""); - case CATEGORY_SPECIAL_NOTENDORSED: return QObject::tr(""); - case CATEGORY_SPECIAL_BACKUP: return QObject::tr(""); - case CATEGORY_SPECIAL_MANAGED: return QObject::tr(""); - case CATEGORY_SPECIAL_UNMANAGED: return QObject::tr(""); - case CATEGORY_SPECIAL_NOGAMEDATA: return QObject::tr(""); - case CATEGORY_SPECIAL_NONEXUSID: return QObject::tr(""); + case CATEGORY_SPECIAL_NOCATEGORY: return QObject::tr(""); + case CATEGORY_SPECIAL_CONFLICT: return QObject::tr(""); + case CATEGORY_SPECIAL_NOTENDORSED: return QObject::tr(""); + case CATEGORY_SPECIAL_BACKUP: return QObject::tr(""); + case CATEGORY_SPECIAL_MANAGED: return QObject::tr(""); + case CATEGORY_SPECIAL_UNMANAGED: return QObject::tr(""); + case CATEGORY_SPECIAL_NOGAMEDATA: return QObject::tr(""); + case CATEGORY_SPECIAL_NONEXUSID: return QObject::tr(""); default: return {}; } } diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 9e5437b3..31783c88 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -5,9 +5,60 @@ #include using namespace MOBase; +using CriteriaType = ModListSortProxy::CriteriaType; +using Criteria = ModListSortProxy::Criteria; + +class FilterList::CriteriaItem : public QTreeWidgetItem +{ +public: + CriteriaItem(FilterList* list, QString name, CriteriaType type, int id) + : QTreeWidgetItem({name}), m_list(list), m_widget(nullptr), m_checkbox(nullptr) + { + setData(0, Qt::ToolTipRole, name); + setData(0, TypeRole, type); + setData(0, IDRole, id); + + m_widget = new QWidget; + m_widget->setStyleSheet("background-color: rgba(0,0,0,0)"); + + auto* ly = new QVBoxLayout(m_widget); + ly->setAlignment(Qt::AlignCenter); + ly->setContentsMargins(0, 0, 0, 0); + + m_checkbox = new QCheckBox; + QObject::connect(m_checkbox, &QCheckBox::toggled, [&]{ m_list->onSelection(); }); + ly->addWidget(m_checkbox); + } + + QWidget* widget() + { + return m_widget; + } + + CriteriaType type() const + { + return static_cast(data(0, TypeRole).toInt()); + } + + int id() const + { + return data(0, IDRole).toInt(); + } + + bool inverse() const + { + return m_checkbox->isChecked(); + } + +private: + const int IDRole = Qt::UserRole; + const int TypeRole = Qt::UserRole + 1; + + FilterList* m_list; + QWidget* m_widget; + QCheckBox* m_checkbox; +}; -const int CategoryIDRole = Qt::UserRole; -const int CategoryTypeRole = Qt::UserRole + 1; FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) : ui(ui), m_factory(factory) @@ -42,13 +93,9 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) QTreeWidgetItem* FilterList::addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, - ModListSortProxy::CriteriaType type) + CriteriaType type) { - QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); - - item->setData(0, Qt::ToolTipRole, name); - item->setData(0, CategoryIDRole, categoryID); - item->setData(0, CategoryTypeRole, type); + auto* item = new CriteriaItem(this, name, type, categoryID); if (root != nullptr) { root->addChild(item); @@ -56,18 +103,7 @@ QTreeWidgetItem* FilterList::addCriteriaItem( ui->filters->addTopLevelItem(item); } - auto* w = new QWidget; - w->setStyleSheet("background-color: rgba(0,0,0,0)"); - - auto* ly = new QVBoxLayout(w); - ly->setAlignment(Qt::AlignCenter); - ly->setContentsMargins(0, 0, 0, 0); - - auto* cb = new QCheckBox; - connect(cb, &QCheckBox::toggled, [&]{ onSelection(); }); - ly->addWidget(cb); - - ui->filters->setItemWidget(item, 1, w); + ui->filters->setItemWidget(item, 1, item->widget()); return item; } @@ -163,7 +199,14 @@ void FilterList::refresh() void FilterList::setSelection(std::vector categories) { for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { - if (ui->filters->topLevelItem(i)->data(0, CategoryIDRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + const auto* item = dynamic_cast( + ui->filters->topLevelItem(i)); + + if (!item) { + continue; + } + + if (item->id() == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); break; } @@ -178,18 +221,17 @@ void FilterList::clearSelection() void FilterList::onSelection() { const QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); - std::vector criteria; + std::vector criteria; for (auto* item: ui->filters->selectedItems()) { - const auto type = static_cast( - item->data(0, CategoryTypeRole).toInt()); - - const int id = item->data(0, CategoryIDRole).toInt(); - - auto* cb = static_cast(ui->filters->itemWidget(item, 1)); - const bool inverse = cb->isChecked(); + const auto* ci = dynamic_cast(item); + if (!ci) { + continue; + } - criteria.push_back({type, id, inverse}); + criteria.push_back({ + ci->type(), ci->id(), ci->inverse() + }); } ui->filtersClear->setEnabled(!criteria.empty()); diff --git a/src/filterlist.h b/src/filterlist.h index 418989e7..72fe3b5f 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -23,6 +23,8 @@ signals: void optionsChanged(ModListSortProxy::FilterMode mode, bool separators); private: + class CriteriaItem; + Ui::MainWindow* ui; CategoryFactory& m_factory; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 646401b9..3bb02c0f 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -305,18 +305,35 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const bool ModListSortProxy::criteriaMatchesMod( ModInfo::Ptr info, bool enabled, const Criteria& c) const { + bool b = false; + switch (c.type) { case TYPE_SPECIAL: // fall-through case TYPE_CATEGORY: - return categoryMatchesMod(info, enabled, c.id); + { + b = categoryMatchesMod(info, enabled, c.id); + break; + } case TYPE_CONTENT: - return contentMatchesMod(info, enabled, c.id); + { + b = contentMatchesMod(info, enabled, c.id); + break; + } default: - return false; + { + log::error("bad criteria type {}", c.type); + break; + } } + + if (c.inverse) { + b = !b; + } + + return b; } bool ModListSortProxy::categoryMatchesMod( -- cgit v1.3.1 From f463c5494362a0fa75c581e59192e2640ec10f7e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 03:02:11 -0500 Subject: added context menu items so set/unset inverted flag --- src/filterlist.cpp | 33 +++++++++++++++++++++++++++++++-- src/filterlist.h | 1 + 2 files changed, 32 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 31783c88..8f297af6 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -50,6 +50,11 @@ public: return m_checkbox->isChecked(); } + void setInverted(bool b) + { + m_checkbox->setChecked(b); + } + private: const int IDRole = Qt::UserRole; const int TypeRole = Qt::UserRole + 1; @@ -223,7 +228,7 @@ void FilterList::onSelection() const QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); std::vector criteria; - for (auto* item: ui->filters->selectedItems()) { + for (auto* item : ui->filters->selectedItems()) { const auto* ci = dynamic_cast(item); if (!ci) { continue; @@ -242,8 +247,11 @@ void FilterList::onSelection() void FilterList::onContextMenu(const QPoint &pos) { QMenu menu; + menu.addAction(tr("Deselect filters"), [&]{ clearSelection(); }); + menu.addAction(tr("Set inverted"), [&]{ toggleInverted(true); }); + menu.addAction(tr("Unset inverted"), [&]{ toggleInverted(false); }); + menu.addSeparator(); menu.addAction(tr("Edit Categories..."), [&]{ editCategories(); }); - menu.addAction(tr("Deselect filter"), [&]{ clearSelection(); }); menu.exec(ui->filters->viewport()->mapToGlobal(pos)); } @@ -257,6 +265,27 @@ void FilterList::editCategories() } } +void FilterList::toggleInverted(bool b) +{ + bool changed = false; + + for (auto* item : ui->filters->selectedItems()) { + auto* ci = dynamic_cast(item); + if (!ci) { + continue; + } + + if (ci->inverse() != b) { + ci->setInverted(b); + changed = true; + } + } + + if (changed) { + onSelection(); + } +} + void FilterList::onCriteriaChanged() { const auto mode = ui->filtersAnd->isChecked() ? diff --git a/src/filterlist.h b/src/filterlist.h index 72fe3b5f..1fd0942a 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -33,6 +33,7 @@ private: void onCriteriaChanged(); void editCategories(); + void toggleInverted(bool b); QTreeWidgetItem* addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, -- cgit v1.3.1 From a38d1723bffcd20bc7011c0fe635636b936aa78b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 03:21:23 -0500 Subject: removed redundant categories now that there's a not filter disabled collapsing for filter, there's already a button to hide it --- src/categories.cpp | 24 +++++++++++------------- src/categories.h | 31 +++++++++++++------------------ src/filterlist.cpp | 28 +++++++++++++++------------- src/mainwindow.cpp | 4 ++-- src/mainwindow.ui | 3 +++ src/modlistsortproxy.cpp | 32 ++++++++++---------------------- 6 files changed, 54 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/categories.cpp b/src/categories.cpp index b75efefa..1bd56f7f 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -320,21 +320,19 @@ QString CategoryFactory::getCategoryName(unsigned int index) const return m_Categories[index].m_Name; } -QString CategoryFactory::getSpecialCategoryName(int type) const +QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const { switch (type) { - case CATEGORY_SPECIAL_CHECKED: return QObject::tr(""); - case CATEGORY_SPECIAL_UNCHECKED: return QObject::tr(""); - case CATEGORY_SPECIAL_UPDATEAVAILABLE: return QObject::tr(""); - case CATEGORY_SPECIAL_NOCATEGORY: return QObject::tr(""); - case CATEGORY_SPECIAL_CONFLICT: return QObject::tr(""); - case CATEGORY_SPECIAL_NOTENDORSED: return QObject::tr(""); - case CATEGORY_SPECIAL_BACKUP: return QObject::tr(""); - case CATEGORY_SPECIAL_MANAGED: return QObject::tr(""); - case CATEGORY_SPECIAL_UNMANAGED: return QObject::tr(""); - case CATEGORY_SPECIAL_NOGAMEDATA: return QObject::tr(""); - case CATEGORY_SPECIAL_NONEXUSID: return QObject::tr(""); + case Checked: return QObject::tr(""); + case UpdateAvailable: return QObject::tr(""); + case HasNoCategory: return QObject::tr(""); + case Conflict: return QObject::tr(""); + case NotEndorsed: return QObject::tr(""); + case Backup: return QObject::tr(""); + case Managed: return QObject::tr(""); + case NoGameData: return QObject::tr(""); + case NoNexusID: return QObject::tr(""); default: return {}; } } @@ -344,7 +342,7 @@ QString CategoryFactory::getCategoryNameByID(int id) const auto itor = m_IDMap.find(id); if (itor == m_IDMap.end()) { - return getSpecialCategoryName(id); + return getSpecialCategoryName(static_cast(id)); } else { const auto index = itor->second; if (index >= m_Categories.size()) { diff --git a/src/categories.h b/src/categories.h index 2041ce1f..296e7711 100644 --- a/src/categories.h +++ b/src/categories.h @@ -37,25 +37,20 @@ class CategoryFactory { friend class CategoriesDialog; public: - - static const int CATEGORY_NONE = 0; - - static const int CATEGORY_SPECIAL_FIRST = 10000; - static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; - static const int CATEGORY_SPECIAL_UNCHECKED = 10001; - static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; - static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; - static const int CATEGORY_SPECIAL_CONFLICT = 10004; - static const int CATEGORY_SPECIAL_NOTENDORSED = 10005; - static const int CATEGORY_SPECIAL_BACKUP = 10006; - static const int CATEGORY_SPECIAL_MANAGED = 10007; - static const int CATEGORY_SPECIAL_UNMANAGED = 10008; - static const int CATEGORY_SPECIAL_NOGAMEDATA = 10009; - static const int CATEGORY_SPECIAL_NONEXUSID = 10010; - + enum SpecialCategories + { + Checked = 10000, + UpdateAvailable, + HasNoCategory, + Conflict, + NotEndorsed, + Backup, + Managed, + NoGameData, + NoNexusID + }; public: - struct Category { Category(int sortValue, int id, const QString &name, const std::vector &nexusIDs, int parentID) : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), @@ -144,7 +139,7 @@ public: * @return QString name of the category **/ QString getCategoryName(unsigned int index) const; - QString getSpecialCategoryName(int type) const; + QString getSpecialCategoryName(SpecialCategories type) const; QString getCategoryNameByID(int id) const; /** diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 8f297af6..36cdacd0 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -94,6 +94,8 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) ui->filters->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->filters->header()->resizeSection(1, 50); + ui->categoriesSplitter->setCollapsible(0, false); + ui->categoriesSplitter->setCollapsible(1, false); } QTreeWidgetItem* FilterList::addCriteriaItem( @@ -142,8 +144,10 @@ void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set void FilterList::addSpecialCriteria(int type) { + const auto sc = static_cast(type); + addCriteriaItem( - nullptr, m_factory.getSpecialCategoryName(type), + nullptr, m_factory.getSpecialCategoryName(sc), type, ModListSortProxy::TYPE_SPECIAL); } @@ -157,17 +161,15 @@ void FilterList::refresh() ui->filters->clear(); using F = CategoryFactory; - addSpecialCriteria(F::CATEGORY_SPECIAL_CHECKED); - addSpecialCriteria(F::CATEGORY_SPECIAL_UNCHECKED); - addSpecialCriteria(F::CATEGORY_SPECIAL_UPDATEAVAILABLE); - addSpecialCriteria(F::CATEGORY_SPECIAL_BACKUP); - addSpecialCriteria(F::CATEGORY_SPECIAL_MANAGED); - addSpecialCriteria(F::CATEGORY_SPECIAL_UNMANAGED); - addSpecialCriteria(F::CATEGORY_SPECIAL_NOCATEGORY); - addSpecialCriteria(F::CATEGORY_SPECIAL_CONFLICT); - addSpecialCriteria(F::CATEGORY_SPECIAL_NOTENDORSED); - addSpecialCriteria(F::CATEGORY_SPECIAL_NONEXUSID); - addSpecialCriteria(F::CATEGORY_SPECIAL_NOGAMEDATA); + addSpecialCriteria(F::Checked); + addSpecialCriteria(F::UpdateAvailable); + addSpecialCriteria(F::Backup); + addSpecialCriteria(F::Managed); + addSpecialCriteria(F::HasNoCategory); + addSpecialCriteria(F::Conflict); + addSpecialCriteria(F::NotEndorsed); + addSpecialCriteria(F::NoNexusID); + addSpecialCriteria(F::NoGameData); addContentCriteria(); @@ -211,7 +213,7 @@ void FilterList::setSelection(std::vector categories) continue; } - if (item->id() == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + if (item->id() == CategoryFactory::UpdateAvailable) { ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); break; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 03d61bc6..0ad57803 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4126,11 +4126,11 @@ void MainWindow::checkModsForUpdates() if (updatesAvailable || checkingModsForUpdate) { m_ModListSortProxy->setCriteria({{ ModListSortProxy::TYPE_SPECIAL, - CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, + CategoryFactory::UpdateAvailable, false} }); - m_Filters->setSelection({CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE}); + m_Filters->setSelection({CategoryFactory::UpdateAvailable}); } } diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 7cc7dca4..6c35d239 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -39,6 +39,9 @@ Qt::Horizontal + + false + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 3bb02c0f..36dcae59 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -61,7 +61,7 @@ void ModListSortProxy::setCriteria(const std::vector& criteria) const bool changed = (criteria != m_Criteria); const bool isForUpdates = ( !criteria.empty() && - criteria[0].id == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE); + criteria[0].id == CategoryFactory::UpdateAvailable); if (changed || isForUpdates) { m_Criteria = criteria; @@ -343,68 +343,56 @@ bool ModListSortProxy::categoryMatchesMod( switch (category) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: + case CategoryFactory::Checked: { b = (enabled || info->alwaysEnabled()); break; } - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: - { - b = (!enabled && !info->alwaysEnabled()); - break; - } - - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: + case CategoryFactory::UpdateAvailable: { b = (info->updateAvailable() || info->downgradeAvailable()); break; } - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: + case CategoryFactory::HasNoCategory: { b = (info->getCategories().size() == 0); break; } - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: + case CategoryFactory::Conflict: { b = (hasConflictFlag(info->getFlags())); break; } - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: + case CategoryFactory::NotEndorsed: { ModInfo::EEndorsedState state = info->endorsedState(); b = (state != ModInfo::ENDORSED_TRUE); break; } - case CategoryFactory::CATEGORY_SPECIAL_BACKUP: + case CategoryFactory::Backup: { b = (info->hasFlag(ModInfo::FLAG_BACKUP)); break; } - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: + case CategoryFactory::Managed: { b = (!info->hasFlag(ModInfo::FLAG_FOREIGN)); break; } - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: - { - b = (info->hasFlag(ModInfo::FLAG_FOREIGN)); - break; - } - - case CategoryFactory::CATEGORY_SPECIAL_NOGAMEDATA: + case CategoryFactory::NoGameData: { b = (info->hasFlag(ModInfo::FLAG_INVALID)); break; } - case CategoryFactory::CATEGORY_SPECIAL_NONEXUSID: + case CategoryFactory::NoNexusID: { b = ( info->getNexusID() == -1 && -- cgit v1.3.1 From 507d29a0fc0765f0f8de49795eb100a258970d01 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 03:29:54 -0500 Subject: fixed bad label for content categories --- src/mainwindow.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0ad57803..420242ad 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6126,9 +6126,15 @@ void MainWindow::onFiltersCriteria(const std::vector label = ""; } else if (criteria.size() == 1) { const auto& c = criteria[0]; - label = m_CategoryFactory.getCategoryNameByID(c.id); + + if (c.type == ModListSortProxy::TYPE_CONTENT) { + label = ModInfo::getContentTypeName(c.id); + } else { + label = m_CategoryFactory.getCategoryNameByID(c.id); + } + if (label.isEmpty()) { - log::error("category '{}' not found", c.id); + log::error("category {}:{} not found", c.type, c.id); } } else { label = tr(""); -- cgit v1.3.1 From f080e2e25d05eb639cc4168d3c6905041f4dc564 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 03:39:12 -0500 Subject: always enable clear filter button, made it so it also removes not flags removed "deselect filters" context menu, redundant disable not flag menu items without selection --- src/filterlist.cpp | 30 ++++++++++++++++++++++++------ src/filterlist.h | 3 ++- src/mainwindow.ui | 6 ------ 3 files changed, 26 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 36cdacd0..66a9aed0 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -78,7 +78,7 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) connect( ui->filtersClear, &QPushButton::clicked, - [&]{ clearSelection(); }); + [&]{ clear(); }); connect( ui->filtersAnd, &QCheckBox::toggled, @@ -241,20 +241,23 @@ void FilterList::onSelection() }); } - ui->filtersClear->setEnabled(!criteria.empty()); - emit criteriaChanged(criteria); } void FilterList::onContextMenu(const QPoint &pos) { QMenu menu; - menu.addAction(tr("Deselect filters"), [&]{ clearSelection(); }); - menu.addAction(tr("Set inverted"), [&]{ toggleInverted(true); }); - menu.addAction(tr("Unset inverted"), [&]{ toggleInverted(false); }); + + QAction* set = menu.addAction(tr("Set inverted"), [&]{ toggleInverted(true); }); + QAction* unset = menu.addAction(tr("Unset inverted"), [&]{ toggleInverted(false); }); menu.addSeparator(); menu.addAction(tr("Edit Categories..."), [&]{ editCategories(); }); + if (ui->filters->selectedItems().empty()) { + set->setEnabled(false); + unset->setEnabled(false); + } + menu.exec(ui->filters->viewport()->mapToGlobal(pos)); } @@ -267,6 +270,21 @@ void FilterList::editCategories() } } +void FilterList::clear() +{ + const auto count = ui->filters->topLevelItemCount(); + for (int i=0; i(ui->filters->topLevelItem(i)); + if (!ci) { + continue; + } + + ci->setInverted(false); + } + + clearSelection(); +} + void FilterList::toggleInverted(bool b) { bool changed = false; diff --git a/src/filterlist.h b/src/filterlist.h index 1fd0942a..e98f81a9 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -32,8 +32,9 @@ private: void onSelection(); void onCriteriaChanged(); - void editCategories(); + void clear(); void toggleInverted(bool b); + void editCategories(); QTreeWidgetItem* addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6c35d239..5206d797 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -114,9 +114,6 @@ - - false - 0 @@ -132,9 +129,6 @@ Clear - - true - -- cgit v1.3.1 From 38d56ef8310674bc5a2f8b304ee17a6b7e1c5798 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 04:04:40 -0500 Subject: fixed setting selection when checking for updates --- src/filterlist.cpp | 10 ++++++---- src/filterlist.h | 2 +- src/mainwindow.cpp | 6 +++++- 3 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 66a9aed0..81f8b670 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -203,7 +203,7 @@ void FilterList::refresh() } } -void FilterList::setSelection(std::vector categories) +void FilterList::setSelection(const std::vector& criteria) { for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { const auto* item = dynamic_cast( @@ -213,9 +213,11 @@ void FilterList::setSelection(std::vector categories) continue; } - if (item->id() == CategoryFactory::UpdateAvailable) { - ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); - break; + for (auto&& c : criteria) { + if (item->type() == c.type && item->id() == c.id) { + ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); + break; + } } } } diff --git a/src/filterlist.h b/src/filterlist.h index e98f81a9..52b90ea7 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -14,7 +14,7 @@ class FilterList : public QObject public: FilterList(Ui::MainWindow* ui, CategoryFactory& factory); - void setSelection(std::vector categories); + void setSelection(const std::vector& criteria); void clearSelection(); void refresh(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 420242ad..096ea076 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4130,7 +4130,11 @@ void MainWindow::checkModsForUpdates() false} }); - m_Filters->setSelection({CategoryFactory::UpdateAvailable}); + m_Filters->setSelection({{ + ModListSortProxy::TYPE_SPECIAL, + CategoryFactory::UpdateAvailable, + false + }}); } } -- cgit v1.3.1 From 7f4fce35f97f262c36e4c00dad55c1b078cf3758 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 05:23:22 -0500 Subject: fixed separators option being used even without filters changed filter list to use tristate items without selection --- src/filterlist.cpp | 232 ++++++++++++++++++++++++++++------------------- src/filterlist.h | 8 +- src/mainwindow.ui | 64 +++++++------ src/modlistsortproxy.cpp | 26 +++++- src/modlistsortproxy.h | 3 +- 5 files changed, 204 insertions(+), 129 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 81f8b670..0dee8544 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -11,28 +11,23 @@ using Criteria = ModListSortProxy::Criteria; class FilterList::CriteriaItem : public QTreeWidgetItem { public: - CriteriaItem(FilterList* list, QString name, CriteriaType type, int id) - : QTreeWidgetItem({name}), m_list(list), m_widget(nullptr), m_checkbox(nullptr) + enum States : int { - setData(0, Qt::ToolTipRole, name); - setData(0, TypeRole, type); - setData(0, IDRole, id); - - m_widget = new QWidget; - m_widget->setStyleSheet("background-color: rgba(0,0,0,0)"); + FirstState = 0, - auto* ly = new QVBoxLayout(m_widget); - ly->setAlignment(Qt::AlignCenter); - ly->setContentsMargins(0, 0, 0, 0); + Inactive = FirstState, + Active, + Inverted, - m_checkbox = new QCheckBox; - QObject::connect(m_checkbox, &QCheckBox::toggled, [&]{ m_list->onSelection(); }); - ly->addWidget(m_checkbox); - } + LastState = Inverted + }; - QWidget* widget() + CriteriaItem(FilterList* list, QString name, CriteriaType type, int id) + : QTreeWidgetItem({"", name}), m_list(list), m_state(Inactive) { - return m_widget; + setData(0, Qt::ToolTipRole, name); + setData(0, TypeRole, type); + setData(0, IDRole, id); } CriteriaType type() const @@ -45,14 +40,37 @@ public: return data(0, IDRole).toInt(); } - bool inverse() const + States state() const + { + return m_state; + } + + void setState(States s) { - return m_checkbox->isChecked(); + if (m_state != s) { + m_state = s; + updateState(); + } } - void setInverted(bool b) + void nextState() { - m_checkbox->setChecked(b); + m_state = static_cast(m_state + 1); + if (m_state > LastState) { + m_state = FirstState; + } + + updateState(); + } + + void previousState() + { + m_state = static_cast(m_state - 1); + if (m_state < FirstState) { + m_state = LastState; + } + + updateState(); } private: @@ -60,40 +78,91 @@ private: const int TypeRole = Qt::UserRole + 1; FilterList* m_list; - QWidget* m_widget; - QCheckBox* m_checkbox; + States m_state; + + void updateState() + { + QString s; + + switch (m_state) + { + case Inactive: + { + break; + } + + case Active: + { + // U+2713 CHECK MARK + s = QString::fromUtf8("\xe2\x9c\x93"); + break; + } + + case Inverted: + { + s = tr("Not"); + break; + } + } + + setText(0, s); + } +}; + + +class ClickFilter : public QObject +{ +public: + ClickFilter(std::function f) + : m_f(std::move(f)) + { + } + + bool eventFilter(QObject* o, QEvent* e) override + { + if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { + if (m_f) { + return m_f(static_cast(e)); + } + } + + return QObject::eventFilter(o, e);; + } + +private: + std::function m_f; }; FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) : ui(ui), m_factory(factory) { - connect( - ui->filters, &QTreeWidget::customContextMenuRequested, - [&](auto&& pos){ onContextMenu(pos); }); + ui->filters->viewport()->installEventFilter( + new ClickFilter([&](auto* e){ return onClick(e); })); connect( - ui->filters, &QTreeWidget::itemSelectionChanged, - [&]{ onSelection(); }); + ui->filtersClear, &QPushButton::clicked, + [&]{ clearSelection(); }); connect( - ui->filtersClear, &QPushButton::clicked, - [&]{ clear(); }); + ui->filtersEdit, &QPushButton::clicked, + [&]{ editCategories(); }); connect( ui->filtersAnd, &QCheckBox::toggled, - [&]{ onCriteriaChanged(); }); + [&]{ onOptionsChanged(); }); connect( ui->filtersOr, &QCheckBox::toggled, - [&]{ onCriteriaChanged(); }); + [&]{ onOptionsChanged(); }); connect( ui->filtersSeparators, &QCheckBox::toggled, - [&]{ onCriteriaChanged(); }); + [&]{ onOptionsChanged(); }); - ui->filters->header()->setSectionResizeMode(0, QHeaderView::Stretch); - ui->filters->header()->resizeSection(1, 50); + ui->filters->header()->setMinimumSectionSize(0); + ui->filters->header()->setSectionResizeMode(0, QHeaderView::Fixed); + ui->filters->header()->resizeSection(0, 30); ui->categoriesSplitter->setCollapsible(0, false); ui->categoriesSplitter->setCollapsible(1, false); } @@ -110,7 +179,7 @@ QTreeWidgetItem* FilterList::addCriteriaItem( ui->filters->addTopLevelItem(item); } - ui->filters->setItemWidget(item, 1, item->widget()); + item->setTextAlignment(0, Qt::AlignCenter); return item; } @@ -224,91 +293,72 @@ void FilterList::setSelection(const std::vector& criteria) void FilterList::clearSelection() { - ui->filters->clearSelection(); -} - -void FilterList::onSelection() -{ - const QModelIndexList indices = ui->filters->selectionModel()->selectedRows(); - std::vector criteria; - - for (auto* item : ui->filters->selectedItems()) { - const auto* ci = dynamic_cast(item); + for (int i=0; ifilters->topLevelItemCount(); ++i) { + auto* ci = dynamic_cast(ui->filters->topLevelItem(i)); if (!ci) { continue; } - criteria.push_back({ - ci->type(), ci->id(), ci->inverse() - }); + ci->setState(CriteriaItem::Inactive); } - emit criteriaChanged(criteria); + checkCriteria(); } -void FilterList::onContextMenu(const QPoint &pos) +bool FilterList::onClick(QMouseEvent* e) { - QMenu menu; + auto* item = ui->filters->itemAt(e->pos()); + if (!item) { + return false; + } - QAction* set = menu.addAction(tr("Set inverted"), [&]{ toggleInverted(true); }); - QAction* unset = menu.addAction(tr("Unset inverted"), [&]{ toggleInverted(false); }); - menu.addSeparator(); - menu.addAction(tr("Edit Categories..."), [&]{ editCategories(); }); + auto* ci = dynamic_cast(item); + if (!ci) { + return false; + } - if (ui->filters->selectedItems().empty()) { - set->setEnabled(false); - unset->setEnabled(false); + if (e->button() == Qt::LeftButton) { + ci->nextState(); + } else if (e->button() == Qt::RightButton) { + ci->previousState(); + } else { + return false; } - menu.exec(ui->filters->viewport()->mapToGlobal(pos)); + checkCriteria(); + return true; } -void FilterList::editCategories() +void FilterList::checkCriteria() { - CategoriesDialog dialog(qApp->activeWindow()); - - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} + std::vector criteria; -void FilterList::clear() -{ - const auto count = ui->filters->topLevelItemCount(); - for (int i=0; i(ui->filters->topLevelItem(i)); + for (int i=0; ifilters->topLevelItemCount(); ++i) { + const auto* ci = dynamic_cast(ui->filters->topLevelItem(i)); if (!ci) { continue; } - ci->setInverted(false); + if (ci->state() != CriteriaItem::Inactive) { + criteria.push_back({ + ci->type(), ci->id(), (ci->state() == CriteriaItem::Inverted) + }); + } } - clearSelection(); + emit criteriaChanged(criteria); } -void FilterList::toggleInverted(bool b) +void FilterList::editCategories() { - bool changed = false; - - for (auto* item : ui->filters->selectedItems()) { - auto* ci = dynamic_cast(item); - if (!ci) { - continue; - } - - if (ci->inverse() != b) { - ci->setInverted(b); - changed = true; - } - } + CategoriesDialog dialog(qApp->activeWindow()); - if (changed) { - onSelection(); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); } } -void FilterList::onCriteriaChanged() +void FilterList::onOptionsChanged() { const auto mode = ui->filtersAnd->isChecked() ? ModListSortProxy::FILTER_AND : ModListSortProxy::FILTER_OR; diff --git a/src/filterlist.h b/src/filterlist.h index 52b90ea7..fac1d683 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -28,13 +28,11 @@ private: Ui::MainWindow* ui; CategoryFactory& m_factory; - void onContextMenu(const QPoint &pos); - void onSelection(); - void onCriteriaChanged(); + bool onClick(QMouseEvent* e); + void onOptionsChanged(); - void clear(); - void toggleInverted(bool b); void editCategories(); + void checkCriteria(); QTreeWidgetItem* addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5206d797..1a64dfdd 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -76,59 +76,69 @@ 0 - - Qt::CustomContextMenu - - QAbstractItemView::ExtendedSelection + QAbstractItemView::NoSelection 0 + + false + true - false + true - true + false - true - - false - Category + - Invert + Category - - - - 0 - 0 - - - - - 0 - 25 - - - - Clear - + + + + 0 + + + 2 + + + 0 + + + 0 + + + + + Clear + + + + + + + Edit... + + + + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 36dcae59..e6bed49c 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -269,12 +269,12 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { - if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + if (!optionsMatchMod(info, enabled)) { return false; } for (auto&& c : m_Criteria) { - if (!criteriaMatchesMod(info, enabled, c)) { + if (!criteriaMatchMod(info, enabled, c)) { return false; } } @@ -284,12 +284,12 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { - if (info->hasFlag(ModInfo::FLAG_SEPARATOR) && !m_FilterSeparators) { + if (!optionsMatchMod(info, enabled)) { return false; } for (auto&& c : m_Criteria) { - if (criteriaMatchesMod(info, enabled, c)) { + if (criteriaMatchMod(info, enabled, c)) { return true; } } @@ -302,7 +302,23 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const return true; } -bool ModListSortProxy::criteriaMatchesMod( +bool ModListSortProxy::optionsMatchMod(ModInfo::Ptr info, bool) const +{ + // don't check options if there are no filters selected + if (!m_FilterActive) { + return true; + } + + if (!m_FilterSeparators) { + if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) { + return false; + } + } + + return true; +} + +bool ModListSortProxy::criteriaMatchMod( ModInfo::Ptr info, bool enabled, const Criteria& c) const { bool b = false; diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 5aeaccce..9b533492 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -157,7 +157,8 @@ private: std::vector m_PreChangeCriteria; - bool criteriaMatchesMod(ModInfo::Ptr info, bool enabled, const Criteria& c) const; + bool optionsMatchMod(ModInfo::Ptr info, bool enabled) const; + bool criteriaMatchMod(ModInfo::Ptr info, bool enabled, const Criteria& c) const; bool categoryMatchesMod(ModInfo::Ptr info, bool enabled, int category) const; bool contentMatchesMod(ModInfo::Ptr info, bool enabled, int content) const; }; -- cgit v1.3.1 From d2073ef2bd62527034864fd0cacd5537aff33218 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 30 Nov 2019 05:37:34 -0500 Subject: made all categories positive fixed context menu sometimes appearing --- src/categories.cpp | 16 ++++++++-------- src/categories.h | 8 ++++---- src/filterlist.cpp | 10 +++++----- src/mainwindow.ui | 3 +++ src/modlistsortproxy.cpp | 29 ++++++++++++++++------------- 5 files changed, 36 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/categories.cpp b/src/categories.cpp index 1bd56f7f..5c9a4d55 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -324,15 +324,15 @@ QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const { switch (type) { - case Checked: return QObject::tr(""); - case UpdateAvailable: return QObject::tr(""); - case HasNoCategory: return QObject::tr(""); + case Checked: return QObject::tr(""); + case UpdateAvailable: return QObject::tr(""); + case HasCategory: return QObject::tr(""); case Conflict: return QObject::tr(""); - case NotEndorsed: return QObject::tr(""); - case Backup: return QObject::tr(""); - case Managed: return QObject::tr(""); - case NoGameData: return QObject::tr(""); - case NoNexusID: return QObject::tr(""); + case Endorsed: return QObject::tr(""); + case Backup: return QObject::tr(""); + case Managed: return QObject::tr(""); + case HasGameData: return QObject::tr(""); + case HasNexusID: return QObject::tr(""); default: return {}; } } diff --git a/src/categories.h b/src/categories.h index 296e7711..02695e4d 100644 --- a/src/categories.h +++ b/src/categories.h @@ -41,13 +41,13 @@ public: { Checked = 10000, UpdateAvailable, - HasNoCategory, + HasCategory, Conflict, - NotEndorsed, + Endorsed, Backup, Managed, - NoGameData, - NoNexusID + HasGameData, + HasNexusID }; public: diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 0dee8544..b65f0f4a 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -11,7 +11,7 @@ using Criteria = ModListSortProxy::Criteria; class FilterList::CriteriaItem : public QTreeWidgetItem { public: - enum States : int + enum States { FirstState = 0, @@ -234,11 +234,11 @@ void FilterList::refresh() addSpecialCriteria(F::UpdateAvailable); addSpecialCriteria(F::Backup); addSpecialCriteria(F::Managed); - addSpecialCriteria(F::HasNoCategory); + addSpecialCriteria(F::HasCategory); addSpecialCriteria(F::Conflict); - addSpecialCriteria(F::NotEndorsed); - addSpecialCriteria(F::NoNexusID); - addSpecialCriteria(F::NoGameData); + addSpecialCriteria(F::Endorsed); + addSpecialCriteria(F::HasNexusID); + addSpecialCriteria(F::HasGameData); addContentCriteria(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 1a64dfdd..92a41c67 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -76,6 +76,9 @@ 0 + + Qt::NoContextMenu + QAbstractItemView::NoSelection diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index e6bed49c..fd3dbc9e 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -371,9 +371,9 @@ bool ModListSortProxy::categoryMatchesMod( break; } - case CategoryFactory::HasNoCategory: + case CategoryFactory::HasCategory: { - b = (info->getCategories().size() == 0); + b = !info->getCategories().empty(); break; } @@ -383,10 +383,9 @@ bool ModListSortProxy::categoryMatchesMod( break; } - case CategoryFactory::NotEndorsed: + case CategoryFactory::Endorsed: { - ModInfo::EEndorsedState state = info->endorsedState(); - b = (state != ModInfo::ENDORSED_TRUE); + b = (info->endorsedState() == ModInfo::ENDORSED_TRUE); break; } @@ -402,20 +401,24 @@ bool ModListSortProxy::categoryMatchesMod( break; } - case CategoryFactory::NoGameData: + case CategoryFactory::HasGameData: { - b = (info->hasFlag(ModInfo::FLAG_INVALID)); + b = !info->hasFlag(ModInfo::FLAG_INVALID); break; } - case CategoryFactory::NoNexusID: + case CategoryFactory::HasNexusID: { - b = ( - info->getNexusID() == -1 && - !info->hasFlag(ModInfo::FLAG_FOREIGN) && - !info->hasFlag(ModInfo::FLAG_BACKUP) && - !info->hasFlag(ModInfo::FLAG_OVERWRITE)); + // never show these + if ( + info->hasFlag(ModInfo::FLAG_FOREIGN) || + info->hasFlag(ModInfo::FLAG_BACKUP) || + info->hasFlag(ModInfo::FLAG_OVERWRITE)) + { + return false; + } + b = (info->getNexusID() > 0); break; } -- cgit v1.3.1 From 3c25117fe163f7fab7afa22ba171ea2d41112f23 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 10:41:52 -0500 Subject: three modes for separators, save state renamed enumerators --- src/filterlist.cpp | 28 ++++++++++++++++----- src/filterlist.h | 7 +++++- src/mainwindow.cpp | 16 +++++++----- src/mainwindow.h | 3 ++- src/mainwindow.ui | 21 ++++++++++++---- src/modlistsortproxy.cpp | 65 +++++++++++++++++++++++++++++------------------- src/modlistsortproxy.h | 25 ++++++++++++------- 7 files changed, 112 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index b65f0f4a..05bff2dd 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -2,6 +2,7 @@ #include "ui_mainwindow.h" #include "categories.h" #include "categoriesdialog.h" +#include "settings.h" #include using namespace MOBase; @@ -157,7 +158,7 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) [&]{ onOptionsChanged(); }); connect( - ui->filtersSeparators, &QCheckBox::toggled, + ui->filtersSeparators, qOverload(&QComboBox::currentIndexChanged), [&]{ onOptionsChanged(); }); ui->filters->header()->setMinimumSectionSize(0); @@ -165,6 +166,20 @@ FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) ui->filters->header()->resizeSection(0, 30); ui->categoriesSplitter->setCollapsible(0, false); ui->categoriesSplitter->setCollapsible(1, false); + + ui->filtersSeparators->addItem(tr("Filter separators"), ModListSortProxy::SeparatorFilter); + ui->filtersSeparators->addItem(tr("Show separators"), ModListSortProxy::SeparatorShow); + ui->filtersSeparators->addItem(tr("Hide separators"), ModListSortProxy::SeparatorHide); +} + +void FilterList::restoreState(const Settings& s) +{ + s.widgets().restoreIndex(ui->filtersSeparators); +} + +void FilterList::saveState(Settings& s) const +{ + s.widgets().saveIndex(ui->filtersSeparators); } QTreeWidgetItem* FilterList::addCriteriaItem( @@ -189,7 +204,7 @@ void FilterList::addContentCriteria() for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { addCriteriaItem( nullptr, tr("").arg(ModInfo::getContentTypeName(i)), - i, ModListSortProxy::TYPE_CONTENT); + i, ModListSortProxy::TypeContent); } } @@ -202,7 +217,7 @@ void FilterList::addCategoryCriteria(QTreeWidgetItem *root, const std::set if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { QTreeWidgetItem *item = addCriteriaItem(root, m_factory.getCategoryName(i), - categoryID, ModListSortProxy::TYPE_CATEGORY); + categoryID, ModListSortProxy::TypeCategory); if (m_factory.hasChildren(i)) { addCategoryCriteria(item, categoriesUsed, categoryID); } @@ -217,7 +232,7 @@ void FilterList::addSpecialCriteria(int type) addCriteriaItem( nullptr, m_factory.getSpecialCategoryName(sc), - type, ModListSortProxy::TYPE_SPECIAL); + type, ModListSortProxy::TypeSpecial); } void FilterList::refresh() @@ -361,9 +376,10 @@ void FilterList::editCategories() void FilterList::onOptionsChanged() { const auto mode = ui->filtersAnd->isChecked() ? - ModListSortProxy::FILTER_AND : ModListSortProxy::FILTER_OR; + ModListSortProxy::FilterAnd: ModListSortProxy::FilterOr; - const bool separators = ui->filtersSeparators->isChecked(); + const auto separators = static_cast( + ui->filtersSeparators->currentData().toInt()); emit optionsChanged(mode, separators); } diff --git a/src/filterlist.h b/src/filterlist.h index fac1d683..671462d4 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -6,6 +6,7 @@ namespace Ui { class MainWindow; }; class CategoryFactory; +class Settings; class FilterList : public QObject { @@ -14,13 +15,17 @@ class FilterList : public QObject public: FilterList(Ui::MainWindow* ui, CategoryFactory& factory); + void restoreState(const Settings& s); + void saveState(Settings& s) const; + void setSelection(const std::vector& criteria); void clearSelection(); void refresh(); signals: void criteriaChanged(std::vector criteria); - void optionsChanged(ModListSortProxy::FilterMode mode, bool separators); + void optionsChanged( + ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); private: class CriteriaItem; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 096ea076..b5af9aa5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -269,7 +269,7 @@ MainWindow::MainWindow(Settings &settings connect( m_Filters.get(), &FilterList::optionsChanged, - [&](auto mode, bool sep) { onFiltersOptions(mode, sep); }); + [&](auto&& mode, auto&& sep) { onFiltersOptions(mode, sep); }); ui->logList->setCore(m_OrganizerCore); @@ -2205,6 +2205,7 @@ void MainWindow::readSettings() } s.widgets().restoreIndex(ui->groupCombo); + m_Filters->restoreState(s); { s.geometry().restoreVisibility(ui->categoriesGroup, false); @@ -2283,6 +2284,8 @@ void MainWindow::storeSettings() s.widgets().saveIndex(ui->groupCombo); s.widgets().saveIndex(ui->executablesListBox); + + m_Filters->saveState(s); } QWidget* MainWindow::qtWidget() @@ -4125,13 +4128,13 @@ void MainWindow::checkModsForUpdates() if (updatesAvailable || checkingModsForUpdate) { m_ModListSortProxy->setCriteria({{ - ModListSortProxy::TYPE_SPECIAL, + ModListSortProxy::TypeSpecial, CategoryFactory::UpdateAvailable, false} }); m_Filters->setSelection({{ - ModListSortProxy::TYPE_SPECIAL, + ModListSortProxy::TypeSpecial, CategoryFactory::UpdateAvailable, false }}); @@ -6131,7 +6134,7 @@ void MainWindow::onFiltersCriteria(const std::vector } else if (criteria.size() == 1) { const auto& c = criteria[0]; - if (c.type == ModListSortProxy::TYPE_CONTENT) { + if (c.type == ModListSortProxy::TypeContent) { label = ModInfo::getContentTypeName(c.id); } else { label = m_CategoryFactory.getCategoryNameByID(c.id); @@ -6148,9 +6151,10 @@ void MainWindow::onFiltersCriteria(const std::vector ui->modList->reset(); } -void MainWindow::onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators) +void MainWindow::onFiltersOptions( + ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep) { - m_ModListSortProxy->setOptions(mode, separators); + m_ModListSortProxy->setOptions(mode, sep); } void MainWindow::updateESPLock(bool locked) diff --git a/src/mainwindow.h b/src/mainwindow.h index 0b559300..69aee073 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -515,7 +515,8 @@ private slots: void deselectFilters(); void refreshFilters(); void onFiltersCriteria(const std::vector& filters); - void onFiltersOptions(ModListSortProxy::FilterMode mode, bool separators); + void onFiltersOptions( + ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep); void displayModInformation(const QString &modName, ModInfoTabIDs tabID); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 92a41c67..85be22b3 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -153,6 +153,18 @@ + + 0 + + + 2 + + + 0 + + + 0 + @@ -177,12 +189,11 @@ - + - Include separators - - - Separators + Filter: only show the separators that match the current filters +Show: always show separators +Hide: never show separators diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index fd3dbc9e..7ac98f66 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -37,8 +37,8 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) : QSortFilterProxyModel(parent) , m_Profile(profile) , m_FilterActive(false) - , m_FilterMode(FILTER_AND) - , m_FilterSeparators(false) + , m_FilterMode(FilterAnd) + , m_FilterSeparators(SeparatorFilter) { setDynamicSortFilter(true); // this seems to work without dynamicsortfilter // but I don't know why. This should be necessary @@ -269,10 +269,6 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { - if (!optionsMatchMod(info, enabled)) { - return false; - } - for (auto&& c : m_Criteria) { if (!criteriaMatchMod(info, enabled, c)) { return false; @@ -284,10 +280,6 @@ bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) cons bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { - if (!optionsMatchMod(info, enabled)) { - return false; - } - for (auto&& c : m_Criteria) { if (criteriaMatchMod(info, enabled, c)) { return true; @@ -304,16 +296,6 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const bool ModListSortProxy::optionsMatchMod(ModInfo::Ptr info, bool) const { - // don't check options if there are no filters selected - if (!m_FilterActive) { - return true; - } - - if (!m_FilterSeparators) { - if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) { - return false; - } - } return true; } @@ -325,14 +307,14 @@ bool ModListSortProxy::criteriaMatchMod( switch (c.type) { - case TYPE_SPECIAL: // fall-through - case TYPE_CATEGORY: + case TypeSpecial: // fall-through + case TypeCategory: { b = categoryMatchesMod(info, enabled, c.id); break; } - case TYPE_CONTENT: + case TypeContent: { b = contentMatchesMod(info, enabled, c.id); break; @@ -439,6 +421,37 @@ bool ModListSortProxy::contentMatchesMod(ModInfo::Ptr info, bool enabled, int co bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { + // don't check if there are no filters selected + if (!m_FilterActive) { + return true; + } + + + // special case for separators + if (info->hasFlag(ModInfo::FLAG_SEPARATOR)) { + switch (m_FilterSeparators) + { + case SeparatorFilter: + { + // filter normally + break; + } + + case SeparatorShow: + { + // force visible + return true; + } + + case SeparatorHide: + { + // force hide + return false; + } + } + } + + if (!m_Filter.isEmpty()) { bool display = false; QString filterCopy = QString(m_Filter); @@ -519,7 +532,8 @@ bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const } }//if (!m_CurrentFilter.isEmpty()) - if (m_FilterMode == FILTER_AND) { + + if (m_FilterMode == FilterAnd) { return filterMatchesModAnd(info, enabled); } else { @@ -532,7 +546,8 @@ void ModListSortProxy::setColumnVisible(int column, bool visible) m_EnabledColumns[column] = visible; } -void ModListSortProxy::setOptions(ModListSortProxy::FilterMode mode, bool separators) +void ModListSortProxy::setOptions( + ModListSortProxy::FilterMode mode, SeparatorsMode separators) { if (m_FilterMode != mode || separators != m_FilterSeparators) { m_FilterMode = mode; diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 9b533492..46356fe9 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -31,16 +31,23 @@ class ModListSortProxy : public QSortFilterProxyModel Q_OBJECT public: - - enum FilterMode { - FILTER_AND, - FILTER_OR + enum FilterMode + { + FilterAnd, + FilterOr }; enum CriteriaType { - TYPE_SPECIAL, - TYPE_CATEGORY, - TYPE_CONTENT + TypeSpecial, + TypeCategory, + TypeContent + }; + + enum SeparatorsMode + { + SeparatorFilter, + SeparatorShow, + SeparatorHide }; struct Criteria @@ -101,7 +108,7 @@ public: bool isFilterActive() const { return m_FilterActive; } void setCriteria(const std::vector& criteria); - void setOptions(FilterMode mode, bool separators); + void setOptions(FilterMode mode, SeparatorsMode separators); /** * @brief tests if the specified index has child nodes @@ -153,7 +160,7 @@ private: bool m_FilterActive; FilterMode m_FilterMode; - bool m_FilterSeparators; + SeparatorsMode m_FilterSeparators; std::vector m_PreChangeCriteria; -- cgit v1.3.1 From 2b4d7929769a91d9de30e1e10319d1c9cf0450af Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 11:06:10 -0500 Subject: don't show the lock overlay for processes that are not hooked starting exe from filetree is now hooked usvfs progress dialog now on top of dialogs --- src/mainwindow.ui | 3 --- src/modinfodialogfiletree.cpp | 5 ++++- src/processrunner.cpp | 9 +++++++++ src/processrunner.h | 5 +++-- src/usvfsconnector.cpp | 3 ++- 5 files changed, 18 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 85be22b3..0520db84 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1073,9 +1073,6 @@ p, li { white-space: pre-wrap; } true - - true - 400 diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 79ed4cba..00471a72 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -140,7 +140,10 @@ void FileTreeTab::onOpen() return; } - shell::Open(m_fs->filePath(selection)); + core().processRunner() + .setFromFile(parentWidget(), m_fs->filePath(selection)) + .setWaitForCompletion() + .run(); } void FileTreeTab::onPreview() diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 89777072..b6167706 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -498,6 +498,10 @@ ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targ default: { m_shellOpen = targetInfo.absoluteFilePath(); + + // picked up by postRun() + m_sp.hooked = false; + break; } } @@ -722,6 +726,11 @@ ProcessRunner::Results ProcessRunner::postRun() { const bool mustWait = (m_waitFlags & ForceWait); + if (!m_sp.hooked && !mustWait) { + // the process wasn't hooked and there's no force wait, don't lock + return Running; + } + if (mustWait && m_lockReason == UILocker::NoReason) { // never lock the ui without an escape hatch for the user log::debug( diff --git a/src/processrunner.h b/src/processrunner.h index c61d6b70..a5099136 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -42,8 +42,9 @@ public: // the ui will be refreshed once the process has completed Refresh = 0x01, - // the process will be waited for even if locking is disabled - ForceWait = 0x02 + // the process will be waited for even if locking is disabled or the + // process is not hooked + ForceWait = 0x02, }; using WaitFlags = QFlags; diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 3c8c355b..59880754 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -181,10 +181,11 @@ UsvfsConnector::~UsvfsConnector() void UsvfsConnector::updateMapping(const MappingType &mapping) { - QProgressDialog progress; + QProgressDialog progress(qApp->activeWindow()); progress.setLabelText(tr("Preparing vfs")); progress.setMaximum(static_cast(mapping.size())); progress.show(); + int value = 0; int files = 0; int dirs = 0; -- cgit v1.3.1 From d4172dc5f8c642dbbe235a86a28992af310e703a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 13:51:46 -0500 Subject: added "open with vfs" option to conflicts tab --- src/env.cpp | 157 +++++++++++++++++++++++++++++++++++++++++ src/env.h | 20 ++++++ src/modinfodialog.cpp | 23 +++++- src/modinfodialogconflicts.cpp | 39 +++++++++- src/modinfodialogconflicts.h | 2 + src/modinfodialogfwd.h | 1 + src/processrunner.cpp | 20 +++++- src/processrunner.h | 12 ++-- 8 files changed, 263 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index f9507dc1..0098456e 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -675,6 +675,163 @@ Service getService(const QString& name) } +std::optional getAssocString(const QFileInfo& file, ASSOCSTR astr) +{ + const auto ext = L"." + file.suffix().toStdWString(); + + // getting buffer size + DWORD bufferSize = 0; + auto r = AssocQueryStringW( + ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", nullptr, &bufferSize); + + // returns S_FALSE when giving back the buffer size, so that's actually the + // expected return value + + if (r != S_FALSE || bufferSize == 0) { + if (r == HRESULT_FROM_WIN32(ERROR_NO_ASSOCIATION)) { + log::error("file '{}' has no associated executable", file.absoluteFilePath()); + } else { + log::error( + "can't get buffer size for AssocQueryStringW(), {}", + formatSystemMessage(r)); + } + return {}; + } + + // getting string + auto buffer = std::make_unique(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + r = AssocQueryStringW( + ASSOCF_INIT_IGNOREUNKNOWN, astr, ext.c_str(), L"open", buffer.get(), &bufferSize); + + if (FAILED(r)) { + log::error( + "failed to get exe associated with '{}', {}", + file.suffix(), formatSystemMessage(r)); + + return {}; + } + + // buffer size includes the null terminator + return QString::fromWCharArray(buffer.get(), bufferSize - 1); +} + +QString formatCommandLine(const QFileInfo& targetInfo, const QString& cmd) +{ + // yeah, FormatMessage() expects at least as many arguments as there are + // placeholders and while the command for associations should typically only + // have %1, the user can actually enter anything in the registry + // + // since the maximum number of arguments is 99, this creates an array of 99 + // wchar_* where the first one (%1) points to the filename and the remaining + // 98 to "" + // + // FormatMessage() actually takes a va_list* for the arguments, but by passing + // FORMAT_MESSAGE_ARGUMENT_ARRAY, an array of DWORD_PTR can be given instead + + // 99 arguments + std::array args; + + // first one is the filename + const auto wpath = targetInfo.absoluteFilePath().toStdWString(); + args[0] = reinterpret_cast(wpath.c_str()); + + // remaining are "" + std::fill(args.begin() + 1, args.end(), reinterpret_cast(L"")); + + // must be freed with LocalFree() + wchar_t* buffer = nullptr; + + const auto wcmd = cmd.toStdWString(); + + const auto n = ::FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_FROM_STRING, + wcmd.c_str(), 0, 0, + reinterpret_cast(&buffer), + 0, reinterpret_cast(&args[0])); + + if (n == 0 || !buffer){ + const auto e = GetLastError(); + + log::error( + "failed to format command line '{}' with path '{}', {}", + cmd, targetInfo.absoluteFilePath(), formatSystemMessage(e)); + + return {}; + } + + auto s = QString::fromWCharArray(buffer, n); + ::LocalFree(buffer); + + return s.trimmed(); +} + +std::pair splitExeAndArguments(const QString& cmd) +{ + int exeBegin = 0; + int exeEnd = -1; + + if (cmd[0] == '"'){ + // surrounded by double-quotes, so find the next one + exeBegin = 1; + exeEnd = cmd.indexOf('"', exeBegin); + + if (exeEnd == -1) { + log::error("missing terminating double-quote in command line '{}'", cmd); + return {}; + } + } else { + // no double-quotes, find the first whitespace + exeEnd = cmd.indexOf(QRegExp("\\s")); + if (exeEnd == -1) { + exeEnd = cmd.size(); + } + } + + QString exe = cmd.mid(exeBegin, exeEnd - exeBegin).trimmed(); + QString args = cmd.mid(exeEnd + 1).trimmed(); + + return {std::move(exe), std::move(args)}; +} + +Association getAssociation(const QFileInfo& targetInfo) +{ + log::debug( + "getting association for '{}', extension is '.{}'", + targetInfo.absoluteFilePath(), targetInfo.suffix()); + + const auto cmd = getAssocString(targetInfo, ASSOCSTR_COMMAND); + if (!cmd) { + return {}; + } + + log::debug("raw cmd is '{}'", *cmd); + + QString formattedCmd = formatCommandLine(targetInfo, *cmd); + if (formattedCmd.isEmpty()) { + log::error( + "command line associated with '{}' is empty", + targetInfo.absoluteFilePath()); + + return {}; + } + + log::debug("formatted cmd is '{}'", formattedCmd); + + const auto p = splitExeAndArguments(formattedCmd); + if (p.first.isEmpty()) { + return {}; + } + + log::debug("split into exe='{}' and cmd='{}'", p.first, p.second); + + return {p.first, *cmd, p.second}; +} + + // returns the filename of the given process or the current one // std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) diff --git a/src/env.h b/src/env.h index dc0fd864..16f8039e 100644 --- a/src/env.h +++ b/src/env.h @@ -246,6 +246,26 @@ Service getService(const QString& name); QString toString(Service::StartType st); QString toString(Service::Status st); + +struct Association +{ + // path to the executable associated with the file + QFileInfo executable; + + // full command line associated with the file, no replacements + QString commandLine; + + // command line _without_ the executable and with placeholders such as %1 + // replaced by the given file + QString formattedCommandLine; +}; + +// returns the associated executable and command line, executable is empty on +// error +// +Association getAssociation(const QFileInfo& file); + + enum class CoreDumpTypes { Mini = 1, diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c7e071ad..f5ca1de7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -61,10 +61,27 @@ bool canPreviewFile( return pluginContainer.previewGenerator().previewSupported(ext); } -bool canOpenFile(bool isArchive, const QString&) +bool isExecutableFilename(const QString& filename) { - // can open anything as long as it's not in an archive - return !isArchive; + static const std::set exeExtensions = { + "exe", "cmd", "bat" + }; + + const auto ext = QFileInfo(filename).suffix().toLower(); + + return exeExtensions.contains(ext); +} + +bool canRunFile(bool isArchive, const QString& filename) +{ + // can run executables that are not archives + return !isArchive && isExecutableFilename(filename); +} + +bool canOpenFile(bool isArchive, const QString& filename) +{ + // can open non-executables that are not archives + return !isArchive && !isExecutableFilename(filename); } bool canExploreFile(bool isArchive, const QString&) diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d37f068c..58e935f2 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -81,6 +81,11 @@ public: return canUnhideFile(isArchive(), fileName()); } + bool canRun() const + { + return canRunFile(isArchive(), fileName()); + } + bool canOpen() const { return canOpenFile(isArchive(), fileName()); @@ -536,6 +541,20 @@ void ConflictsTab::openItems(QTreeView* tree) }); } +void ConflictsTab::runItemsHooked(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + core().processRunner() + .setFromFile(parentWidget(), item->fileName(), true) + .setWaitForCompletion() + .run(); + + return true; + }); +} + void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -571,6 +590,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.open); } + // run hooked + if (actions.runHooked) { + connect(actions.runHooked, &QAction::triggered, [&]{ + runItemsHooked(tree); + }); + + menu.addAction(actions.runHooked); + } + // preview if (actions.preview) { connect(actions.preview, &QAction::triggered, [&]{ @@ -633,6 +661,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) bool enableHide = true; bool enableUnhide = true; + bool enableRun = true; bool enableOpen = true; bool enablePreview = true; bool enableExplore = true; @@ -657,6 +686,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableHide = item->canHide(); enableUnhide = item->canUnhide(); + enableRun = item->canRun(); enableOpen = item->canOpen(); enablePreview = item->canPreview(plugin()); enableExplore = item->canExplore(); @@ -665,6 +695,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) else { // this is a multiple selection, don't show open/preview so users don't open // a thousand files + enableRun = false; enableOpen = false; enablePreview = false; @@ -709,8 +740,12 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) actions.unhide = new QAction(tr("&Unhide"), parentWidget()); actions.unhide->setEnabled(enableUnhide); - actions.open = new QAction(tr("&Open/Execute"), parentWidget()); - actions.open->setEnabled(enableOpen); + if (enableRun) { + actions.open = new QAction(tr("&Execute"), parentWidget()); + } else if (enableOpen) { + actions.open = new QAction(tr("&Open"), parentWidget()); + actions.runHooked = new QAction(tr("Open with &VFS"), parentWidget()); + } actions.preview = new QAction(tr("&Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index ad305dfc..ebf82033 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -112,6 +112,7 @@ public: bool canHandleUnmanaged() const override; void openItems(QTreeView* tree); + void runItemsHooked(QTreeView* tree); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); @@ -125,6 +126,7 @@ private: QAction* hide = nullptr; QAction* unhide = nullptr; QAction* open = nullptr; + QAction* runHooked = nullptr; QAction* preview = nullptr; QAction* explore = nullptr; QMenu* gotoMenu = nullptr; diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 9ede766f..2147fc04 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -23,6 +23,7 @@ enum class ModInfoTabIDs class PluginContainer; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); +bool canRunFile(bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); bool canExploreFile(bool isArchive, const QString& filename); bool canHideFile(bool isArchive, const QString& filename); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index b6167706..46065d69 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -3,6 +3,8 @@ #include "instancemanager.h" #include "iuserinterface.h" #include "envmodule.h" +#include "env.h" +#include #include using namespace MOBase; @@ -473,13 +475,14 @@ ProcessRunner& ProcessRunner::setWaitForCompletion( return *this; } -ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targetInfo) +ProcessRunner& ProcessRunner::setFromFile( + QWidget* parent, const QFileInfo& targetInfo, bool forceHook) { if (!parent && m_ui) { parent = m_ui->qtWidget(); } - // if the file is a .exe, start it directory; if it's anything else, ask the + // if the file is a .exe, start it directly; if it's anything else, ask the // shell to start it const auto fec = spawn::getFileExecutionContext(parent, targetInfo); @@ -497,6 +500,19 @@ ProcessRunner& ProcessRunner::setFromFile(QWidget* parent, const QFileInfo& targ case spawn::FileExecutionTypes::Other: // fall-through default: { + if (forceHook) { + auto assoc = env::getAssociation(targetInfo); + if (!assoc.executable.filePath().isEmpty()) { + setBinary(assoc.executable); + setArguments(assoc.formattedCommandLine); + setCurrentDirectory(assoc.executable.absoluteDir()); + + return *this; + } + + // if it fails, just use the regular shell open + } + m_shellOpen = targetInfo.absoluteFilePath(); // picked up by postRun() diff --git a/src/processrunner.h b/src/processrunner.h index a5099136..d576216a 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -68,10 +68,14 @@ public: ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); - // if the target is an executable file, runs that; for anything else, calls - // ShellExecute() on it - // - ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); + // - if the target is an executable file, runs it hooked + // - if the target is a file: + // - if forceHook is false, calls ShellExecute() on it + // - if forceHook is true, gets the executable associated with the file + // and runs that hooked by passing the file as an argument + // + ProcessRunner& setFromFile( + QWidget* parent, const QFileInfo& targetInfo, bool forceHook = false); ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); -- cgit v1.3.1 From 47b767ef2fd1071a065e546c543805f490ab3e2d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 14:36:09 -0500 Subject: added "open with vfs" to filetree --- src/modinfodialogconflicts.h | 4 -- src/modinfodialogfiletree.cpp | 115 ++++++++++++++++++++++-------------------- src/modinfodialogfiletree.h | 18 ++++--- 3 files changed, 69 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index ebf82033..8baa62b6 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -60,10 +60,6 @@ private: void onOverwriteActivated(const QModelIndex& index); void onOverwrittenActivated(const QModelIndex& index); - - void onOverwriteTreeContext(const QPoint &pos); - void onOverwrittenTreeContext(const QPoint &pos); - void onNoConflictTreeContext(const QPoint &pos); }; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 00471a72..23d65fdb 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -24,6 +24,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); + m_actions.runHooked = new QAction(tr("Open with &VFS"), ui->filetree); m_actions.preview = new QAction(tr("&Preview"), ui->filetree); m_actions.explore = new QAction(tr("Open in &Explorer"), ui->filetree); m_actions.rename = new QAction(tr("&Rename"), ui->filetree); @@ -33,6 +34,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); + connect(m_actions.runHooked, &QAction::triggered, [&]{ onRunHooked(); }); connect(m_actions.preview, &QAction::triggered, [&]{ onPreview(); }); connect(m_actions.explore, &QAction::triggered, [&]{ onExplore(); }); connect(m_actions.rename, &QAction::triggered, [&]{ onRename(); }); @@ -146,6 +148,19 @@ void FileTreeTab::onOpen() .run(); } +void FileTreeTab::onRunHooked() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + core().processRunner() + .setFromFile(parentWidget(), m_fs->filePath(selection), true) + .setWaitForCompletion() + .run(); +} + void FileTreeTab::onPreview() { auto selection = singleSelection(); @@ -342,75 +357,53 @@ void FileTreeTab::onContextMenu(const QPoint &pos) QMenu menu(ui->filetree); - bool enableNewFolder = true; - bool enableOpen = true; - bool enablePreview = true; - bool enableExplore = true; - bool enableRename = true; - bool enableDelete = true; - bool enableHide = true; - bool enableUnhide = true; + bool enableNewFolder = false; + bool enableRun = false; + bool enableOpen = false; + bool enablePreview = false; + bool enableExplore = false; + bool enableRename = false; + bool enableDelete = false; + bool enableHide = false; + bool enableUnhide = false; if (selection.size() == 0) { // no selection, only new folder and explore - enableOpen = false; - enablePreview = false; - enableRename = false; - enableDelete = false; - enableHide = false; - enableUnhide = false; + enableNewFolder = true; + enableExplore = true; } else if (selection.size() == 1) { // single selection + enableNewFolder = true; + enableRename = true; + enableDelete = true; // only enable open action if a file is selected bool hasFiles = false; - for (auto index : selection) { - if (m_fs->fileInfo(index).isFile()) { - hasFiles = true; - break; - } - } - - if (!hasFiles) { - enableOpen = false; - enablePreview = false; - } - const QString fileName = m_fs->fileName(selection[0]); - if (!canPreviewFile(plugin(), false, fileName)) { - enablePreview = false; - } - - if (!canExploreFile(false, fileName)) { - enableExplore = false; - } - - if (!canHideFile(false, fileName)) { - enableHide = false; + if (m_fs->fileInfo(selection[0]).isFile()) { + if (canRunFile(false, fileName)) { + enableRun = true; + } else if (canOpenFile(false, fileName)) { + enableOpen = true; + } } - if (!canUnhideFile(false, fileName)) { - enableUnhide = false; - } + enablePreview = canPreviewFile(plugin(), false, fileName); + enableExplore = canExploreFile(false, fileName); + enableHide = canHideFile(false, fileName); + enableUnhide = canUnhideFile(false, fileName); } else { - // this is a multiple selection, don't show open action so users don't open - // a thousand files - enableOpen = false; - enablePreview = false; - - // can't explore multiple files - enableExplore = false; - - // can't rename multiple files - enableRename = false; + // this is a multiple selection, don't show open or explore actions so users + // don't open a thousand files + enableNewFolder = true; + enablePreview = true; + enableDelete = true; if (selection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it - enableHide = false; - enableUnhide = false; for (const auto& index : selection) { const QString fileName = m_fs->fileName(index); @@ -431,11 +424,14 @@ void FileTreeTab::onContextMenu(const QPoint &pos) } } - menu.addAction(m_actions.newFolder); - m_actions.newFolder->setEnabled(enableNewFolder); - - menu.addAction(m_actions.open); - m_actions.open->setEnabled(enableOpen); + if (enableRun) { + m_actions.open->setText(tr("&Execute")); + menu.addAction(m_actions.open); + } else if (enableOpen) { + m_actions.open->setText(tr("&Open")); + menu.addAction(m_actions.open); + menu.addAction(m_actions.runHooked); + } menu.addAction(m_actions.preview); m_actions.preview->setEnabled(enablePreview); @@ -443,12 +439,19 @@ void FileTreeTab::onContextMenu(const QPoint &pos) menu.addAction(m_actions.explore); m_actions.explore->setEnabled(enableExplore); + menu.addSeparator(); + + menu.addAction(m_actions.newFolder); + m_actions.newFolder->setEnabled(enableNewFolder); + menu.addAction(m_actions.rename); m_actions.rename->setEnabled(enableRename); menu.addAction(m_actions.del); m_actions.del->setEnabled(enableDelete); + menu.addSeparator(); + menu.addAction(m_actions.hide); m_actions.hide->setEnabled(enableHide); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 494a7e14..2f2e501c 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -20,14 +20,15 @@ public: private: struct Actions { - QAction *newFolder = nullptr; - QAction *open = nullptr; - QAction *preview = nullptr; - QAction *explore = nullptr; - QAction *rename = nullptr; - QAction *del = nullptr; - QAction *hide = nullptr; - QAction *unhide = nullptr; + QAction* newFolder = nullptr; + QAction* open = nullptr; + QAction* runHooked = nullptr; + QAction* preview = nullptr; + QAction* explore = nullptr; + QAction* rename = nullptr; + QAction* del = nullptr; + QAction* hide = nullptr; + QAction* unhide = nullptr; }; QFileSystemModel* m_fs; @@ -35,6 +36,7 @@ private: void onCreateDirectory(); void onOpen(); + void onRunHooked(); void onPreview(); void onExplore(); void onRename(); -- cgit v1.3.1 From 48fd9fd08070e1a937bb7e49d44189d9a071edc5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 14:50:01 -0500 Subject: added "open with vfs" to the data tab uniform order for file context menus --- src/mainwindow.cpp | 37 +++++++++++++++++++++++++------- src/mainwindow.h | 1 + src/modinfodialogconflicts.cpp | 48 ++++++++++++++++++++++-------------------- 3 files changed, 55 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5af9aa5..cfa27fad 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5304,6 +5304,21 @@ void MainWindow::openDataFile() .run(); } +void MainWindow::runDataFileHooked() +{ + if (m_ContextItem == nullptr) { + return; + } + + const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); + + m_OrganizerCore.processRunner() + .setFromFile(this, targetInfo, true) + .setWaitForCompletion(ProcessRunner::Refresh) + .run(); +} + void MainWindow::openDataOriginExplorer_clicked() { if (m_ContextItem == nullptr) { @@ -5380,23 +5395,31 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) QMenu menu; if ((m_ContextItem != nullptr) && (m_ContextItem->childCount() == 0) && (m_ContextItem->data(0, Qt::UserRole + 3).toBool() != true)) { - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); - menu.addAction(tr("Add as Executable"), this, SLOT(addAsExecutable())); - QString fileName = m_ContextItem->text(0); + const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); + + if (canRunFile(isArchive, fileName)) { + menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); + } else if (canOpenFile(isArchive, fileName)) { + menu.addAction(tr("&Open"), this, SLOT(openDataFile())); + menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); + } + + menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); + if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); } - const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); - if (!isArchive && !isDirectory) { menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked())); } menu.addAction("Open Mod Info", this, SLOT(openDataModInfo_clicked())); + menu.addSeparator(); + // offer to hide/unhide file, but not for files from archives if (!isArchive) { if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { @@ -5405,8 +5428,6 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Hide"), this, SLOT(hideFile())); } } - - menu.addSeparator(); } menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); diff --git a/src/mainwindow.h b/src/mainwindow.h index 69aee073..ada8e7a7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -440,6 +440,7 @@ private slots: // data-tree context menu void writeDataToFile(); void openDataFile(); + void runDataFileHooked(); void addAsExecutable(); void previewDataFile(); void hideFile(); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 58e935f2..6018306c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -608,6 +608,19 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.preview); } + // goto + if (actions.gotoMenu) { + menu.addMenu(actions.gotoMenu); + + for (auto* a : actions.gotoActions) { + connect(a, &QAction::triggered, [&, name=a->text()]{ + emitModOpen(name); + }); + + actions.gotoMenu->addAction(a); + } + } + // explore if (actions.explore) { connect(actions.explore, &QAction::triggered, [&]{ @@ -617,6 +630,8 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.explore); } + menu.addSeparator(); + // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ @@ -635,19 +650,6 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } - // goto - if (actions.gotoMenu) { - menu.addMenu(actions.gotoMenu); - - for (auto* a : actions.gotoActions) { - connect(a, &QAction::triggered, [&, name=a->text()]{ - emitModOpen(name); - }); - - actions.gotoMenu->addAction(a); - } - } - if (!menu.isEmpty()) { menu.exec(tree->viewport()->mapToGlobal(pos)); } @@ -732,14 +734,6 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) Actions actions; - actions.hide = new QAction(tr("&Hide"), parentWidget()); - actions.hide->setEnabled(enableHide); - - // note that it is possible for hidden files to appear if they override other - // hidden files from another mod - actions.unhide = new QAction(tr("&Unhide"), parentWidget()); - actions.unhide->setEnabled(enableUnhide); - if (enableRun) { actions.open = new QAction(tr("&Execute"), parentWidget()); } else if (enableOpen) { @@ -750,11 +744,19 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) actions.preview = new QAction(tr("&Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); + actions.gotoMenu = new QMenu(tr("&Go to..."), parentWidget()); + actions.gotoMenu->setEnabled(enableGoto); + actions.explore = new QAction(tr("Open in &Explorer"), parentWidget()); actions.explore->setEnabled(enableExplore); - actions.gotoMenu = new QMenu(tr("&Go to..."), parentWidget()); - actions.gotoMenu->setEnabled(enableGoto); + actions.hide = new QAction(tr("&Hide"), parentWidget()); + actions.hide->setEnabled(enableHide); + + // note that it is possible for hidden files to appear if they override other + // hidden files from another mod + actions.unhide = new QAction(tr("&Unhide"), parentWidget()); + actions.unhide->setEnabled(enableUnhide); if (enableGoto && n == 1) { const auto* item = model->getItem(static_cast( -- cgit v1.3.1 From 3b78e436dbee043e4a3f81e5bfa09695c2a708c4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 15:18:55 -0500 Subject: double-click now opens files for the data tab, filetree and conflict lists --- src/mainwindow.cpp | 36 ++++++++++++++++++++++++++++++------ src/mainwindow.h | 2 ++ src/modinfodialogconflicts.cpp | 16 ++++++++++++++-- src/modinfodialogfiletree.cpp | 10 ++++++++++ 4 files changed, 56 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cfa27fad..3af0f30c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -350,6 +350,7 @@ MainWindow::MainWindow(Settings &settings connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); + connect(ui->dataTree, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(activateDataTreeItem(QTreeWidgetItem*, int))); connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); @@ -1767,6 +1768,10 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) } } +void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) +{ + openDataFile(item); +} bool MainWindow::refreshProfiles(bool selectProfile) { @@ -5295,7 +5300,19 @@ void MainWindow::openDataFile() return; } - const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + openDataFile(m_ContextItem); +} + +void MainWindow::openDataFile(QTreeWidgetItem* item) +{ + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); m_OrganizerCore.processRunner() @@ -5389,8 +5406,7 @@ void MainWindow::motdReceived(const QString &motd) void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) { - QTreeWidget *dataTree = findChild("dataTree"); - m_ContextItem = dataTree->itemAt(pos.x(), pos.y()); + m_ContextItem = ui->dataTree->itemAt(pos.x(), pos.y()); QMenu menu; if ((m_ContextItem != nullptr) && (m_ContextItem->childCount() == 0) @@ -5399,13 +5415,21 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) const auto isArchive = m_ContextItem->data(0, Qt::UserRole + 1).toBool(); const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); + QAction* open = nullptr; + if (canRunFile(isArchive, fileName)) { - menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); + open = menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); } else if (canOpenFile(isArchive, fileName)) { - menu.addAction(tr("&Open"), this, SLOT(openDataFile())); + open = menu.addAction(tr("&Open"), this, SLOT(openDataFile())); menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); } + if (open) { + auto bold = open->font(); + bold.setBold(true); + open->setFont(bold); + } + menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { @@ -5432,7 +5456,7 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); - menu.exec(dataTree->viewport()->mapToGlobal(pos)); + menu.exec(ui->dataTree->viewport()->mapToGlobal(pos)); } void MainWindow::on_conflictsCheckBox_toggled(bool) diff --git a/src/mainwindow.h b/src/mainwindow.h index ada8e7a7..2894b2e7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -440,6 +440,7 @@ private slots: // data-tree context menu void writeDataToFile(); void openDataFile(); + void openDataFile(QTreeWidgetItem* item); void runDataFileHooked(); void addAsExecutable(); void previewDataFile(); @@ -589,6 +590,7 @@ private slots: void refreshSavesIfOpen(); void expandDataTreeItem(QTreeWidgetItem *item); + void activateDataTreeItem(QTreeWidgetItem *item, int column); void about(); void delayedRemove(); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 6018306c..daa40cb3 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -587,6 +587,10 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) openItems(tree); }); + auto bold = actions.open->font(); + bold.setBold(true); + actions.open->setFont(bold); + menu.addAction(actions.open); } @@ -824,11 +828,15 @@ GeneralConflictsTab::GeneralConflictsTab( QObject::connect( ui->overwriteTree, &QTreeView::doubleClicked, - [&](auto&& item){ onOverwriteActivated(item); }); + [&](auto&&){ m_tab->openItems(ui->overwriteTree); }); QObject::connect( ui->overwrittenTree, &QTreeView::doubleClicked, - [&](auto&& item){ onOverwrittenActivated(item); }); + [&](auto&& item){ m_tab->openItems(ui->overwrittenTree); }); + + QObject::connect( + ui->noConflictTree, &QTreeView::doubleClicked, + [&](auto&& item){ m_tab->openItems(ui->noConflictTree); }); QObject::connect( ui->overwriteTree, &QTreeView::customContextMenuRequested, @@ -1039,6 +1047,10 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); + QObject::connect( + ui->conflictsAdvancedList, &QTreeView::activated, + [&]{ m_tab->openItems(ui->conflictsAdvancedList); }); + QObject::connect( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 23d65fdb..e94b0a4f 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -32,6 +32,10 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) m_actions.hide = new QAction(tr("&Hide"), ui->filetree); m_actions.unhide = new QAction(tr("&Unhide"), ui->filetree); + auto bold = m_actions.open->font(); + bold.setBold(true); + m_actions.open->setFont(bold); + connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); connect(m_actions.runHooked, &QAction::triggered, [&]{ onRunHooked(); }); @@ -47,6 +51,12 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) connect( ui->filetree, &QTreeView::customContextMenuRequested, [&](const QPoint& pos){ onContextMenu(pos); }); + + // disable renaming on double click, open the file instead + ui->filetree->setEditTriggers( + ui->filetree->editTriggers() & (~QAbstractItemView::DoubleClicked)); + + connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onOpen(); }); } void FileTreeTab::clear() -- cgit v1.3.1 From 2de015815c279dbf965c04c50376a4d39e28f92b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 15:34:29 -0500 Subject: added "tracked on nexus" filter --- src/categories.cpp | 1 + src/categories.h | 3 ++- src/filterlist.cpp | 1 + src/modlistsortproxy.cpp | 6 ++++++ 4 files changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/categories.cpp b/src/categories.cpp index 5c9a4d55..3e005079 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -333,6 +333,7 @@ QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const case Managed: return QObject::tr(""); case HasGameData: return QObject::tr(""); case HasNexusID: return QObject::tr(""); + case Tracked: return QObject::tr(""); default: return {}; } } diff --git a/src/categories.h b/src/categories.h index 02695e4d..6b27c6a7 100644 --- a/src/categories.h +++ b/src/categories.h @@ -47,7 +47,8 @@ public: Backup, Managed, HasGameData, - HasNexusID + HasNexusID, + Tracked }; public: diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 05bff2dd..0a5d0414 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -252,6 +252,7 @@ void FilterList::refresh() addSpecialCriteria(F::HasCategory); addSpecialCriteria(F::Conflict); addSpecialCriteria(F::Endorsed); + addSpecialCriteria(F::Tracked); addSpecialCriteria(F::HasNexusID); addSpecialCriteria(F::HasGameData); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 7ac98f66..64d5de42 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -404,6 +404,12 @@ bool ModListSortProxy::categoryMatchesMod( break; } + case CategoryFactory::Tracked: + { + b = (info->trackedState() == ModInfo::TRACKED_TRUE); + break; + } + default: { b = (info->categorySet(category)); -- cgit v1.3.1 From 8fb970faa38d808a84b1a5359462f39efbe2b37b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 2 Dec 2019 16:59:30 -0500 Subject: fixed crash when starting multiple downloads with dialog boxes opened fixed download manager dialog boxes not having a parent --- src/downloadmanager.cpp | 31 +++++++++++++++++++++++-------- src/downloadmanager.h | 4 ++++ src/organizercore.cpp | 1 + 3 files changed, 28 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 3143a22e..361e7164 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -200,8 +200,9 @@ QString DownloadManager::DownloadInfo::currentURL() } -DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) - : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false) +DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) : + IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), + m_ParentWidget(nullptr) { m_OrganizerCore = dynamic_cast(parent); connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); @@ -219,6 +220,10 @@ DownloadManager::~DownloadManager() m_ActiveDownloads.clear(); } +void DownloadManager::setParentWidget(QWidget* w) +{ + m_ParentWidget = w; +} bool DownloadManager::downloadsInProgress() { @@ -501,7 +506,7 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl if (QFile::exists(m_OutputDirectory + "/" + newDownload->m_FileName)) { setState(newDownload, STATE_PAUSING); QCoreApplication::processEvents(); - if (QMessageBox::question(nullptr, tr("Download again?"), tr("A file with the same name \"%1\" has already been downloaded. " + if (QMessageBox::question(m_ParentWidget, tr("Download again?"), tr("A file with the same name \"%1\" has already been downloaded. " "Do you want to download it again? The new file will receive a different name.").arg(newDownload->m_FileName), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { if (reply->isFinished()) @@ -513,7 +518,7 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl newDownload->setName(getDownloadFileName(newDownload->m_FileName, true), true); endDisableDirWatcher(); if (newDownload->m_State == STATE_PAUSED) - resumeDownload(indexByName(newDownload->m_FileName)); + resumeDownload(indexByInfo(newDownload)); else setState(newDownload, STATE_DOWNLOADING); } @@ -527,7 +532,7 @@ void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownl newDownload->m_State != STATE_READY && newDownload->m_State != STATE_FETCHINGMODINFO && reply->isFinished()) { - downloadFinished(indexByName(newDownload->m_FileName)); + downloadFinished(indexByInfo(newDownload)); return; } } else @@ -556,7 +561,7 @@ void DownloadManager::addNXMDownload(const QString &url) log::debug("add nxm download: {}", url); if (foundGame == nullptr) { log::debug("download requested for wrong game (game: {}, url: {})", m_ManagedGame->gameShortName(), nxmInfo.game()); - QMessageBox::information(nullptr, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " + QMessageBox::information(m_ParentWidget, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " "has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok); return; } @@ -571,7 +576,7 @@ void DownloadManager::addNXMDownload(const QString &url) "download requested is already queued (mod: {}, file: {})", nxmInfo.modId(), nxmInfo.fileId()); - QMessageBox::information(nullptr, tr("Already Queued"), infoStr, QMessageBox::Ok); + QMessageBox::information(m_ParentWidget, tr("Already Queued"), infoStr, QMessageBox::Ok); return; } } @@ -615,7 +620,7 @@ void DownloadManager::addNXMDownload(const QString &url) } log::debug("{}", debugStr); - QMessageBox::information(nullptr, tr("Already Started"), infoStr, QMessageBox::Ok); + QMessageBox::information(m_ParentWidget, tr("Already Started"), infoStr, QMessageBox::Ok); return; } } @@ -1738,6 +1743,16 @@ int DownloadManager::indexByName(const QString &fileName) const return -1; } +int DownloadManager::indexByInfo(const DownloadInfo* info) const +{ + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i] == info) { + return i; + } + } + return -1; +} + void DownloadManager::nxmDownloadURLsAvailable(QString gameName, int modID, int fileID, QVariant userData, QVariant resultData, int requestID) { std::set::iterator idIter = m_RequestIDs.find(requestID); diff --git a/src/downloadmanager.h b/src/downloadmanager.h index bed1b3cc..f2ad15f4 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -137,6 +137,8 @@ public: ~DownloadManager(); + void setParentWidget(QWidget* w); + /** * @brief determine if a download is currently in progress * @@ -368,6 +370,7 @@ public: * @return index of that download or -1 if it wasn't found */ int indexByName(const QString &fileName) const; + int indexByInfo(const DownloadInfo* info) const; void pauseAll(); @@ -529,6 +532,7 @@ private: NexusInterface *m_NexusInterface; OrganizerCore *m_OrganizerCore; + QWidget* m_ParentWidget; QVector> m_PendingDownloads; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index c585ba09..55cb82ff 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -251,6 +251,7 @@ void OrganizerCore::setUserInterface(IUserInterface* ui) m_InstallationManager.setParentWidget(w); m_Updater.setUserInterface(w); m_UILocker.setUserInterface(w); + m_DownloadManager.setParentWidget(w); checkForUpdates(); } -- cgit v1.3.1 From e263d3049acd7e296ad87f2ac91318b37bf33f2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 11:43:36 -0500 Subject: keyboard nav for filter list, alternating row colors --- src/filterlist.cpp | 96 ++++++++++++++++++++++++++++++++++++------------------ src/filterlist.h | 3 +- src/mainwindow.ui | 6 ++++ 3 files changed, 72 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 0a5d0414..b345d21d 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -111,51 +111,88 @@ private: }; -class ClickFilter : public QObject +class CriteriaItemFilter : public QObject { public: - ClickFilter(std::function f) - : m_f(std::move(f)) + using Callback = std::function; + + CriteriaItemFilter(QTreeWidget* tree, Callback f) + : QObject(tree), m_tree(tree), m_f(std::move(f)) { } bool eventFilter(QObject* o, QEvent* e) override { - if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { - if (m_f) { - return m_f(static_cast(e)); + // careful: this filter is installed on both the tree and the viewport + // + // no check is currently necessary because mouse events originate from the + // viewport only and keyboard events from the tree only + + if (m_f) { + if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { + if (handleMouse(static_cast(e))) { + return true; + } + } else if (e->type() == QEvent::KeyPress) { + if (handleKeyboard(static_cast(e))) { + return true; + } } } - return QObject::eventFilter(o, e);; + return QObject::eventFilter(o, e); } private: - std::function m_f; + QTreeWidget* m_tree; + Callback m_f; + + bool handleMouse(QMouseEvent* e) + { + auto* item = m_tree->itemAt(e->pos()); + if (!item) { + return false; + } + + m_tree->setCurrentItem(item); + + const auto dir = (e->button() == Qt::LeftButton ? 1 : - 1); + + return m_f(item, dir); + } + + bool handleKeyboard(QKeyEvent* e) + { + if (e->key() == Qt::Key_Space) { + auto* item = m_tree->currentItem(); + if (!item) { + return false; + } + + const auto shiftPressed = (e->modifiers() & Qt::ShiftModifier); + const auto dir = (shiftPressed ? -1 : 1); + + return m_f(item, dir); + } + + return false; + } }; FilterList::FilterList(Ui::MainWindow* ui, CategoryFactory& factory) : ui(ui), m_factory(factory) { - ui->filters->viewport()->installEventFilter( - new ClickFilter([&](auto* e){ return onClick(e); })); + auto* eventFilter = new CriteriaItemFilter( + ui->filters, [&](auto* item, int dir){ return cycleItem(item, dir); }); - connect( - ui->filtersClear, &QPushButton::clicked, - [&]{ clearSelection(); }); + ui->filters->installEventFilter(eventFilter); + ui->filters->viewport()->installEventFilter(eventFilter); - connect( - ui->filtersEdit, &QPushButton::clicked, - [&]{ editCategories(); }); - - connect( - ui->filtersAnd, &QCheckBox::toggled, - [&]{ onOptionsChanged(); }); - - connect( - ui->filtersOr, &QCheckBox::toggled, - [&]{ onOptionsChanged(); }); + connect(ui->filtersClear, &QPushButton::clicked, [&]{ clearSelection(); }); + connect(ui->filtersEdit, &QPushButton::clicked, [&]{ editCategories(); }); + connect(ui->filtersAnd, &QCheckBox::toggled, [&]{ onOptionsChanged(); }); + connect(ui->filtersOr, &QCheckBox::toggled, [&]{ onOptionsChanged(); }); connect( ui->filtersSeparators, qOverload(&QComboBox::currentIndexChanged), @@ -321,21 +358,16 @@ void FilterList::clearSelection() checkCriteria(); } -bool FilterList::onClick(QMouseEvent* e) +bool FilterList::cycleItem(QTreeWidgetItem* item, int direction) { - auto* item = ui->filters->itemAt(e->pos()); - if (!item) { - return false; - } - auto* ci = dynamic_cast(item); if (!ci) { return false; } - if (e->button() == Qt::LeftButton) { + if (direction > 0) { ci->nextState(); - } else if (e->button() == Qt::RightButton) { + } else if (direction < 0) { ci->previousState(); } else { return false; diff --git a/src/filterlist.h b/src/filterlist.h index 671462d4..72cbe8bf 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -34,10 +34,12 @@ private: CategoryFactory& m_factory; bool onClick(QMouseEvent* e); + void onItemActivated(QTreeWidgetItem* item); void onOptionsChanged(); void editCategories(); void checkCriteria(); + bool cycleItem(QTreeWidgetItem* item, int direction); QTreeWidgetItem* addCriteriaItem( QTreeWidgetItem *root, const QString &name, int categoryID, @@ -47,7 +49,6 @@ private: void addCategoryCriteria( QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); void addSpecialCriteria(int type); - }; #endif // MODORGANIZER_CATEGORIESLIST_INCLUDED diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0520db84..309e9f62 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -79,6 +79,9 @@ Qt::NoContextMenu + + true + QAbstractItemView::NoSelection @@ -91,6 +94,9 @@ true + + true + true -- cgit v1.3.1 From dd6f0fed1085682f56d2815ff885a5ab58058a0b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 12:00:54 -0500 Subject: added explore button in the settings to open stylesheets folder added stylesheets folder to the open folders button moved "Open MO2 Logs folder" up to be with instance folders, removed "MO2" in the name --- src/mainwindow.cpp | 17 +++++++---------- src/mainwindow.h | 1 + src/settingsdialog.ui | 31 +++++++++++++++++++------------ src/settingsdialoggeneral.cpp | 42 +++++++++++++++++++++++++----------------- src/settingsdialoggeneral.h | 7 ++++--- 5 files changed, 56 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3af0f30c..c365fb6b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4302,6 +4302,11 @@ void MainWindow::openPluginsFolder() shell::Explore(pluginsPath); } +void MainWindow::openStylesheetsFolder() +{ + QString ssPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()); + shell::Explore(ssPath); +} void MainWindow::openProfileFolder() { @@ -4519,33 +4524,25 @@ static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) QMenu *MainWindow::openFolderMenu() { - QMenu *FolderMenu = new QMenu(this); FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); - FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); - FolderMenu->addAction(tr("Open INIs folder"), this, SLOT(openIniFolder())); FolderMenu->addSeparator(); FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); - FolderMenu->addAction(tr("Open Mods folder"), this, SLOT(openModsFolder())); - FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); - FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); + FolderMenu->addAction(tr("Open Logs folder"), this, SLOT(openLogsFolder())); FolderMenu->addSeparator(); FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); - FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); - - FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder())); - + FolderMenu->addAction(tr("Open MO2 Stylesheets folder"), this, SLOT(openStylesheetsFolder())); return FolderMenu; } diff --git a/src/mainwindow.h b/src/mainwindow.h index 2894b2e7..f5bab586 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -545,6 +545,7 @@ private slots: void openLogsFolder(); void openInstallFolder(); void openPluginsFolder(); + void openStylesheetsFolder(); void openDownloadsFolder(); void openModsFolder(); void openProfileFolder(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index ad7e2ab3..bc59d635 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -45,7 +45,7 @@ - + 0 @@ -65,16 +65,6 @@ - - - - The language of the user interface. - - - The language of the user interface. - - - @@ -92,7 +82,24 @@ - + + + + Explore... + + + + + + + The language of the user interface. + + + The language of the user interface. + + + + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index b0e64305..dca4410b 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -3,8 +3,11 @@ #include "appconfig.h" #include "categoriesdialog.h" #include "colortable.h" +#include #include +using namespace MOBase; + GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d) { @@ -24,17 +27,16 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->usePrereleaseBox->setChecked(settings().usePrereleases()); ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); + QObject::connect(ui->exploreStyles, &QPushButton::clicked, [&]{ onExploreStyles(); }); + QObject::connect( - ui->categoriesBtn, &QPushButton::clicked, - [&]{ on_categoriesBtn_clicked(); }); + ui->categoriesBtn, &QPushButton::clicked, [&]{ onEditCategories(); }); QObject::connect( - ui->resetColorsBtn, &QPushButton::clicked, - [&]{ on_resetColorsBtn_clicked(); }); + ui->resetColorsBtn, &QPushButton::clicked, [&]{ onResetColors(); }); QObject::connect( - ui->resetDialogsButton, &QPushButton::clicked, - [&]{ on_resetDialogsButton_clicked(); }); + ui->resetDialogsButton, &QPushButton::clicked, [&]{ onResetDialogs(); }); } void GeneralSettingsTab::update() @@ -175,12 +177,27 @@ void GeneralSettingsTab::resetDialogs() settings().widgets().resetQuestionButtons(); } -void GeneralSettingsTab::on_resetColorsBtn_clicked() +void GeneralSettingsTab::onExploreStyles() +{ + QString ssPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()); + shell::Explore(ssPath); +} + +void GeneralSettingsTab::onEditCategories() +{ + CategoriesDialog dialog(&dialog()); + + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} + +void GeneralSettingsTab::onResetColors() { ui->colorTable->resetColors(); } -void GeneralSettingsTab::on_resetDialogsButton_clicked() +void GeneralSettingsTab::onResetDialogs() { const auto r = QMessageBox::question( parentWidget(), @@ -194,12 +211,3 @@ void GeneralSettingsTab::on_resetDialogsButton_clicked() resetDialogs(); } } - -void GeneralSettingsTab::on_categoriesBtn_clicked() -{ - CategoriesDialog dialog(&dialog()); - - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} diff --git a/src/settingsdialoggeneral.h b/src/settingsdialoggeneral.h index 706ba9ef..455edcaf 100644 --- a/src/settingsdialoggeneral.h +++ b/src/settingsdialoggeneral.h @@ -20,9 +20,10 @@ private: void resetDialogs(); - void on_categoriesBtn_clicked(); - void on_resetColorsBtn_clicked(); - void on_resetDialogsButton_clicked(); + void onExploreStyles(); + void onEditCategories(); + void onResetColors(); + void onResetDialogs(); }; #endif // SETTINGSDIALOGGENERAL_H -- cgit v1.3.1 From a0fa896e68856ec5204e7f74db775bdb3595010a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 12:42:15 -0500 Subject: added open previews on double-click option implemented for filetree --- src/modinfodialogfiletree.cpp | 42 +++++++++++++++++++++++++++++++++--------- src/settings.cpp | 12 +++++++++++- src/settings.h | 7 ++++++- src/settingsdialog.ui | 10 ++++++++++ src/settingsdialoggeneral.cpp | 2 ++ 5 files changed, 62 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index e94b0a4f..c79a5264 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -32,10 +32,6 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) m_actions.hide = new QAction(tr("&Hide"), ui->filetree); m_actions.unhide = new QAction(tr("&Unhide"), ui->filetree); - auto bold = m_actions.open->font(); - bold.setBold(true); - m_actions.open->setFont(bold); - connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); connect(m_actions.runHooked, &QAction::triggered, [&]{ onRunHooked(); }); @@ -152,10 +148,17 @@ void FileTreeTab::onOpen() return; } - core().processRunner() - .setFromFile(parentWidget(), m_fs->filePath(selection)) - .setWaitForCompletion() - .run(); + const auto path = m_fs->filePath(selection); + const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview && canPreviewFile(plugin(), false, path)) { + core().previewFile(parentWidget(), mod().name(), path); + } else { + core().processRunner() + .setFromFile(parentWidget(), path) + .setWaitForCompletion() + .run(); + } } void FileTreeTab::onRunHooked() @@ -408,7 +411,6 @@ void FileTreeTab::onContextMenu(const QPoint &pos) // this is a multiple selection, don't show open or explore actions so users // don't open a thousand files enableNewFolder = true; - enablePreview = true; enableDelete = true; if (selection.size() < max_scan_for_context_menu) { @@ -446,6 +448,28 @@ void FileTreeTab::onContextMenu(const QPoint &pos) menu.addAction(m_actions.preview); m_actions.preview->setEnabled(enablePreview); + auto bold = m_actions.preview->font(); + bold.setBold(true); + auto notBold = m_actions.preview->font(); + notBold.setBold(false); + + // preview is bold if the file is previewable and [the preview on double-click + // option is enabled or the file can't be opened]; open is bold if the file + // can be opened and cannot be previewed + if (enablePreview && core().settings().interface().doubleClicksOpenPreviews()) { + m_actions.open->setFont(notBold); + m_actions.preview->setFont(bold); + } else if (enableOpen) { + m_actions.open->setFont(bold); + m_actions.preview->setFont(notBold); + } else if (enablePreview) { + m_actions.open->setFont(notBold); + m_actions.preview->setFont(bold); + } else { + m_actions.open->setFont(notBold); + m_actions.preview->setFont(notBold); + } + menu.addAction(m_actions.explore); m_actions.explore->setEnabled(enableExplore); diff --git a/src/settings.cpp b/src/settings.cpp index e1e5c2da..68dc19d9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1966,11 +1966,21 @@ bool InterfaceSettings::showChangeGameConfirmation() const return get(m_Settings, "Settings", "show_change_game_confirmation", true); } -void InterfaceSettings::setShowChangeGameConfirmation(bool b) const +void InterfaceSettings::setShowChangeGameConfirmation(bool b) { set(m_Settings, "Settings", "show_change_game_confirmation", b); } +bool InterfaceSettings::doubleClicksOpenPreviews() const +{ + return get(m_Settings, "Settings", "double_click_previews", false); +} + +void InterfaceSettings::setDoubleClicksOpenPreviews(bool b) +{ + set(m_Settings, "Settings", "double_click_previews", b); +} + DiagnosticsSettings::DiagnosticsSettings(QSettings& settings) : m_Settings(settings) diff --git a/src/settings.h b/src/settings.h index 870e0fc4..0e5238b1 100644 --- a/src/settings.h +++ b/src/settings.h @@ -608,7 +608,12 @@ public: // whether to show the confirmation when switching instances // bool showChangeGameConfirmation() const; - void setShowChangeGameConfirmation(bool b) const; + void setShowChangeGameConfirmation(bool b); + + // whether double-clicks on files should try to open previews first + // + bool doubleClicksOpenPreviews() const; + void setDoubleClicksOpenPreviews(bool b); private: QSettings& m_Settings; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index bc59d635..8e175312 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -101,6 +101,9 @@ + + https://www.transifex.com/tannin/mod-organizer/ + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> @@ -132,6 +135,13 @@ + + + + Open previews on double-click + + + diff --git a/src/settingsdialoggeneral.cpp b/src/settingsdialoggeneral.cpp index dca4410b..58871cee 100644 --- a/src/settingsdialoggeneral.cpp +++ b/src/settingsdialoggeneral.cpp @@ -21,6 +21,7 @@ GeneralSettingsTab::GeneralSettingsTab(Settings& s, SettingsDialog& d) ui->centerDialogs->setChecked(settings().geometry().centerDialogs()); ui->changeGameConfirmation->setChecked(settings().interface().showChangeGameConfirmation()); + ui->doubleClickPreviews->setChecked(settings().interface().doubleClicksOpenPreviews()); ui->compactBox->setChecked(settings().interface().compactDownloads()); ui->showMetaBox->setChecked(settings().interface().metaDownloads()); ui->checkForUpdates->setChecked(settings().checkForUpdates()); @@ -63,6 +64,7 @@ void GeneralSettingsTab::update() settings().geometry().setCenterDialogs(ui->centerDialogs->isChecked()); settings().interface().setShowChangeGameConfirmation(ui->changeGameConfirmation->isChecked()); + settings().interface().setDoubleClicksOpenPreviews(ui->doubleClickPreviews->isChecked()); settings().interface().setCompactDownloads(ui->compactBox->isChecked()); settings().interface().setMetaDownloads(ui->showMetaBox->isChecked()); settings().setCheckForUpdates(ui->checkForUpdates->isChecked()); -- cgit v1.3.1 From 91b95d00a6fda3788e774488ebeb72b28c89e656 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 13:24:31 -0500 Subject: implemented previews on double-click for the data and conflicts tabs --- src/mainwindow.cpp | 90 +++++++++++++++++++++++++++++++++++++----- src/mainwindow.h | 1 + src/modinfodialogconflicts.cpp | 57 +++++++++++++++++++------- src/modinfodialogconflicts.h | 3 ++ src/modinfodialogfiletree.cpp | 50 +++++++++++------------ src/modinfodialogfiletree.h | 1 + src/settingsdialog.ui | 3 ++ 7 files changed, 154 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c365fb6b..01f683a7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -198,6 +198,55 @@ QString UnmanagedModName() bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); +void setDefaultActivationActionForFile(QAction* open, QAction* preview) +{ + if (!open && !preview) { + return; + } + + QFont bold, notBold; + + if (open) { + bold = open->font(); + notBold = open->font(); + } else { + bold = preview->font(); + notBold = preview->font(); + } + + notBold.setBold(false); + bold.setBold(true); + + + const auto& s = Settings::instance(); + const auto openEnabled = (open && open->isEnabled()); + const auto previewEnabled = (preview && preview->isEnabled()); + + bool doPreview = false; + + // preview is bold if the file is previewable and [the preview on double-click + // option is enabled or the file can't be opened]; open is bold if the file + // can be opened and cannot be previewed + if (previewEnabled && s.interface().doubleClicksOpenPreviews()) { + doPreview = true; + } else if (openEnabled) { + doPreview = false; + } else if (previewEnabled) { + doPreview = true; + } else { + // shouldn't happen, checked above + return; + } + + if (open) { + open->setFont(doPreview ? notBold : bold); + } + + if (preview) { + preview->setFont(doPreview ? bold : notBold); + } +} + MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore @@ -1770,7 +1819,23 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) { - openDataFile(item); + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); + + const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { + previewDataFile(item); + } else { + openDataFile(item); + } } bool MainWindow::refreshProfiles(bool selectProfile) @@ -5287,7 +5352,16 @@ void MainWindow::disableSelectedMods_clicked() void MainWindow::previewDataFile() { - QString fileName = QDir::fromNativeSeparators(m_ContextItem->data(0, Qt::UserRole).toString()); + if (m_ContextItem == nullptr) { + return; + } + + previewDataFile(m_ContextItem); +} + +void MainWindow::previewDataFile(QTreeWidgetItem* item) +{ + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); m_OrganizerCore.previewFileWithAlternatives(this, fileName); } @@ -5413,6 +5487,7 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); QAction* open = nullptr; + QAction* preview = nullptr; if (canRunFile(isArchive, fileName)) { open = menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); @@ -5421,16 +5496,10 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); } - if (open) { - auto bold = open->font(); - bold.setBold(true); - open->setFont(bold); - } - menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + preview = menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); } if (!isArchive && !isDirectory) { @@ -5449,7 +5518,10 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Hide"), this, SLOT(hideFile())); } } + + setDefaultActivationActionForFile(open, preview); } + menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); diff --git a/src/mainwindow.h b/src/mainwindow.h index f5bab586..98573423 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -444,6 +444,7 @@ private slots: void runDataFileHooked(); void addAsExecutable(); void previewDataFile(); + void previewDataFile(QTreeWidgetItem* item); void hideFile(); void unhideFile(); void openDataOriginExplorer_clicked(); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index daa40cb3..9c7ccc8c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -12,6 +12,9 @@ using namespace MOBase; // checking whether menu items apply to them, just show all of them const std::size_t max_small_selection = 50; +// in mainwindow.cpp +void setDefaultActivationActionForFile(QAction* open, QAction* preview); + class ConflictItem { @@ -527,20 +530,43 @@ void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) } } -void ConflictsTab::openItems(QTreeView* tree) +void ConflictsTab::activateItems(QTreeView* tree) { + const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); + // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner() - .setFromFile(parentWidget(), item->fileName()) - .setWaitForCompletion() - .run(); + const auto path = item->fileName(); + + if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { + previewItem(item); + } else { + openItem(item); + } + + return true; + }); +} +void ConflictsTab::openItems(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + openItem(item); return true; }); } +void ConflictsTab::openItem(const ConflictItem* item) +{ + core().processRunner() + .setFromFile(parentWidget(), item->fileName()) + .setWaitForCompletion() + .run(); +} + void ConflictsTab::runItemsHooked(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -560,11 +586,16 @@ void ConflictsTab::previewItems(QTreeView* tree) // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - core().previewFileWithAlternatives(parentWidget(), item->fileName()); + previewItem(item); return true; }); } +void ConflictsTab::previewItem(const ConflictItem* item) +{ + core().previewFileWithAlternatives(parentWidget(), item->fileName()); +} + void ConflictsTab::exploreItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -587,10 +618,6 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) openItems(tree); }); - auto bold = actions.open->font(); - bold.setBold(true); - actions.open->setFont(bold); - menu.addAction(actions.open); } @@ -654,6 +681,8 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } + setDefaultActivationActionForFile(actions.open, actions.preview); + if (!menu.isEmpty()) { menu.exec(tree->viewport()->mapToGlobal(pos)); } @@ -828,15 +857,15 @@ GeneralConflictsTab::GeneralConflictsTab( QObject::connect( ui->overwriteTree, &QTreeView::doubleClicked, - [&](auto&&){ m_tab->openItems(ui->overwriteTree); }); + [&](auto&&){ m_tab->activateItems(ui->overwriteTree); }); QObject::connect( ui->overwrittenTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->openItems(ui->overwrittenTree); }); + [&](auto&& item){ m_tab->activateItems(ui->overwrittenTree); }); QObject::connect( ui->noConflictTree, &QTreeView::doubleClicked, - [&](auto&& item){ m_tab->openItems(ui->noConflictTree); }); + [&](auto&& item){ m_tab->activateItems(ui->noConflictTree); }); QObject::connect( ui->overwriteTree, &QTreeView::customContextMenuRequested, @@ -1049,7 +1078,7 @@ AdvancedConflictsTab::AdvancedConflictsTab( QObject::connect( ui->conflictsAdvancedList, &QTreeView::activated, - [&]{ m_tab->openItems(ui->conflictsAdvancedList); }); + [&]{ m_tab->activateItems(ui->conflictsAdvancedList); }); QObject::connect( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 8baa62b6..3ac8de23 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -107,11 +107,14 @@ public: void restoreState(const Settings& s) override; bool canHandleUnmanaged() const override; + void activateItems(QTreeView* tree); void openItems(QTreeView* tree); void runItemsHooked(QTreeView* tree); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); + void openItem(const ConflictItem* item); + void previewItem(const ConflictItem* item); void changeItemsVisibility(QTreeView* tree, bool visible); void showContextMenu(const QPoint &pos, QTreeView* tree); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index c79a5264..d1ae3823 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -14,6 +14,9 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; +// in mainwindow.cpp +void setDefaultActivationActionForFile(QAction* open, QAction* preview); + FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { @@ -52,7 +55,7 @@ FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) ui->filetree->setEditTriggers( ui->filetree->editTriggers() & (~QAbstractItemView::DoubleClicked)); - connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onOpen(); }); + connect(ui->filetree, &QTreeView::activated, [&](auto&&){ onActivated(); }); } void FileTreeTab::clear() @@ -141,7 +144,7 @@ void FileTreeTab::onCreateDirectory() ui->filetree->edit(newIndex); } -void FileTreeTab::onOpen() +void FileTreeTab::onActivated() { auto selection = singleSelection(); if (!selection.isValid()) { @@ -152,13 +155,24 @@ void FileTreeTab::onOpen() const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); if (tryPreview && canPreviewFile(plugin(), false, path)) { - core().previewFile(parentWidget(), mod().name(), path); + onPreview(); } else { - core().processRunner() - .setFromFile(parentWidget(), path) - .setWaitForCompletion() - .run(); + onOpen(); + } +} + +void FileTreeTab::onOpen() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; } + + const auto path = m_fs->filePath(selection); + core().processRunner() + .setFromFile(parentWidget(), path) + .setWaitForCompletion() + .run(); } void FileTreeTab::onRunHooked() @@ -448,27 +462,7 @@ void FileTreeTab::onContextMenu(const QPoint &pos) menu.addAction(m_actions.preview); m_actions.preview->setEnabled(enablePreview); - auto bold = m_actions.preview->font(); - bold.setBold(true); - auto notBold = m_actions.preview->font(); - notBold.setBold(false); - - // preview is bold if the file is previewable and [the preview on double-click - // option is enabled or the file can't be opened]; open is bold if the file - // can be opened and cannot be previewed - if (enablePreview && core().settings().interface().doubleClicksOpenPreviews()) { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(bold); - } else if (enableOpen) { - m_actions.open->setFont(bold); - m_actions.preview->setFont(notBold); - } else if (enablePreview) { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(bold); - } else { - m_actions.open->setFont(notBold); - m_actions.preview->setFont(notBold); - } + setDefaultActivationActionForFile(m_actions.open, m_actions.preview); menu.addAction(m_actions.explore); m_actions.explore->setEnabled(enableExplore); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 2f2e501c..c3c84ed4 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -35,6 +35,7 @@ private: Actions m_actions; void onCreateDirectory(); + void onActivated(); void onOpen(); void onRunHooked(); void onPreview(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 8e175312..84ca5731 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -137,6 +137,9 @@ + + Whether double-clicking on a file opens the preview window or launches the program associated with it. This applies to the Data tab as well as the Conflicts and Filetree tabs in the mod info window. + Open previews on double-click -- cgit v1.3.1 From dd28b85ad0c8183687a8a9992314d3f3104c73aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 15:42:13 -0500 Subject: deleted 6788 stylesheets, they're now dependencies in umbrella --- src/stylesheets/Paper Automata.qss | 1002 ------------------- src/stylesheets/Paper Dark by 6788.qss | 1030 -------------------- src/stylesheets/Paper Light by 6788.qss | 1009 ------------------- src/stylesheets/Paper/Automata/Arrows/down.svg | 97 -- src/stylesheets/Paper/Automata/Arrows/left.svg | 97 -- src/stylesheets/Paper/Automata/Arrows/right.svg | 97 -- src/stylesheets/Paper/Automata/Arrows/up.svg | 97 -- .../Paper/Automata/Toolbar/archives.svg | 99 -- .../Paper/Automata/Toolbar/executables.svg | 99 -- src/stylesheets/Paper/Automata/Toolbar/help.svg | 128 --- .../Paper/Automata/Toolbar/instances.svg | 98 -- src/stylesheets/Paper/Automata/Toolbar/nexus.svg | 122 --- .../Paper/Automata/Toolbar/problems.svg | 132 --- .../Paper/Automata/Toolbar/profiles.svg | 112 --- .../Paper/Automata/Toolbar/settings.svg | 95 -- src/stylesheets/Paper/Automata/Toolbar/tools.svg | 117 --- src/stylesheets/Paper/Automata/Toolbar/update.svg | 141 --- src/stylesheets/Paper/Automata/background.svg | 99 -- src/stylesheets/Paper/Automata/backup.svg | 126 --- src/stylesheets/Paper/Automata/branch.svg | 99 -- src/stylesheets/Paper/Automata/collapsed.svg | 97 -- src/stylesheets/Paper/Automata/cross.svg | 94 -- src/stylesheets/Paper/Automata/dots.svg | 111 --- src/stylesheets/Paper/Automata/expanded.svg | 104 -- src/stylesheets/Paper/Automata/folder.svg | 94 -- src/stylesheets/Paper/Automata/heart.svg | 111 --- src/stylesheets/Paper/Automata/refresh.svg | 103 -- src/stylesheets/Paper/Automata/restore-alt.svg | 126 --- src/stylesheets/Paper/Automata/restore.svg | 94 -- src/stylesheets/Paper/Automata/run.svg | 109 --- src/stylesheets/Paper/Automata/shortcut.svg | 94 -- src/stylesheets/Paper/Automata/sort.svg | 125 --- src/stylesheets/Paper/Dark/Arrows/down.svg | 97 -- src/stylesheets/Paper/Dark/Arrows/left.svg | 97 -- src/stylesheets/Paper/Dark/Arrows/right.svg | 97 -- src/stylesheets/Paper/Dark/Arrows/up.svg | 97 -- src/stylesheets/Paper/Dark/Toolbar/archives.svg | 99 -- src/stylesheets/Paper/Dark/Toolbar/executables.svg | 99 -- src/stylesheets/Paper/Dark/Toolbar/help.svg | 128 --- src/stylesheets/Paper/Dark/Toolbar/instances.svg | 98 -- src/stylesheets/Paper/Dark/Toolbar/nexus.svg | 122 --- src/stylesheets/Paper/Dark/Toolbar/problems.svg | 132 --- src/stylesheets/Paper/Dark/Toolbar/profiles.svg | 112 --- src/stylesheets/Paper/Dark/Toolbar/settings.svg | 95 -- src/stylesheets/Paper/Dark/Toolbar/tools.svg | 117 --- src/stylesheets/Paper/Dark/Toolbar/update.svg | 141 --- src/stylesheets/Paper/Dark/backup.svg | 128 --- src/stylesheets/Paper/Dark/check-alt.svg | 94 -- src/stylesheets/Paper/Dark/check.svg | 94 -- src/stylesheets/Paper/Dark/cross.svg | 94 -- src/stylesheets/Paper/Dark/dots.svg | 111 --- src/stylesheets/Paper/Dark/folder.svg | 94 -- src/stylesheets/Paper/Dark/heart.svg | 111 --- src/stylesheets/Paper/Dark/highlight.svg | 205 ---- src/stylesheets/Paper/Dark/refresh.svg | 103 -- src/stylesheets/Paper/Dark/restore-alt.svg | 125 --- src/stylesheets/Paper/Dark/restore.svg | 94 -- src/stylesheets/Paper/Dark/run.svg | 109 --- src/stylesheets/Paper/Dark/shortcut.svg | 96 -- src/stylesheets/Paper/Dark/sort.svg | 122 --- src/stylesheets/Paper/Dark/unchecked-alt.svg | 100 -- src/stylesheets/Paper/Dark/unchecked-disabled.svg | 94 -- src/stylesheets/Paper/Dark/unchecked.svg | 100 -- src/stylesheets/Paper/Light/Arrows/down.svg | 97 -- src/stylesheets/Paper/Light/Arrows/left.svg | 97 -- src/stylesheets/Paper/Light/Arrows/right.svg | 97 -- src/stylesheets/Paper/Light/Arrows/up.svg | 97 -- src/stylesheets/Paper/Light/Toolbar/archives.svg | 99 -- .../Paper/Light/Toolbar/executables.svg | 99 -- src/stylesheets/Paper/Light/Toolbar/help.svg | 128 --- src/stylesheets/Paper/Light/Toolbar/instances.svg | 98 -- src/stylesheets/Paper/Light/Toolbar/nexus.svg | 122 --- src/stylesheets/Paper/Light/Toolbar/problems.svg | 132 --- src/stylesheets/Paper/Light/Toolbar/profiles.svg | 112 --- src/stylesheets/Paper/Light/Toolbar/settings.svg | 95 -- src/stylesheets/Paper/Light/Toolbar/tools.svg | 117 --- src/stylesheets/Paper/Light/Toolbar/update.svg | 141 --- src/stylesheets/Paper/Light/backup.svg | 126 --- src/stylesheets/Paper/Light/check-alt.svg | 94 -- src/stylesheets/Paper/Light/check-white.svg | 94 -- src/stylesheets/Paper/Light/check.svg | 94 -- src/stylesheets/Paper/Light/cross.svg | 94 -- src/stylesheets/Paper/Light/dots.svg | 111 --- src/stylesheets/Paper/Light/folder.svg | 94 -- src/stylesheets/Paper/Light/heart.svg | 111 --- src/stylesheets/Paper/Light/refresh.svg | 103 -- src/stylesheets/Paper/Light/restore-alt.svg | 126 --- src/stylesheets/Paper/Light/restore.svg | 94 -- src/stylesheets/Paper/Light/run.svg | 109 --- src/stylesheets/Paper/Light/shortcut.svg | 94 -- src/stylesheets/Paper/Light/sort.svg | 125 --- src/stylesheets/Paper/Light/unchecked-alt.svg | 94 -- src/stylesheets/Paper/Light/unchecked-disabled.svg | 94 -- src/stylesheets/Paper/Light/unchecked-hover.svg | 94 -- src/stylesheets/Paper/Light/unchecked.svg | 94 -- 95 files changed, 12939 deletions(-) delete mode 100644 src/stylesheets/Paper Automata.qss delete mode 100644 src/stylesheets/Paper Dark by 6788.qss delete mode 100644 src/stylesheets/Paper Light by 6788.qss delete mode 100644 src/stylesheets/Paper/Automata/Arrows/down.svg delete mode 100644 src/stylesheets/Paper/Automata/Arrows/left.svg delete mode 100644 src/stylesheets/Paper/Automata/Arrows/right.svg delete mode 100644 src/stylesheets/Paper/Automata/Arrows/up.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/archives.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/executables.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/help.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/instances.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/nexus.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/problems.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/profiles.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/settings.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/tools.svg delete mode 100644 src/stylesheets/Paper/Automata/Toolbar/update.svg delete mode 100644 src/stylesheets/Paper/Automata/background.svg delete mode 100644 src/stylesheets/Paper/Automata/backup.svg delete mode 100644 src/stylesheets/Paper/Automata/branch.svg delete mode 100644 src/stylesheets/Paper/Automata/collapsed.svg delete mode 100644 src/stylesheets/Paper/Automata/cross.svg delete mode 100644 src/stylesheets/Paper/Automata/dots.svg delete mode 100644 src/stylesheets/Paper/Automata/expanded.svg delete mode 100644 src/stylesheets/Paper/Automata/folder.svg delete mode 100644 src/stylesheets/Paper/Automata/heart.svg delete mode 100644 src/stylesheets/Paper/Automata/refresh.svg delete mode 100644 src/stylesheets/Paper/Automata/restore-alt.svg delete mode 100644 src/stylesheets/Paper/Automata/restore.svg delete mode 100644 src/stylesheets/Paper/Automata/run.svg delete mode 100644 src/stylesheets/Paper/Automata/shortcut.svg delete mode 100644 src/stylesheets/Paper/Automata/sort.svg delete mode 100644 src/stylesheets/Paper/Dark/Arrows/down.svg delete mode 100644 src/stylesheets/Paper/Dark/Arrows/left.svg delete mode 100644 src/stylesheets/Paper/Dark/Arrows/right.svg delete mode 100644 src/stylesheets/Paper/Dark/Arrows/up.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/archives.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/executables.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/help.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/instances.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/nexus.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/problems.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/profiles.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/settings.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/tools.svg delete mode 100644 src/stylesheets/Paper/Dark/Toolbar/update.svg delete mode 100644 src/stylesheets/Paper/Dark/backup.svg delete mode 100644 src/stylesheets/Paper/Dark/check-alt.svg delete mode 100644 src/stylesheets/Paper/Dark/check.svg delete mode 100644 src/stylesheets/Paper/Dark/cross.svg delete mode 100644 src/stylesheets/Paper/Dark/dots.svg delete mode 100644 src/stylesheets/Paper/Dark/folder.svg delete mode 100644 src/stylesheets/Paper/Dark/heart.svg delete mode 100644 src/stylesheets/Paper/Dark/highlight.svg delete mode 100644 src/stylesheets/Paper/Dark/refresh.svg delete mode 100644 src/stylesheets/Paper/Dark/restore-alt.svg delete mode 100644 src/stylesheets/Paper/Dark/restore.svg delete mode 100644 src/stylesheets/Paper/Dark/run.svg delete mode 100644 src/stylesheets/Paper/Dark/shortcut.svg delete mode 100644 src/stylesheets/Paper/Dark/sort.svg delete mode 100644 src/stylesheets/Paper/Dark/unchecked-alt.svg delete mode 100644 src/stylesheets/Paper/Dark/unchecked-disabled.svg delete mode 100644 src/stylesheets/Paper/Dark/unchecked.svg delete mode 100644 src/stylesheets/Paper/Light/Arrows/down.svg delete mode 100644 src/stylesheets/Paper/Light/Arrows/left.svg delete mode 100644 src/stylesheets/Paper/Light/Arrows/right.svg delete mode 100644 src/stylesheets/Paper/Light/Arrows/up.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/archives.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/executables.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/help.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/instances.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/nexus.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/problems.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/profiles.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/settings.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/tools.svg delete mode 100644 src/stylesheets/Paper/Light/Toolbar/update.svg delete mode 100644 src/stylesheets/Paper/Light/backup.svg delete mode 100644 src/stylesheets/Paper/Light/check-alt.svg delete mode 100644 src/stylesheets/Paper/Light/check-white.svg delete mode 100644 src/stylesheets/Paper/Light/check.svg delete mode 100644 src/stylesheets/Paper/Light/cross.svg delete mode 100644 src/stylesheets/Paper/Light/dots.svg delete mode 100644 src/stylesheets/Paper/Light/folder.svg delete mode 100644 src/stylesheets/Paper/Light/heart.svg delete mode 100644 src/stylesheets/Paper/Light/refresh.svg delete mode 100644 src/stylesheets/Paper/Light/restore-alt.svg delete mode 100644 src/stylesheets/Paper/Light/restore.svg delete mode 100644 src/stylesheets/Paper/Light/run.svg delete mode 100644 src/stylesheets/Paper/Light/shortcut.svg delete mode 100644 src/stylesheets/Paper/Light/sort.svg delete mode 100644 src/stylesheets/Paper/Light/unchecked-alt.svg delete mode 100644 src/stylesheets/Paper/Light/unchecked-disabled.svg delete mode 100644 src/stylesheets/Paper/Light/unchecked-hover.svg delete mode 100644 src/stylesheets/Paper/Light/unchecked.svg (limited to 'src') diff --git a/src/stylesheets/Paper Automata.qss b/src/stylesheets/Paper Automata.qss deleted file mode 100644 index 7c0a604e..00000000 --- a/src/stylesheets/Paper Automata.qss +++ /dev/null @@ -1,1002 +0,0 @@ -/* v2.0 Paper Automata by 6788-00 */ -/* https://6788-00.tumblr.com/ */ - -/* Color Palette */ -/* Background - Main | #CDC8B0 */ -/* Background - Content | #DAD4BB */ -/* Hover | #B4AF9A */ -/* Selected | #4E4B42 */ -/* Accent | #CD664D */ - -/* All */ - -* { - color: #4E4B42; - font-size: 13px; -} - -/* Main Window */ - -QMainWindow, -QDialog { - /* most windows */ - background: url(./Paper/Automata/background.svg); -} - -QWidget:disabled { - /* disabled parts of the window like the update button when there are no updates */ - background: url(./Paper/Automata/background.svg); - color: #B4AF9A; -} - -QSplitter { - /* for resizing the left pane, right pane, or log console */ - height: 2px; - width: 8px; -} - -QSplitter::handle:horizontal { - /* horizontal handle */ - background: transparent; -} - -QSplitter::handle { - /* actual visible handle */ - background: #4E4B42; - margin-top: 6px; -} - -QAbstractItemView { - /* left and right pane container */ - background: #DAD4BB; - alternate-background-color: transparent; - show-decoration-selected: 1; - selection-background-color: #B4AF9A; - selection-color: #4E4B42; -} - -QAbstractItemView::item { - min-height: 22px; -} - -QAbstractItemView::item:hover { - /* rows on left and right pane when moused-over */ - background: #B4AF9A; -} - -QAbstractItemView::item:selected { - /* rows on left and right pane when clicked */ - background: #4E4B42; - color: #CDC8B0; -} - -QAbstractScrollArea::corner { - /* corner between where a vertical scrollbar and a horizontal scrollbar meet */ - background: #4E4B42; - border: 2px solid #4E4B42; - margin: 0px -2px -2px 0px; -} - -/* Toolbar */ - -QToolBar { - /* top toolbar; */ - border-bottom: 2px solid #4E4B42; - margin-left: 12px; - margin-right: 12px; -} - -QToolBar QWidget { - background: transparent; - margin: 0px; -} - -QToolButton { - /* toolbar buttons */ - padding: 4px 6px; - border: 2px solid transparent; - margin: 4px 4px 0px 4px; -} - -QToolButton:hover, -QToolButton:pressed { - background: #B4AF9A; - border-bottom: 2px solid #4E4B42; -} - -QToolButton:menu-indicator { - /* expandable indicator on toolbar buttons */ - image: url(./Paper/Automata/Arrows/down.svg); - margin: 4px; -} - -QToolButton QMenu { - /* toolbar button dropdown menus */ - margin: 0px; -} - -/* Toolbar Button Icons */ - -#actionChange_Game { - qproperty-icon: url(./Paper/Automata/Toolbar/instances.svg); -} - -#actionInstallMod { - qproperty-icon: url(./Paper/Automata/Toolbar/archives.svg); -} - -#actionNexus { - qproperty-icon: url(./Paper/Automata/Toolbar/nexus.svg); -} - -#actionAdd_Profile { - qproperty-icon: url(./Paper/Automata/Toolbar/profiles.svg); -} - -#actionModify_Executables { - qproperty-icon: url(./Paper/Automata/Toolbar/executables.svg); -} - -#actionTool { - qproperty-icon: url(./Paper/Automata/Toolbar/tools.svg); -} - -#actionSettings { - qproperty-icon: url(./Paper/Automata/Toolbar/settings.svg); -} - -#actionNotifications { - qproperty-icon: url(./Paper/Automata/Toolbar/problems.svg); -} - -#actionUpdate { - qproperty-icon: url(./Paper/Automata/Toolbar/update.svg); -} - -#actionHelp { - qproperty-icon: url(./Paper/Automata/Toolbar/help.svg); -} - -/* Left Pane & File Trees */ - -QTreeView { - /* left pane and right pane under QAbstractItemView*/ - border: none; -} - -QTreeView::branch:has-siblings:!adjoins-item{ - background: none; -} - -QTreeView::branch:closed:has-children:has-siblings, -QTreeView::branch:closed:has-children:!has-siblings { - /* a branch that is closed */ - image: url(./Paper/Automata/collapsed.svg) center no-repeat; -} - -QTreeView::branch:open:has-children:has-siblings, -QTreeView::branch:open:has-children:!has-siblings { - /* a branch that is open */ - image: url(./Paper/Automata/expanded.svg) center no-repeat; -} - -QTreeView::branch:hover { - background: #B4AF9A; -} - -QTreeView::branch:selected { - /* rows on the left pane when clicked (below QAbstractItemView, i.e. to the left of the checkbox) */ - background: #4E4B42; - color: #CDC8B0; -} - -QTreeView::item:selected { - /* entry on left pane when clicked */ - background: #4E4B42; - color: #CDC8B0; -} - -QListView { - /* saves window */ - border: none; -} - -QListView::item:hover { - /* uncertain, assumed: rows on the saves window when moused-over */ - background: #B4AF9A; -} - -QListView::item:selected { - /*uncertain, assumed: rows on the saves window when clicked */ - background: #4E4B42; - color: #FFFFFF; -} - -QTextEdit { - /* large text fields */ - background: #DAD4BB; - border: none; -} - -QWebView { - /* Nexus Info window */ - background: #DAD4BB; - border-radius: 0px; -} - -/* Group Boxes */ - -QGroupBox { - /* boxes that group multiple elements together (e.g. on Settings) */ - padding: 24px 4px; - border: none; -} - -QGroupBox::title { - /* title of group boxes */ - background: transparent; - subcontrol-origin: padding; - subcontrol-position: top left; - padding: 4px 8px; -} - -/* Text Fields */ - -QLineEdit { - /* text fields like NameFilter and directory fields */ - background: #DAD4BB; - min-height: 14px; - padding: 2px; - border: 2px solid #DAD4BB; - border-radius: 0px; -} - -QLineEdit:hover { - /* text fields when moused-over */ - background: 2px solid #B4AF9A; - border: 2px solid #B4AF9A; -} - -/* Most Dropdown Menus */ - -QComboBox { - /* dropdown menus */ - background: #DAD4BB; - min-height: 20px; - padding-left: 5px; - border: 2px solid #DAD4BB; - border-radius: 0px; - margin: 4px 0px; -} - -QComboBox:hover { - /* dropdown menus when moused-over */ - background: 2px solid #B4AF9A; - border: 2px solid #B4AF9A; -} - -QComboBox:on { - /* dropdown menus when expanded */ - background: #4E4B42; - color: #DAD4BB; - border: 2px solid #4E4B42; -} - -QComboBox::drop-down { - /* area for expandable indicator */ - width: 20px; - subcontrol-origin: padding; - subcontrol-position: top right; - border: none; -} - -QComboBox QAbstractItemView { - /* actual menu that expands */ - background: #DAD4BB; - border: 2px solid #CDC8B0; - border-radius: 0px; -} - -QComboBox::down-arrow { - /* expandable indicator */ - image: url(./Paper/Automata/Arrows/down.svg); -} - -/* Most Buttons */ - -QPushButton { - /* most buttons */ - background: #B4AF9A; - min-height: 20px; - padding: 2px 12px; - border-radius: 0px; -} - -QPushButton:disabled { - /* most buttons when disabled */ - background: transparent; - border: 2px solid #DAD4BB; -} - -QPushButton:hover { - /* most buttons when hovered */ - background: #DAD4BB; -} - -QPushButton:pressed { - /* most buttons when clicked */ - background: #4E4B42; - color: #DAD4BB; -} - -QPushButton::menu-indicator { - /* expandable indicator for most buttons */ - subcontrol-position: right center; - image: url(./Paper/Automata/Arrows/down.svg); - padding: 2px; - margin: 4px 4px; -} - -/* Icons */ - -#listOptionsBtn { - /* Options button */ - qproperty-icon: url(./Paper/Automata/dots.svg); - qproperty-iconSize: 16px; - padding-left: 2px; -} - -#openFolderMenu { - /* Open Folder button */ - qproperty-icon: url(./Paper/Automata/folder.svg); - qproperty-iconSize: 14px; - padding-left: 4px; -} - -#restoreModsButton, -#restoreButton { - /* Restore Backup buttons */ - qproperty-icon: url(./Paper/Automata/restore.svg); - qproperty-iconSize: 14px; -} - -#saveModsButton, -#saveButton { - /* Backup buttons */ - qproperty-icon: url(./Paper/Automata/backup.svg); - qproperty-iconSize: 14px; -} - -#bossButton { - /* Sort button */ - qproperty-icon: url(./Paper/Automata/sort.svg); - qproperty-iconSize: 14px; -} - -#linkButton { - /* Shortcuts button */ - qproperty-icon: url(./Paper/Automata/shortcut.svg); - qproperty-iconSize: 14px; -} - -#btnRefreshData, -#refreshButton { - /* Refresh buttons */ - qproperty-icon: url(./Paper/Automata/refresh.svg); - qproperty-iconSize: 14px; -} - -#endorseBtn { - /* Endorse button on the Nexus Info tab of the Information window */ - qproperty-icon: url(./Paper/Automata/heart.svg); - qproperty-iconSize: 14px; -} - -#clearCacheButton { - /* Clear Cache button on the Nexus tab of the Settings window */ - qproperty-icon: url(./Paper/Automata/cross.svg); - qproperty-iconSize: 14px; -} - -#deactivateESP, -#activateESP { - /* activate and deactivate ESP buttons */ - background: #DAD4BB; -} - -#deactivateESP:hover, -#activateESP:hover { - /* activate and deactivate ESP buttons when moused-over */ - background: #B4AF9A; -} - -#deactivateESP { - /* icon for the deactivate ESP button */ - qproperty-icon: url(./Paper/Automata/backup.svg); -} - -#activateESP { - /* icon for the activate ESP button */ - qproperty-icon: url(./Paper/Automata/restore-alt.svg); -} - -/* Run button */ - -#startButton { - /* Run button */ - background: #4E4B42; - color: #DAD4BB; - qproperty-icon: url(./Paper/Automata/run.svg); - qproperty-iconSize: 30px; - padding: 6px; -} - -#startButton:hover { - /* Run button when moused-over*/ - background: #B4AF9A; - color: #4E4B42; -} - -/* Scroll Bars */ - -/* Horizontal */ - -QScrollBar:horizontal { - /* horizontal scroll bar */ - background: #DAD4BB; - height: 18px; - border: 2px solid #DAD4BB; - margin: 0px 23px -2px 23px; -} - -QScrollBar::handle:horizontal { - /* handle for horizontal scroll bars */ - background: #4E4B42; - min-width: 32px; - margin: 2px; -} - -QScrollBar::add-line:horizontal { - /* scroll right button */ - background: #DAD4BB; - image: url(./Paper/Automata/Arrows/right.svg); - width: 23px; - subcontrol-position: right; - subcontrol-origin: margin; - border: 2px solid #CDC8B0; - margin: 0px -2px -2px 0px; -} - -QScrollBar::sub-line:horizontal { - /* scroll left button */ - background: #DAD4BB; - image: url(./Paper/Automata/Arrows/left.svg); - width: 23px; - subcontrol-position: left; - subcontrol-origin: margin; - border: 2px solid #CDC8B0; - margin: 0px 0px -2px -2px; -} - -/* Vertical */ - -QScrollBar:vertical { - /* vertical scroll bar */ - background: #DAD4BB; - width: 18px; - border: 2px solid #CDC8B0; - margin: 23px -2px 23px 0px; -} - -QScrollBar::handle:vertical { - /* handle for vertical scroll bars */ - background: #4E4B42; - min-height: 32px; - margin: 2px; -} - -QScrollBar::add-line:vertical { - /* scroll down button */ - background: #DAD4BB; - image: url(./Paper/Automata/Arrows/down.svg); - height: 23px; - subcontrol-position: bottom; - subcontrol-origin: margin; - border: 2px solid #CDC8B0; - border-bottom-right-radius: 0px; - margin: 0px -2px -2px 0px; -} - -QScrollBar::sub-line:vertical { - /* scroll up button */ - background: #B4AF9A; - image: url(./Paper/Automata/Arrows/up.svg); - height: 23px; - subcontrol-position: top; - subcontrol-origin: margin; - border: 2px solid #CDC8B0; - border-top-right-radius: 0px; - margin: -2px -2px 0px 0px; -} - -/* Combined */ - -QScrollBar::handle:horizontal:hover, -QScrollBar::handle:vertical:hover, -QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, -QScrollBar::sub-line:vertical:hover { - /* buttons and handles when moused-over */ - background: #B4AF9A; -} - -QScrollBar::handle:horizontal:pressed, -QScrollBar::handle:vertical:pressed, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, -QScrollBar::sub-line:vertical:pressed { - /* buttons and handles when clicked */ - background: #4E4B42; -} - -QScrollBar::add-page:horizontal, -QScrollBar::sub-page:horizontal, -QScrollBar::add-page:vertical, -QScrollBar::sub-page:vertical { - /* area on scroll bars where clicking it scrolls to where you clicked */ - background: transparent; -} - -/* Header Rows */ - -QHeaderView { - /* header row (i.e. Mod Name, Flags, Category, etc.) */ - background: #B4AF9A; -} - -QHeaderView::section { - /* each section on the header row (i.e. Mod name is one section and Flags another) */ - background: #B4AF9A; - height: 22px; - padding: 0px 5px; - border: 0px; - border-bottom: 2px solid #CDC8B0; - border-right: 2px solid #CDC8B0; -} - -QHeaderView::section:hover { - /* a section on a header row when hovered */ - background: #B4AF9A; -} - -QHeaderView::up-arrow { - /* ascending sort indicator */ - image: url(./Paper/Automata/Arrows/up.svg); - padding-right: 4px; - height: 10px; - width: 10px; -} - -QHeaderView::down-arrow { - /* descending sort indicator */ - image: url(./Paper/Automata/Arrows/down.svg); - padding-right: 4px; - height: 10px; - width: 10px; -} - -QHeaderView::section:last { - margin-right: -2px; -} - -/* Right Click Menus, Toolbar Dropdown Menus, & Tooltips */ - -QMenuBar { - background: #DAD4BB; - border: 2px solid #CDC8B0; -} - -QMenuBar::item:selected { - background: #B4AF9A; - border: none; -} - -QMenu { - /* right click menu */ - background: #DAD4BB; - border: 2px solid #CDC8B0; -} - -QMenu::item { - /* rows on right click menus */ - background: #DAD4BB; - padding: 5px 20px 5px 24px; -} - -QMenu::item:selected { - /* rows on right click menus when moused-over (i dunno) */ - background: #B4AF9A; - border: none; -} - -QMenu::item:disabled { - /* unavailable rows on right click menus */ - background: #CDC8B0; - color: #B4AF9A; -} - -QMenu::separator { - /* seperators on right click menus */ - height: 2px; - background: #CDC8B0; -} - -QMenu::icon { - /* area for icons on right click menus */ - padding: 4px; -} - -QMenu::right-arrow { - /* submenu indicator */ - image: url(./Paper/Automata/Arrows/right.svg); - padding-right: 5px; -} - -QMenu QPushButton { - /* Change Categories and Primary Categories buttons */ - background: #DAD4BB; - padding: 2px 24px; - text-align: left; - border: none; -} - -QMenu QPushButton:hover { - /* Change Categories and Primary Categories buttons when moused-over */ - background: #B4AF9A; - border: none; -} - -QMenu QCheckBox { - /* checkboxes on right click menus (change categories)*/ - background: #DAD4BB; - padding: 2px 6px; -} - -QMenu QCheckBox:hover { - /* checkboxes on right click menus when moused-over (change categories) */ - background: #B4AF9A; -} - -QMenu QRadioButton { - /* radio buttons on right click menus (primary categories) */ - background: #DAD4BB; - padding: 2px 6px; -} - -QToolTip { - /* all tooltips */ - background: #DAD4BB; - border: 2px solid #CDC8B0; -} - -QStatusBar::item {border: None;} - -/* Progress Bars (Downloads) */ - -QProgressBar { - /* progress bars when downloading */ - background: transparent; - text-align: center; - border: 0px; - margin: 0px 10px; -} - -QProgressBar::chunk { - /* the loading part that moves on progress bars */ - background: #CD664D; -} - -/* Right Pane and Tab Bars */ - -QTabWidget::pane { - /* right pane */ - top: 1px; - padding: 2px 2px 3px 2px; - border-top: 2px solid #4E4B42; -} - -QTabWidget::tab-bar { - /* tabs */ - alignment: center; -} - -QTabBar::tab { - /* a tab */ - background: #B4AF9A; - padding: 4px 1em; - border: 2px solid #DAD4BB; - margin: 3px 1px; -} - -QTabBar::tab:!selected { - /* an unselected tab */ - background: #B4AF9A; - border: 2px solid #B4AF9A; -} - -QTabBar::tab:disabled { - /* An unavailable tab */ - background: transparent; - color: #B4AF9A; - border: transparent; -} - -QTabBar::tab:selected { - /* a clicked tab */ - color: #CDC8B0; - background: #4E4B42; - border: 2px solid #4E4B42; -} - -QTabBar::tab:hover { - /* a tab when hovered */ - background: #DAD4BB; - color: #4E4B42; - border: 2px solid #DAD4BB; -} - -QTabBar QToolButton { - /* buttons to scroll between more tabs on a tab bar */ - background: #CD664D; - padding: 0px; - margin: 3px; -} - -QTabBar QToolButton:disabled { - /* buttons to scroll on a tab bar when it's unavailable */ - background: transparent; - border: 2px solid transparent; -} - -QLCDNumber { - /* LCD number on the Conflicts tab */ - background: #DAD4BB; - color: #4E4B42; - border: none; -} - -/* Tables (Configure Mod Categories) */ - -QTableView { - /* tables */ - gridline-color: #CDC8B0; - border: 0px; -} - -/* Checkboxes */ - -QCheckBox::indicator { - /* a checkbox */ - width: 12px; - height: 12px; -} - -QCheckBox::indicator:disabled, -QRadioButton::indicator:disabled { - /* a checkbox that is disabled */ - background: none; - border: 2px solid #B4AF9A; -} - -QTreeView::indicator:unchecked, -QCheckBox::indicator:unchecked, -QGroupBox::indicator:unchecked, -QRadioButton::indicator:unchecked { - /* a checkbox when unchecked */ - border: 2px solid #4E4B42; -} - -QCheckBox::indicator:unchecked:hover, -QRadioButton::indicator:unchecked:hover { - /* a checkbox that is unchecked when moused-over */ - background: #B4AF9A; -} - -QTreeView::indicator:unchecked:selected, -QRadioButton::indicator:unchecked:selected { - /* a checkbox that is unchecked when clicked */ - border: 2px solid #DAD4BB; -} - -QTreeView::indicator:checked, -QCheckBox::indicator:checked, -QGroupBox::indicator:checked, -QRadioButton::indicator:checked { - /* a checkbox when checked */ - background: #CD664D; - border: 2px solid #CD664D; -} - -QCheckBox::indicator:checked:hover, -QRadioButton::indicator:checked:hover { - /* a checkbox that is checked when moused-over */ - border: 2px solid #4E4B42; -} - -/* Spinboxes */ - -QSpinBox, -QDoubleSpinBox { - /* usually boxes for selecting numbers */ - min-height: 24px; - min-width: 60px; - background: #DAD4BB; - padding: 0px 2px; - border: 2px solid #CDC8B0; - margin: 0px -4px; -} - -QSpinBox::up-button, -QDoubleSpinBox::up-button { - /* up button on spinboxes */ - min-height: 28px; - min-width: 18px; - subcontrol-position: center right; - border: 2px solid #CDC8B0; -} - -QSpinBox::up-arrow, -QDoubleSpinBox::up-arrow { - /* arrow for the up button */ - image: url(./Paper/Automata/Arrows/up.svg); -} - -QSpinBox::up-button:hover, -QDoubleSpinBox::up-button:hover { - /* up button on spinboxes when moused-over */ - background: #B4AF9A; -} - -QSpinBox::down-button, -QDoubleSpinBox::down-button { - /* down button on spinboxes */ - min-height: 28px; - min-width: 18px; - subcontrol-position: center left; - border: 2px solid #CDC8B0; -} - -QSpinBox::down-arrow, -QDoubleSpinBox::down-arrow { - /* arrow for the up button */ - image: url(./Paper/Automata/Arrows/down.svg); -} - -QSpinBox::down-button:hover, -QDoubleSpinBox::down-button:hover { - /* down button on spinboxes when moused-over */ - background: #B4AF9A; -} - -/* Sliders */ - -QSlider::groove { - /* sliders */ - height: 0px; - border: 1px solid #B4AF9A; -} - -QSlider::handle { - /* slider handles */ - background: #DAD4BB; - border: 2px solid #4E4B42; - border-radius: 0px; - margin: -10px; -} - -QSlider::handle:hover { - /* Slider handles when moused-over */ - background: #4E4B42; -} - -/* Downloads Tab */ - -#downloadTab QAbstractScrollArea, -DownloadListWidget { - /* background of the entire downloads tab */ - background: #DAD4BB; -} - -DownloadListWidget QFrame, -DownloadListWidgetCompact, -DownloadListWidgetCompact QLabel { - /* an entry on the Downloads tab */ - background: #DAD4BB; -} - -DownloadListWidget#frame { - /* outer box of an entry on the Downloads tab */ - border: 2px solid #DAD4BB; -} - -#installLabel { - /* installed/done label */ - color: none; -} - -DownloadListWidgetCompact, -DownloadListWidgetCompact QLabel { - /* an entry on the Downloads tab */ - background: #DAD4BB; -} - -/* New Downloads View */ - -DownloadListWidget[downloadView=standard]::item { - /* Entries on the Standard Downloads View */ - min-height: 44px; - margin: -16px 0; -} - -DownloadListWidget[downloadView=compact]::item { - /* Entries on the Compact Downloads View */ - min-height: 22px; - margin: -4px 0; -} - -QProgressBar[downloadView=standard] { - /* Progress Bars on the Standard Downloads View */ - background: transparent; - margin: 11px 0; -} - -QProgressBar[downloadView=compact] { - /* Progress Bars on the Compact Downloads View */ - background: transparent; -} - -/* Categories Filter */ - -#displayCategoriesBtn { - /* Filter button */ - min-width: 12px; -} - -#categoriesList { - /* Categories panel */ - min-width: 200px; - margin-bottom: 4px; -} - -#categoriesGroup { - /* Categories group box */ - padding-bottom: 0; - margin-top: 3px; -} - -/* Fixes */ - -#executablesListBox { - /* Increase right margin of the select executables box */ - margin-right: 8px; -} - -#stepsStack QWidget { - /* Groupboxes on the FOMOD Installer Dialog */ - background: #CDC8B0; - border: none; -} - -#stepsStack QGroupBox { - /* Fix to reimplement styling for Groupboxes on the FOMOD Installer dialog */ - border: 2px solid #4E4B42; -} - -#activeModslabel, #activePluginsLabel { - /* Increase the left margin of the counters */ - padding-left: 6px; -} - -/* For the Glory of Mankind */ diff --git a/src/stylesheets/Paper Dark by 6788.qss b/src/stylesheets/Paper Dark by 6788.qss deleted file mode 100644 index 6043ae6b..00000000 --- a/src/stylesheets/Paper Dark by 6788.qss +++ /dev/null @@ -1,1030 +0,0 @@ -/* v5.0 Paper Dark by 6788-00 */ -/* https://6788-00.tumblr.com/ */ - -/* Color Palette */ -/* Background - Main | #242424 */ -/* Background - Content | #141414 */ -/* Background - Alternate | #1C1C1C */ -/* Hover | #3D3D3D */ -/* Selected | #006868 */ - -/* All */ - -* { - color: #D3D3D3; - font-size: 12px; -} - -/* Main Window */ - -QWidget { - /* most of the window */ - background: #242424; - color: #D3D3D3; -} - -QWidget:disabled { - /* disabled parts of the window like the update button when there are no updates */ - background: #242424; - color: #808080; -} - -QAbstractItemView { - /* left and right pane container */ - background: #141414; - alternate-background-color: #1C1C1C; - show-decoration-selected: 1; - selection-background-color: #006868; - selection-color: #FFFFFF; -} - -QAbstractItemView::item { - min-height: 22px; -} - -QAbstractItemView::item:hover { - /* rows on left and right pane when moused-over */ - background: #3D3D3D; - color: #FFFFFF; -} - -QAbstractItemView::item:selected { - /* rows on left and right pane when clicked */ - background: #006868; - color: #FFFFFF; -} - -QAbstractScrollArea::corner { - /* corner between where a vertical scrollbar and a horizontal scrollbar meet */ - background: #141414; - border: 2px solid #242424; - border-bottom-right-radius: 6px; - margin: 0px -2px -2px 0px; -} - -QSplitter { - width: 8px; -} - -LinkLabel { - qproperty-linkColor: #3399FF; -} - -/* Toolbar */ - -QToolBar { - /* top toolbar; */ - background: #242424; - border: 1px solid #242424; -} - -QToolBar::separator { - /* uncertain, assumed: vertical seperator on toolbar left of the warnings button*/ - background: #242424; - margin: 6px 8px; -} - -QToolButton { - /* toolbar buttons */ - padding: 6px; - border-radius: 10px; - margin: 4px 4px 0px 4px; -} - -QToolButton:hover { - /* toolbar buttons when moused-over; */ - background: #006868; -} - -QToolButton:pressed { - /* toolbar buttons when clicked; */ - background: #006868; -} - -QToolButton:menu-indicator { - /* expandable indicator on toolbar buttons */ - image: url(./Paper/Dark/Arrows/down.svg); - margin: 4px; -} - -/* Toolbar Button Icons */ - -#actionChange_Game { - qproperty-icon: url(./Paper/Dark/Toolbar/instances.svg); -} - -#actionInstallMod { - qproperty-icon: url(./Paper/Dark/Toolbar/archives.svg); -} - -#actionNexus { - qproperty-icon: url(./Paper/Dark/Toolbar/nexus.svg); -} - -#actionAdd_Profile { - qproperty-icon: url(./Paper/Dark/Toolbar/profiles.svg); -} - -#actionModify_Executables { - qproperty-icon: url(./Paper/Dark/Toolbar/executables.svg); -} - -#actionTool { - qproperty-icon: url(./Paper/Dark/Toolbar/tools.svg); -} - -#actionSettings { - qproperty-icon: url(./Paper/Dark/Toolbar/settings.svg); -} - -#actionNotifications { - qproperty-icon: url(./Paper/Dark/Toolbar/problems.svg); -} - -#actionUpdate { - qproperty-icon: url(./Paper/Dark/Toolbar/update.svg); -} - -#actionHelp { - qproperty-icon: url(./Paper/Dark/Toolbar/help.svg); -} - -/* Left Pane & File Trees */ - -ModListView, PluginListView { - /* Mods List and Plugins List specifically */ - margin: 2px 0px; -} - -QTreeView { - /* left pane and right pane under QAbstractItemView*/ - border-radius: 6px; -} - -QTreeView::branch:hover { - /* rows on the left pane when moused-over (below QAbstractItemView, i.e. to the left of the checkbox) */ - background: #3A3A3A; - color: #FFFFFF; -} - -QTreeView::branch:selected { - /* rows on the left pane when clicked (below QAbstractItemView, i.e. to the left of the checkbox) */ - background: #006868; - color: #FFFFFF; -} - -QTreeView::branch:closed:has-children:has-siblings, -QTreeView::branch:closed:has-children:!has-siblings { - /* a branch that is closed */ - image: url(./Paper/Dark/Arrows/right.svg); -} - -QTreeView::branch:open:has-children:has-siblings, -QTreeView::branch:open:has-children:!has-siblings { - /* a branch that is open */ - image: url(./Paper/Dark/Arrows/down.svg); -} - -QListView { - /* saves window */ - border-radius: 6px; -} - -QListView::item:hover { - /* a row on the saves tab when moused-over */ - background: #3D3D3D; - color: #FFFFFF; - padding: 0; -} - -QListView::item:selected { - /* a row on the saves tab when clicked */ - background: #006868; - color: #FFFFFF; - padding: 0; -} - -QTextEdit { - /* large text fields */ - background: #141414; - border-radius: 6px; -} - -/* Group Boxes */ - -QGroupBox { - /* boxes that group multiple elements together (e.g. on Settings) */ - padding: 24px 4px; - border: 2px solid #141414; - border-radius: 10px; -} - -QGroupBox::title { - /* title of group boxes */ - background: transparent; - subcontrol-origin: padding; - subcontrol-position: top left; - padding: 8px; -} - -/* Text Fields */ - -QLineEdit { - /* text fields like NameFilter and directory fields */ - background: #141414; - min-height: 14px; - padding: 2px; - border: 2px solid #141414; - border-radius: 6px; -} - -QLineEdit:hover { - /* text fields when moused-over */ - border: 2px solid #006868; -} - -/* Most Dropdown Menus */ - -QComboBox { - /* dropdown menus */ - background: #141414; - min-height: 20px; - padding-left: 5px; - border: 2px solid #141414; - border-radius: 6px; - margin: 4px 0px; -} - -QComboBox:hover { - /* dropdown menus when moused-over */ - border: 2px solid #006868; -} - -QComboBox:on { - /* dropdown menus when expanded */ - background: #006868; - color: #FFFFFF; - border: 2px solid #006868; -} - -QComboBox::drop-down { - /* area for expandable indicator */ - width: 20px; - subcontrol-origin: padding; - subcontrol-position: top right; - border: none; -} - -QComboBox QAbstractItemView { - /* actual menu that expands */ - background: #141414; - border: 2px solid #242424; - border-radius: 6px; -} - -QComboBox::down-arrow { - /* expandable indicator */ - image: url(./Paper/Dark/Arrows/down.svg); -} - -/* Most Buttons */ - -QPushButton { - /* most buttons */ - background: #141414; - color: #D3D3D3; - min-height: 20px; - padding: 2px 12px; - border-radius: 6px; - margin: 2px 0px; -} - -QPushButton:disabled { - /* most buttons when disabled */ - border: 2px solid #141414; -} - -QPushButton:hover { - /* most buttons when hovered */ - background: #006868; - color: #FFFFFF; -} - -QPushButton:pressed { - /* most buttons when clicked */ - background: #006868; - color: #FFFFFF; -} - -QPushButton::menu-indicator { - /* expandable indicator for most buttons */ - subcontrol-position: right center; - image: url(./Paper/Dark/Arrows/down.svg); - padding: 2px; - margin: 4px 4px; -} - -/* Icons */ - -#listOptionsBtn { - /* Options button */ - qproperty-icon: url(./Paper/Dark/dots.svg); - qproperty-iconSize: 16px; - padding-left: 2px; -} - -#openFolderMenu { - /* Open Folder button */ - qproperty-icon: url(./Paper/Dark/folder.svg); - qproperty-iconSize: 14px; - padding-left: 4px; -} - -#restoreModsButton, -#restoreButton { - /* Restore Backup buttons */ - qproperty-icon: url(./Paper/Dark/restore.svg); - qproperty-iconSize: 14px; -} - -#saveModsButton, -#saveButton { - /* Backup buttons */ - qproperty-icon: url(./Paper/Dark/backup.svg); - qproperty-iconSize: 14px; -} - -#bossButton { - /* Sort button */ - qproperty-icon: url(./Paper/Dark/sort.svg); - qproperty-iconSize: 14px; -} - -#linkButton { - /* Shortcuts button */ - qproperty-icon: url(./Paper/Dark/shortcut.svg); - qproperty-iconSize: 14px; -} - -#refreshButton, -#btnRefreshData, -#btnRefreshDownloads { - /* Refresh buttons */ - qproperty-icon: url(./Paper/Dark/refresh.svg); - qproperty-iconSize: 14px; -} - -#endorseBtn { - /* Endorse button on the Nexus Info tab of the Information window */ - qproperty-icon: url(./Paper/Dark/heart.svg); - qproperty-iconSize: 14px; -} - -#clearCacheButton { - /* Clear Cache button on the Nexus tab of the Settings window */ - qproperty-icon: url(./Paper/Dark/cross.svg); - qproperty-iconSize: 14px; -} - -#deactivateESP, -#activateESP { - /* activate and deactivate ESP buttons */ - background: #141414; -} - -#deactivateESP:hover, -#activateESP:hover { - /* activate and deactivate ESP buttons when moused-over */ - background: #006868; -} - -#deactivateESP { - /* icon for the deactivate ESP button */ - qproperty-icon: url(./Paper/Dark/backup.svg); -} - -#activateESP { - /* icon for the activate ESP button */ - qproperty-icon: url(./Paper/Dark/restore-alt.svg); -} - -/* Run button */ - -#startButton { - /* Run button */ - background: #006868; - color: #FFFFFF; - qproperty-icon: url(./Paper/Dark/run.svg); - qproperty-iconSize: 30px; - padding: 6px; -} - -#startButton:hover { - /* Run button when moused-over*/ - background: #3D3D3D; -} - -/* Scroll Bars */ - -/* Horizontal */ - -QScrollBar:horizontal { - /* horizontal scroll bar */ - background: #141414; - height: 20px; - border: 2px solid #242424; - margin: 0px 23px -2px 23px; -} - -QScrollBar::handle:horizontal { - /* handle for horizontal scroll bars */ - background: #3D3D3D; - min-width: 32px; - border-radius: 6px; - margin: 2px; -} - -QScrollBar::add-line:horizontal { - /* scroll right button */ - background: #141414; - image: url(./Paper/Dark/Arrows/right.svg); - width: 23px; - subcontrol-position: right; - subcontrol-origin: margin; - border: 2px solid #242424; - margin: 0px -2px -2px 0px; -} - -QScrollBar::sub-line:horizontal { - /* scroll left button */ - background: #141414; - image: url(./Paper/Dark/Arrows/left.svg); - width: 23px; - subcontrol-position: left; - subcontrol-origin: margin; - border: 2px solid #242424; - border-bottom-left-radius: 6px; - margin: 0px 0px -2px -2px; -} - -/* Vertical */ - -QScrollBar:vertical { - /* vertical scroll bar */ - background: #141414; - width: 20px; - border: 2px solid #242424; - margin: 23px -2px 23px 0px; -} - -QScrollBar::handle:vertical { - /* handle for vertical scroll bars */ - background: #3D3D3D; - min-height: 32px; - border-radius: 6px; - margin: 2px; -} - -QScrollBar::add-line:vertical { - /* scroll down button */ - background: #141414; - image: url(./Paper/Dark/Arrows/down.svg); - height: 23px; - subcontrol-position: bottom; - subcontrol-origin: margin; - border: 2px solid #242424; - border-bottom-right-radius: 6px; - margin: 0px -2px -2px 0px; -} - -QScrollBar::sub-line:vertical { - /* scroll up button */ - background: #141414; - image: url(./Paper/Dark/Arrows/up.svg); - height: 23px; - subcontrol-position: top; - subcontrol-origin: margin; - border: 2px solid #242424; - border-top-right-radius: 6px; - margin: -2px -2px 0px 0px; -} - -/* Combined */ - -QScrollBar::handle:horizontal:hover, -QScrollBar::handle:vertical:hover, -QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, -QScrollBar::sub-line:vertical:hover { - /* buttons and handles when moused-over */ - background: #006868; -} - -QScrollBar::handle:horizontal:pressed, -QScrollBar::handle:vertical:pressed, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, -QScrollBar::sub-line:vertical:pressed { - /* buttons and handles when clicked */ - background: #006868; -} - -QScrollBar::add-page:horizontal, -QScrollBar::sub-page:horizontal, -QScrollBar::add-page:vertical, -QScrollBar::sub-page:vertical { - /* area on scroll bars where clicking it scrolls to where you clicked */ - background: transparent; -} - -/* Header Rows */ - -QHeaderView { - /* header row (i.e. Mod Name, Flags, Category, etc.) */ - background: #242424; -} - -QHeaderView::section { - /* each section on the header row (i.e. Mod name is one section and Flags another) */ - background: #141414; - color: #D3D3D3; - height: 22px; - padding: 0px 5px; - border: 0px; - border-bottom: 2px solid #242424; - border-right: 2px solid #242424; -} - -QHeaderView::section:first { - /* first section on a header row */ - border-top-left-radius: 6px; -} - -QHeaderView::section:last { - /* last section on a header row */ - border-right: 0px; - border-top-right-radius: 6px; -} - -QHeaderView::section:hover { - /* a section on a header row when hovered */ - background: #3D3D3D; - color: #FFFFFF; -} - -QHeaderView::up-arrow { - /* ascending sort indicator */ - image: url(./Paper/Dark/Arrows/up.svg); - padding-right: 4px; - height: 10px; - width: 10px; -} - -QHeaderView::down-arrow { - /* descending sort indicator */ - image: url(./Paper/Dark/Arrows/down.svg); - padding-right: 4px; - height: 10px; - width: 10px; -} - -/* Right Click Menus, Toolbar Dropdown Menus, & Tooltips */ - -QMenuBar { - background: #242424; - border: 1px solid #242424; -} - -QMenuBar::item:selected { - background: #006868; - color: #FFFFFF; - border: 0px; - border-radius: 4px; -} - -QMenu { - /* right click menu */ - background: #141414; - border: 2px solid #242424; - border-radius: 6px; -} - -QMenu::item { - /* rows on right click menus */ - background: #141414; - padding: 5px 20px 5px 24px; -} - -QMenu::item:selected { - /* rows on right click menus when moused-over (i dunno) */ - background: #006868; - color: #FFFFFF; - border: 0px; - border-radius: 4px; -} - -QMenu::item:disabled { - /* unavailable rows on right click menus */ - background: #242424; - color: #808080; -} - -QMenu::separator { - /* seperators on right click menus */ - height: 2px; - background: #242424; -} - -QMenu::icon { - /* area for icons on right click menus */ - padding: 4px; -} - -QMenu::right-arrow { - /* submenu indicator */ - image: url(./Paper/Dark/Arrows/right.svg); - padding-right: 5px; -} - -QMenu QPushButton { - /* Change Categories and Primary Categories buttons */ - background: #141414; - color: #D3D3D3; - padding: 2px 24px; - text-align: left; - border-radius: 0px; -} - -QMenu QPushButton:hover { - /* Change Categories and Primary Categories buttons when moused-over */ - border-radius: 6px; -} - -QMenu QCheckBox { - /* checkboxes on right click menus (change categories)*/ - background: #141414; - padding: 2px 6px; -} - -QMenu QCheckBox:hover { - /* checkboxes on right click menus when moused-over (change categories) */ - background: #3D3D3D; - color: #FFFFFF; -} - -QMenu QRadioButton { - /* radio buttons on right click menus (primary categories) */ - background: #141414; - padding: 2px 6px; -} - -QToolTip { - /* all tooltips */ - background: #141414; - border: 2px solid #242424; - border-radius: 6px; -} - -QStatusBar::item {border: None;} - -/* Progress Bars (Downloads) */ - -QProgressBar { - /* progress bars when downloading */ - background: #141414; - color: #FFFFFF; - text-align: center; - border: 2px solid #242424; - border-radius: 6px; - margin: 0px 10px; -} - -QProgressBar::chunk { - /* the loading part that moves on progress bars */ - background: #006868; - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} - -/* Right Pane and Tab Bars */ - -QTabWidget::pane { - /* right pane */ - top: 1px; - padding: 2px 2px 10px 2px; - border: 2px solid #141414; - border-radius: 10px; -} - -QTabWidget::tab-bar { - /* tabs */ - alignment: center; -} - -QTabBar::tab { - /* a tab */ - background: #141414; - color: #141414; - padding: 4px 1em; - border: 2px solid #141414; - margin: 3px 1px; -} - -QTabBar::tab:!selected { - /* a tab that is not clicked */ - background: #141414; - color: #D3D3D3; - border: 2px solid #141414; -} - -QTabBar::tab:disabled { - /* a tab that is disabled */ - background: #242424; - color: #808080; - border: 2px solid transparent; -} - -QTabBar::tab:selected { - /* a tab that is clicked */ - color: #FFFFFF; - background: #006868; - border: 2px solid #006868; -} - -QTabBar::tab:hover { - /* a tab when moused-over */ - color: #FFFFFF; - background: #3D3D3D; - border: 2px solid #3D3D3D; -} - -QTabBar::tab:first { - /* that first tab */ - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; -} - -QTabBar::tab:last { - /* the last tab */ - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; -} - -QTabBar QToolButton { - /* buttons to scroll between more tabs on a tab bar */ - background: #3D3D3D; - padding: 1px; - border-radius: 6px; - margin: 1px; -} - -QTabBar QToolButton:disabled { - /* buttons to scroll on a tab bar when it's unavailable */ - background: transparent; -} - -QLCDNumber { - /* LCD number on the Conflicts tab */ - background: #141414; - color: #006868; - border-radius: 6px; - -} - -/* Tables (Configure Mod Categories) */ - -QTableView { - /* a table */ - gridline-color: #242424; - border: 0px; -} - -/* Checkboxes */ - -QTreeView::indicator:unchecked, -QCheckBox::indicator:unchecked, -QGroupBox::indicator:unchecked, -QRadioButton::indicator:unchecked { - /* a checkbox that is unchecked */ - image: url(./Paper/Dark/unchecked.svg); - width: 14px; - height: 14px; -} - -QCheckBox::indicator:unchecked:hover, -QRadioButton::indicator:unchecked:hover { - /* a checkbox that is unchecked when moused-over and clicked */ - image: url(./Paper/Dark/unchecked-alt.svg); -} - -QTreeView::indicator:checked, -QCheckBox::indicator:checked, -QGroupBox::indicator:checked, -QRadioButton::indicator:checked { - /* a checkbox that is checked */ - image: url(./Paper/Dark/check.svg); - width: 14px; - height: 14px; -} - -QTreeView::indicator:checked:selected, -QCheckBox::indicator:checked:hover, -QRadioButton::indicator:checked:hover { - /* a checkbox that is checked when moused-over and clicked */ - image: url(./Paper/Dark/check-alt.svg); -} - -QCheckBox::indicator:disabled { - /* a checkbox that is disabled */ - image: url(./Paper/Dark/unchecked-disabled.svg); -} - -/* Spinboxes */ - -QSpinBox, -QDoubleSpinBox { - /* usually boxes for selecting numbers */ - min-height: 24px; - min-width: 60px; - background: #141414; - padding: 0px 2px; - border: 2px solid #242424; - border-radius: 6px; - margin: 0px -4px; -} - -QSpinBox::up-button, -QDoubleSpinBox::up-button { - /* up button on spinboxes */ - min-height: 28px; - min-width: 20px; - subcontrol-position: center right; - border: 2px solid #242424; - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} - -QSpinBox::up-arrow, -QDoubleSpinBox::up-arrow { - /* arrow for the up button */ - image: url(./Paper/Dark/Arrows/up.svg); -} - -QSpinBox::up-button:hover, -QDoubleSpinBox::up-button:hover { - /* up button on spinboxes when moused-over */ - background: #3D3D3D; -} - -QSpinBox::down-button, -QDoubleSpinBox::down-button { - /* down button on spinboxes */ - min-height: 28px; - min-width: 20px; - subcontrol-position: center left; - border: 2px solid #242424; - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} - -QSpinBox::down-arrow, -QDoubleSpinBox::down-arrow { - /* arrow for the up button */ - image: url(./Paper/Dark/Arrows/down.svg); -} - -QSpinBox::down-button:hover, -QDoubleSpinBox::down-button:hover { - /* down button on spinboxes when moused-over */ - background: #3D3D3D; -} - -/* Sliders */ - -QSlider::groove { - /* sliders */ - height: 0px; - border: 1px solid #3D3D3D; -} - -QSlider::handle { - /* slider handles */ - background: #141414; - border: 2px solid #3D3D3D; - border-radius: 6px; - margin: -10px; -} - -QSlider::handle:hover { - /* Slider handles when moused-over */ - background: #3D3D3D; -} - -/* Pre-v2.1.7 Downloads Tab */ - -#downloadTab QAbstractScrollArea { - /* background of the entire downloads tab */ - background: #141414; -} - -DownloadListWidget QFrame, -DownloadListWidgetCompact, -DownloadListWidgetCompact QLabel { - /* an entry on the Downloads tab */ - background: #141414; -} - -DownloadListWidget#frame { - /* outer box of an entry on the Downloads tab */ - border: none; -} - -#installLabel { - /* installed/done label */ - color: none; -} - -DownloadListWidgetCompact, -DownloadListWidgetCompact QLabel { - /* an entry on the Downloads tab in Compact View */ - background: #141414; -} - -/* New Downloads View */ - -DownloadListWidget[downloadView=standard]::item { - /* Entries on the Standard Downloads View */ - min-height: 44px; - margin: -16px 0; -} - -DownloadListWidget[downloadView=compact]::item { - /* Entries on the Compact Downloads View */ - min-height: 22px; - margin: -4px 0; -} - -QProgressBar[downloadView=standard] { - /* Progress Bars on the Standard Downloads View */ - background: transparent; - margin: 11px 0; -} - -QProgressBar[downloadView=standard]::chunk, -QProgressBar[downloadView=compact]::chunk { - /* The Loading Portion of Progress Bars on the Downloads View */ - border-right: 2px solid #242424; -} - -QProgressBar[downloadView=compact] { - /* Progress Bars on the Compact Downloads View */ - background: transparent; -} - -/* Categories Filter */ - -#displayCategoriesBtn { - /* Filter button */ - min-width: 12px; -} - -#categoriesList { - /* Categories panel */ - min-width: 200px; - margin-bottom: 4px; -} - -#categoriesGroup { - /* Categories group box */ - padding-bottom: 0px; -} - -/* Fixes */ - -#executablesListBox { - /* Increase right margin of the select executables box */ - margin-right: 8px; -} - -#executablesListBox::item { - /* fixes the black text problem on the Modify Executables window */ - color: #D3D3D3; -} - -#stepsStack QWidget { - /* Groupboxes on the FOMOD Installer Dialog */ - border: none; -} - -#stepsStack QGroupBox { - /* Fix to reimplement styling for Groupboxes on the FOMOD Installer dialog */ - border: 2px solid #141414; - border-radius: 10px; -} - -#activeModslabel, #activePluginsLabel { - /* Increase the left margin of the counters */ - padding-left: 6px; -} diff --git a/src/stylesheets/Paper Light by 6788.qss b/src/stylesheets/Paper Light by 6788.qss deleted file mode 100644 index 2b08bcd1..00000000 --- a/src/stylesheets/Paper Light by 6788.qss +++ /dev/null @@ -1,1009 +0,0 @@ -/* v5.0 Paper Light by 6788-00 */ -/* https://6788-00.tumblr.com/ */ - -/* Color Palette */ -/* Background - Main | #EBEBEB */ -/* Background - Content | #FFFFFF */ -/* Background - Alternate | #F6F6F6 */ -/* Hover | #C2C2C2 */ -/* Selected | #008484 */ - -/* All */ - -* { - color: #000000; - font-size: 12px; -} - -/* Main Window */ - -QWidget { - /* most of the window */ - background: #EBEBEB; - color: #000000; -} - -QWidget:disabled { - /* disabled parts of the window like the update button when there are no updates */ - background: #EBEBEB; - color: #808080; -} - -QAbstractItemView { - /* left and right pane container */ - background: #FFFFFF; - alternate-background-color: #F6F6F6; - show-decoration-selected: 1; - selection-background-color: #008484; - selection-color: #FFFFFF; -} - -QAbstractItemView::item { - min-height: 22px; -} - -QAbstractItemView::item:hover { - /* rows on left and right pane when moused-over */ - background: #C2C2C2; -} - -QAbstractItemView::item:selected { - /* rows on left and right pane when clicked */ - background: #008484; - color: #FFFFFF; -} - -QAbstractScrollArea::corner { - /* corner between where a vertical scrollbar and a horizontal scrollbar meet */ - background: #FFFFFF; - border: 2px solid #EBEBEB; - border-bottom-right-radius: 6px; - margin: 0px -2px -2px 0px; -} - -QSplitter { - width: 8px; -} - -/* Toolbar */ - -QToolBar { - /* top toolbar; */ - background: #EBEBEB; - border: 1px solid #EBEBEB; -} - -QToolBar::separator { - /* uncertain, assumed: vertical seperator on toolbar left of the warnings button*/ - background: #EBEBEB; - margin: 6px 8px; -} - -QToolButton { - /* toolbar buttons */ - padding: 6px; - border-radius: 10px; - margin: 4px 4px 0px 4px; -} - -QToolButton:hover { - /* toolbar buttons when moused-over; */ - background: #008484; -} - -QToolButton:pressed { - /* toolbar buttons when clicked; */ - background: #008484; -} - -QToolButton:menu-indicator { - /* expandable indicator on toolbar buttons */ - image: url(./Paper/Light/Arrows/down.svg); - margin: 4px; -} - -/* Toolbar Button Icons */ - -#actionChange_Game { - qproperty-icon: url(./Paper/Light/Toolbar/instances.svg); -} - -#actionInstallMod { - qproperty-icon: url(./Paper/Light/Toolbar/archives.svg); -} - -#actionNexus { - qproperty-icon: url(./Paper/Light/Toolbar/nexus.svg); -} - -#actionAdd_Profile { - qproperty-icon: url(./Paper/Light/Toolbar/profiles.svg); -} - -#actionModify_Executables { - qproperty-icon: url(./Paper/Light/Toolbar/executables.svg); -} - -#actionTool { - qproperty-icon: url(./Paper/Light/Toolbar/tools.svg); -} - -#actionSettings { - qproperty-icon: url(./Paper/Light/Toolbar/settings.svg); -} - -#actionNotifications { - qproperty-icon: url(./Paper/Light/Toolbar/problems.svg); -} - -#actionUpdate { - qproperty-icon: url(./Paper/Light/Toolbar/update.svg); -} - -#actionHelp { - qproperty-icon: url(./Paper/Light/Toolbar/help.svg); -} - -/* Left Pane & File Trees */ - -ModListView, PluginListView { - /* Mods List and Plugins List specifically */ - margin: 4px 0px; -} - -QTreeView { - /* left pane and right pane under QAbstractItemView*/ - border-radius: 6px; -} - -QTreeView::branch:hover { - /* rows on the left pane when moused-over (below QAbstractItemView, i.e. to the left of the checkbox) */ - background: #C2C2C2; - color: #FFFFFF; -} - -QTreeView::branch:selected { - /* rows on the left pane when clicked (below QAbstractItemView, i.e. to the left of the checkbox) */ - background: #008484; - color: #FFFFFF; -} - -QTreeView::item:selected { - /* rows on the left pane when selected */ - background: #008484; - color: #FFFFFF; -} - -QTreeView::branch:closed:has-children:has-siblings, -QTreeView::branch:closed:has-children:!has-siblings { - /* a branch that is closed */ - image: url(./Paper/Light/Arrows/right.svg); -} - -QTreeView::branch:open:has-children:has-siblings, -QTreeView::branch:open:has-children:!has-siblings { - /* a branch that is open */ - image: url(./Paper/Light/Arrows/down.svg); -} - -QListView { - /* saves window */ - border-radius: 6px; -} - -QListView::item:hover { - /* uncertain, assumed: rows on the saves window when moused-over */ - background: #C2C2C2; - padding: 0; -} - -QListView::item:selected { - /*uncertain, assumed: rows on the saves window when clicked */ - background: #008484; - color: #FFFFFF; - padding: 0; -} - -QTextEdit { - /* large text fields */ - background: #FFFFFF; - border-radius: 6px; -} - -/* Group Boxes */ - -QGroupBox { - /* boxes that group multiple elements together (e.g. on Settings) */ - padding: 24px 4px; - border: 2px solid #FFFFFF; - border-radius: 10px; -} - -QGroupBox::title { - /* title of group boxes */ - background: transparent; - subcontrol-origin: padding; - subcontrol-position: top left; - padding: 8px; -} - -/* Text Fields */ - -QLineEdit { - /* text fields like NameFilter and directory fields */ - background: #FFFFFF; - min-height: 14px; - padding: 2px; - border: 2px solid #FFFFFF; - border-radius: 6px; -} - -QLineEdit:hover { - /* text fields when moused-over */ - border: 2px solid #008484; -} - -/* Most Dropdown Menus */ - -QComboBox { - /* dropdown menus */ - background: #FFFFFF; - min-height: 20px; - padding-left: 5px; - border: 2px solid #FFFFFF; - border-radius: 6px; - margin: 4px 0px; -} - -QComboBox:hover { - /* dropdown menus when moused-over */ - border: 2px solid #008484; -} - -QComboBox:on { - /* dropdown menus when expanded */ - background: #008484; - color: #FFFFFF; - border: 2px solid #008484; -} - -QComboBox::drop-down { - /* area for expandable indicator */ - width: 20px; - subcontrol-origin: padding; - subcontrol-position: top right; - border: none; -} - -QComboBox QAbstractItemView { - /* actual menu that expands */ - background: #FFFFFF; - border: 2px solid #EBEBEB; - border-radius: 6px; -} - -QComboBox::down-arrow { - /* expandable indicator */ - image: url(./Paper/Light/Arrows/down.svg); -} - -/* Most Buttons */ - -QPushButton { - /* most buttons */ - background: #FFFFFF; - color: #000000; - min-height: 20px; - padding: 2px 12px; - border-radius: 6px; -} - -QPushButton:disabled { - /* most buttons when disabled */ - border: 2px solid #FFFFFF; -} - -QPushButton:hover { - /* most buttons when hovered */ - background: #008484; - color: #FFFFFF; -} - -QPushButton:pressed { - /* most buttons when clicked */ - background: #008484; - color: #FFFFFF; -} - -QPushButton::menu-indicator { - /* expandable indicator for most buttons */ - subcontrol-position: right center; - image: url(./Paper/Light/Arrows/down.svg); - padding: 2px; - margin: 4px 4px; -} - -/* Icons */ - -#listOptionsBtn { - /* Options button */ - qproperty-icon: url(./Paper/Light/dots.svg); - qproperty-iconSize: 16px; - padding-left: 2px; -} - -#openFolderMenu { - /* Open Folder button */ - qproperty-icon: url(./Paper/Light/folder.svg); - qproperty-iconSize: 14px; - padding-left: 4px; -} - -#restoreModsButton, -#restoreButton { - /* Restore Backup buttons */ - qproperty-icon: url(./Paper/Light/restore.svg); - qproperty-iconSize: 14px; -} - -#saveModsButton, -#saveButton { - /* Backup buttons */ - qproperty-icon: url(./Paper/Light/backup.svg); - qproperty-iconSize: 14px; -} - -#bossButton { - /* Sort button */ - qproperty-icon: url(./Paper/Light/sort.svg); - qproperty-iconSize: 14px; -} - -#linkButton { - /* Shortcuts button */ - qproperty-icon: url(./Paper/Light/shortcut.svg); - qproperty-iconSize: 14px; -} - -#refreshButton, -#btnRefreshData, -#btnRefreshDownloads { - /* Refresh buttons */ - qproperty-icon: url(./Paper/Light/refresh.svg); - qproperty-iconSize: 14px; -} - -#endorseBtn { - /* Endorse button on the Nexus Info tab of the Information window */ - qproperty-icon: url(./Paper/Light/heart.svg); - qproperty-iconSize: 14px; -} - -#clearCacheButton { - /* Clear Cache button on the Nexus tab of the Settings window */ - qproperty-icon: url(./Paper/Light/cross.svg); - qproperty-iconSize: 14px; -} - -#deactivateESP, -#activateESP { - /* activate and deactivate ESP buttons */ - background: #FFFFFF; -} - -#deactivateESP:hover, -#activateESP:hover { - /* activate and deactivate ESP buttons when moused-over */ - background: #008484; -} - -#deactivateESP { - /* icon for the deactivate ESP button */ - qproperty-icon: url(./Paper/Light/backup.svg); -} - -#activateESP { - /* icon for the activate ESP button */ - qproperty-icon: url(./Paper/Light/restore-alt.svg); -} - -/* Run button */ - -#startButton { - /* Run button */ - background: #008484; - color: #FFFFFF; - qproperty-icon: url(./Paper/Light/run.svg); - qproperty-iconSize: 30px; - padding: 6px; -} - -#startButton:hover { - /* Run button when moused-over*/ - background: #C2C2C2; -} - -/* Scroll Bars */ - -/* Horizontal */ - -QScrollBar:horizontal { - /* horizontal scroll bar */ - background: #FFFFFF; - height: 20px; - border: 2px solid #EBEBEB; - margin: 0px 23px -2px 23px; -} - -QScrollBar::handle:horizontal { - /* handle for horizontal scroll bars */ - background: #C2C2C2; - min-width: 32px; - border-radius: 6px; - margin: 2px; -} - -QScrollBar::add-line:horizontal { - /* scroll right button */ - background: #FFFFFF; - image: url(./Paper/Light/Arrows/right.svg); - width: 23px; - subcontrol-position: right; - subcontrol-origin: margin; - border: 2px solid #EBEBEB; - margin: 0px -2px -2px 0px; -} - -QScrollBar::sub-line:horizontal { - /* scroll left button */ - background: #FFFFFF; - image: url(./Paper/Light/Arrows/left.svg); - width: 23px; - subcontrol-position: left; - subcontrol-origin: margin; - border: 2px solid #EBEBEB; - border-bottom-left-radius: 6px; - margin: 0px 0px -2px -2px; -} - -/* Vertical */ - -QScrollBar:vertical { - /* vertical scroll bar */ - background: #FFFFFF; - width: 20px; - border: 2px solid #EBEBEB; - margin: 23px -2px 23px 0px; -} - -QScrollBar::handle:vertical { - /* handle for vertical scroll bars */ - background: #C2C2C2; - min-height: 32px; - border-radius: 6px; - margin: 2px; -} - -QScrollBar::add-line:vertical { - /* scroll down button */ - background: #FFFFFF; - image: url(./Paper/Light/Arrows/down.svg); - height: 23px; - subcontrol-position: bottom; - subcontrol-origin: margin; - border: 2px solid #EBEBEB; - border-bottom-right-radius: 6px; - margin: 0px -2px -2px 0px; -} - -QScrollBar::sub-line:vertical { - /* scroll up button */ - background: #FFFFFF; - image: url(./Paper/Light/Arrows/up.svg); - height: 23px; - subcontrol-position: top; - subcontrol-origin: margin; - border: 2px solid #EBEBEB; - border-top-right-radius: 6px; - margin: -2px -2px 0px 0px; -} - -/* Combined */ - -QScrollBar::handle:horizontal:hover, -QScrollBar::handle:vertical:hover, -QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, -QScrollBar::sub-line:vertical:hover { - /* buttons and handles when moused-over */ - background: #008484; -} - -QScrollBar::handle:horizontal:pressed, -QScrollBar::handle:vertical:pressed, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, -QScrollBar::sub-line:vertical:pressed { - /* buttons and handles when clicked */ - background: #008484; -} - -QScrollBar::add-page:horizontal, -QScrollBar::sub-page:horizontal, -QScrollBar::add-page:vertical, -QScrollBar::sub-page:vertical { - /* area on scroll bars where clicking it scrolls to where you clicked */ - background: transparent; -} - -/* Header Rows */ - -QHeaderView { - /* header row (i.e. Mod Name, Flags, Category, etc.) */ - background: #EBEBEB; -} - -QHeaderView::section { - /* each section on the header row (i.e. Mod name is one section and Flags another) */ - background: #FFFFFF; - color: #000000; - height: 22px; - padding: 0px 5px; - border: 0px; - border-bottom: 2px solid #EBEBEB; - border-right: 2px solid #EBEBEB; -} - -QHeaderView::section:first { - /* first section on a header row */ - border-top-left-radius: 6px; -} - -QHeaderView::section:last { - /* last section on a header row */ - border-right: 0px; - border-top-right-radius: 6px; -} - -QHeaderView::section:hover { - /* a section on a header row when hovered */ - background: #C2C2C2; -} - -QHeaderView::up-arrow { - /* ascending sort indicator */ - image: url(./Paper/Light/Arrows/up.svg); - padding-right: 4px; - height: 10px; - width: 10px; -} - -QHeaderView::down-arrow { - /* descending sort indicator */ - image: url(./Paper/Light/Arrows/down.svg); - padding-right: 4px; - height: 10px; - width: 10px; -} - -/* Right Click Menus, Toolbar Dropdown Menus, & Tooltips */ - -QMenuBar { - background: #EBEBEB; - border: 1px solid #EBEBEB; -} - -QMenuBar::item:selected { - background: #008484; - color: #FFFFFF; - border: 0px; - border-radius: 4px; -} - -QMenu { - /* right click menu */ - background: #FFFFFF; - border: 2px solid #EBEBEB; - border-radius: 6px; -} - -QMenu::item { - /* rows on right click menus */ - background: #FFFFFF; - padding: 5px 20px 5px 24px; -} - -QMenu::item:selected { - /* rows on right click menus when moused-over (i dunno) */ - background: #008484; - color: #FFFFFF; - border: 0px; - border-radius: 4px; -} - -QMenu::item:disabled { - /* unavailable rows on right click menus */ - background: #EBEBEB; - color: #808080; -} - -QMenu::separator { - /* seperators on right click menus */ - height: 2px; - background: #EBEBEB; -} - -QMenu::icon { - /* area for icons on right click menus */ - padding: 4px; -} - -QMenu::right-arrow { - /* submenu indicator */ - image: url(./Paper/Light/Arrows/right.svg); - padding-right: 5px; -} - -QMenu QPushButton { - /* Change Categories and Primary Categories buttons */ - background: #FFFFFF; - color: #000000; - padding: 2px 24px; - text-align: left; - border-radius: 0px; -} - -QMenu QPushButton:hover { - /* Change Categories and Primary Categories buttons when moused-over */ - border-radius: 6px; -} - -QMenu QCheckBox { - /* checkboxes on right click menus (change categories)*/ - background: #FFFFFF; - padding: 2px 6px; -} - -QMenu QCheckBox:hover { - /* checkboxes on right click menus when moused-over (change categories) */ - background: #C2C2C2; - color: #FFFFFF; -} - -QMenu QRadioButton { - /* radio buttons on right click menus (primary categories) */ - background: #FFFFFF; - padding: 2px 6px; -} - -QToolTip { - /* all tooltips */ - background: #FFFFFF; - border: 2px solid #EBEBEB; - border-radius: 6px; -} - -QStatusBar::item {border: None;} - -/* Progress Bars (Downloads) */ - -QProgressBar { - /* progress bars when downloading */ - background: #FFFFFF; - text-align: center; - border: 0px; - border-radius: 6px; - margin: 0px 10px; -} - -QProgressBar::chunk { - /* the loading part that moves on progress bars */ - background: #C2C2C2; - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} - -/* Right Pane and Tab Bars */ - -QTabWidget::pane { - /* right pane */ - top: 1px; - padding: 2px 2px 10px 2px; - border: 2px solid #FFFFFF; - border-radius: 10px; -} - -QTabWidget::tab-bar { - /* tabs */ - alignment: center; -} - -QTabBar::tab { - /* a tab */ - background: #FFFFFF; - color: #000000; - padding: 4px 1em; - border: 2px solid #FFFFFF; - margin: 3px 1px; -} - -QTabBar::tab:!selected { - /* a tab that is not clicked */ - background: #FFFFFF; - color: #000000; - border: 2px solid #FFFFFF; -} - -QTabBar::tab:disabled { - /* a tab that is disabled */ - background: #EBEBEB; - color: #808080; - border: 2px solid transparent; -} - -QTabBar::tab:selected { - /* a tab that is clicked */ - color: #FFFFFF; - background: #008484; - border: 2px solid #008484; -} - -QTabBar::tab:hover { - /* a tab when moused-over */ - background: #C2C2C2; - color: #000000; - border: 2px solid #C2C2C2; -} - -QTabBar::tab:first { - /* that first tab */ - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; -} - -QTabBar::tab:last { - /* the last tab */ - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; -} - -QTabBar QToolButton { - /* buttons to scroll between more tabs on a tab bar */ - background: #C2C2C2; - padding: 1px; - border-radius: 6px; - margin: 1px; -} - -QTabBar QToolButton:disabled { - /* buttons to scroll on a tab bar when it's unavailable */ - background: transparent; -} - -QLCDNumber { - /* LCD number on the Conflicts tab */ - background: #FFFFFF; - color: #008484; - border-radius: 6px; -} - -/* Tables (Configure Mod Categories) */ - -QTableView { - /* a table */ - gridline-color: #EBEBEB; - border: 0px; -} - -/* Checkboxes */ - -QTreeView::indicator:unchecked, -QCheckBox::indicator:unchecked, -QGroupBox::indicator:unchecked, -QRadioButton::indicator:unchecked { - /* a checkbox that is unchecked */ - image: url(./Paper/Light/unchecked.svg); - width: 14px; - height: 14px; -} - -QCheckBox::indicator:unchecked:hover, -QRadioButton::indicator:unchecked:hover { - /* a checkbox that is unchecked when moused-over and clicked */ - image: url(./Paper/Light/unchecked-hover.svg); -} - -QTreeView::indicator:unchecked:selected { - image: url(./Paper/Light/unchecked-alt.svg); -} - -QTreeView::indicator:checked, -QCheckBox::indicator:checked, -QGroupBox::indicator:checked, -QRadioButton::indicator:checked { - /* a checkbox that is checked */ - image: url(./Paper/Light/check.svg); - width: 14px; - height: 14px; -} - -QCheckBox::indicator:checked:hover, -QRadioButton::indicator:checked:hover { - /* a checkbox that is checked when moused-over and clicked */ - image: url(./Paper/Light/check-alt.svg); -} - -QTreeView::indicator:checked:selected { - image: url(./Paper/Light/check-white.svg); -} - -QCheckBox::indicator:disabled { - /* a checkbox that is disabled */ - image: url(./Paper/Light/unchecked-disabled.svg); -} - -/* Spinboxes */ - -QSpinBox, -QDoubleSpinBox { - /* usually boxes for selecting numbers */ - min-height: 24px; - min-width: 60px; - background: #FFFFFF; - padding: 0px 2px; - border: 2px solid #EBEBEB; - border-radius: 6px; - margin: 0px -4px; -} - -QSpinBox::up-button, -QDoubleSpinBox::up-button { - /* up button on spinboxes */ - min-height: 28px; - min-width: 20px; - subcontrol-position: center right; - border: 2px solid #EBEBEB; - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; -} - -QSpinBox::up-arrow, -QDoubleSpinBox::up-arrow { - /* arrow for the up button */ - image: url(./Paper/Light/Arrows/up.svg); -} - -QSpinBox::up-button:hover, -QDoubleSpinBox::up-button:hover { - /* up button on spinboxes when moused-over */ - background: #C2C2C2; -} - -QSpinBox::down-button, -QDoubleSpinBox::down-button { - /* down button on spinboxes */ - min-height: 28px; - min-width: 20px; - subcontrol-position: center left; - border: 2px solid #EBEBEB; - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; -} - -QSpinBox::down-arrow, -QDoubleSpinBox::down-arrow { - /* arrow for the up button */ - image: url(./Paper/Light/Arrows/down.svg); -} - -QSpinBox::down-button:hover, -QDoubleSpinBox::down-button:hover { - /* down button on spinboxes when moused-over */ - background: #C2C2C2; -} - -/* Sliders */ - -QSlider::groove { - /* sliders */ - height: 0px; - border: 1px solid #008484; -} - -QSlider::handle { - /* slider handles */ - background: #FFFFFF; - border: 2px solid #008484; - border-radius: 6px; - margin: -10px; -} - -QSlider::handle:hover { - /* Slider handles when moused-over */ - background: #008484; -} - -/* Pre-v2.1.7 Downloads Tab */ - -#downloadTab QAbstractScrollArea { - /* background of the entire downloads tab */ - background: #FFFFFF; -} - -DownloadListWidget QFrame, -DownloadListWidgetCompact, -DownloadListWidgetCompact QLabel { - /* an entry on the Downloads tab */ - background: #FFFFFF; -} - -DownloadListWidget #frame { - /* outer box of an entry on the Downloads tab */ - border: none; -} - -#installLabel { - /* installed/done label */ - color: none; -} - -DownloadListWidgetCompact, -DownloadListWidgetCompact QLabel { - /* an entry on the Downloads tab in Compacts View*/ - background: #FFFFFF; -} - -/* New Downloads View */ - -DownloadListWidget[downloadView=standard]::item { - /* an entry on the Downloads view */ - min-height: 44px; -} - -QProgressBar[downloadView=standard] { - /* a progress bar on the Downloads view */ - margin: 11px 0; -} - -/* Categories Filter */ - -#displayCategoriesBtn { - /* Filter button */ - min-width: 12px; -} - -#categoriesList { - /* Categories panel */ - min-width: 200px; - margin-bottom: 4px; -} - -#categoriesGroup { - /* Categories group box */ - padding-bottom: 0px; -} - -/* Fixes */ - -#executablesListBox { - /* Increase right margin of the select executables box */ - margin-right: 8px; -} - -#stepsStack QWidget { - /* Groupboxes on the FOMOD Installer Dialog */ - border: none; -} - -#stepsStack QGroupBox { - /* Fix to reimplement styling for Groupboxes on the FOMOD Installer dialog */ - border: 2px solid #EBEBEB; - border-radius: 10px; -} - -#activeModslabel, #activePluginsLabel { - /* Increase the left margin of the counters */ - padding-left: 6px; -} diff --git a/src/stylesheets/Paper/Automata/Arrows/down.svg b/src/stylesheets/Paper/Automata/Arrows/down.svg deleted file mode 100644 index 30f13836..00000000 --- a/src/stylesheets/Paper/Automata/Arrows/down.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Arrows/left.svg b/src/stylesheets/Paper/Automata/Arrows/left.svg deleted file mode 100644 index 97094916..00000000 --- a/src/stylesheets/Paper/Automata/Arrows/left.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Arrows/right.svg b/src/stylesheets/Paper/Automata/Arrows/right.svg deleted file mode 100644 index 5eaa99cf..00000000 --- a/src/stylesheets/Paper/Automata/Arrows/right.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Arrows/up.svg b/src/stylesheets/Paper/Automata/Arrows/up.svg deleted file mode 100644 index 5fa569f4..00000000 --- a/src/stylesheets/Paper/Automata/Arrows/up.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/archives.svg b/src/stylesheets/Paper/Automata/Toolbar/archives.svg deleted file mode 100644 index c13182c9..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/archives.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/executables.svg b/src/stylesheets/Paper/Automata/Toolbar/executables.svg deleted file mode 100644 index 6c442f76..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/executables.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/help.svg b/src/stylesheets/Paper/Automata/Toolbar/help.svg deleted file mode 100644 index 2cab3931..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/help.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/instances.svg b/src/stylesheets/Paper/Automata/Toolbar/instances.svg deleted file mode 100644 index 4b0cd707..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/instances.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/nexus.svg b/src/stylesheets/Paper/Automata/Toolbar/nexus.svg deleted file mode 100644 index 852f0034..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/nexus.svg +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/problems.svg b/src/stylesheets/Paper/Automata/Toolbar/problems.svg deleted file mode 100644 index f3fed97c..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/problems.svg +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/profiles.svg b/src/stylesheets/Paper/Automata/Toolbar/profiles.svg deleted file mode 100644 index 306c94f5..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/profiles.svg +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/settings.svg b/src/stylesheets/Paper/Automata/Toolbar/settings.svg deleted file mode 100644 index adc4ca7a..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/settings.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/tools.svg b/src/stylesheets/Paper/Automata/Toolbar/tools.svg deleted file mode 100644 index 555472e8..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/tools.svg +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/Toolbar/update.svg b/src/stylesheets/Paper/Automata/Toolbar/update.svg deleted file mode 100644 index 28bef275..00000000 --- a/src/stylesheets/Paper/Automata/Toolbar/update.svg +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/background.svg b/src/stylesheets/Paper/Automata/background.svg deleted file mode 100644 index 76468b68..00000000 --- a/src/stylesheets/Paper/Automata/background.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/backup.svg b/src/stylesheets/Paper/Automata/backup.svg deleted file mode 100644 index 1c55c45f..00000000 --- a/src/stylesheets/Paper/Automata/backup.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/branch.svg b/src/stylesheets/Paper/Automata/branch.svg deleted file mode 100644 index edabfeab..00000000 --- a/src/stylesheets/Paper/Automata/branch.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/collapsed.svg b/src/stylesheets/Paper/Automata/collapsed.svg deleted file mode 100644 index 614cca6b..00000000 --- a/src/stylesheets/Paper/Automata/collapsed.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/cross.svg b/src/stylesheets/Paper/Automata/cross.svg deleted file mode 100644 index 450623c3..00000000 --- a/src/stylesheets/Paper/Automata/cross.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/dots.svg b/src/stylesheets/Paper/Automata/dots.svg deleted file mode 100644 index 08d5139f..00000000 --- a/src/stylesheets/Paper/Automata/dots.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/expanded.svg b/src/stylesheets/Paper/Automata/expanded.svg deleted file mode 100644 index 19f776d3..00000000 --- a/src/stylesheets/Paper/Automata/expanded.svg +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/folder.svg b/src/stylesheets/Paper/Automata/folder.svg deleted file mode 100644 index 4ac83f47..00000000 --- a/src/stylesheets/Paper/Automata/folder.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/heart.svg b/src/stylesheets/Paper/Automata/heart.svg deleted file mode 100644 index 396fab55..00000000 --- a/src/stylesheets/Paper/Automata/heart.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/refresh.svg b/src/stylesheets/Paper/Automata/refresh.svg deleted file mode 100644 index a148aa1d..00000000 --- a/src/stylesheets/Paper/Automata/refresh.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/restore-alt.svg b/src/stylesheets/Paper/Automata/restore-alt.svg deleted file mode 100644 index 79f96c1a..00000000 --- a/src/stylesheets/Paper/Automata/restore-alt.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/restore.svg b/src/stylesheets/Paper/Automata/restore.svg deleted file mode 100644 index b5fbbee1..00000000 --- a/src/stylesheets/Paper/Automata/restore.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/run.svg b/src/stylesheets/Paper/Automata/run.svg deleted file mode 100644 index e969e49f..00000000 --- a/src/stylesheets/Paper/Automata/run.svg +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/shortcut.svg b/src/stylesheets/Paper/Automata/shortcut.svg deleted file mode 100644 index 72b918ec..00000000 --- a/src/stylesheets/Paper/Automata/shortcut.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Automata/sort.svg b/src/stylesheets/Paper/Automata/sort.svg deleted file mode 100644 index 32c08366..00000000 --- a/src/stylesheets/Paper/Automata/sort.svg +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Arrows/down.svg b/src/stylesheets/Paper/Dark/Arrows/down.svg deleted file mode 100644 index 21d34b87..00000000 --- a/src/stylesheets/Paper/Dark/Arrows/down.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Arrows/left.svg b/src/stylesheets/Paper/Dark/Arrows/left.svg deleted file mode 100644 index b4aa8784..00000000 --- a/src/stylesheets/Paper/Dark/Arrows/left.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Arrows/right.svg b/src/stylesheets/Paper/Dark/Arrows/right.svg deleted file mode 100644 index 37c9c20b..00000000 --- a/src/stylesheets/Paper/Dark/Arrows/right.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Arrows/up.svg b/src/stylesheets/Paper/Dark/Arrows/up.svg deleted file mode 100644 index b5aed913..00000000 --- a/src/stylesheets/Paper/Dark/Arrows/up.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/archives.svg b/src/stylesheets/Paper/Dark/Toolbar/archives.svg deleted file mode 100644 index 32758c87..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/archives.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/executables.svg b/src/stylesheets/Paper/Dark/Toolbar/executables.svg deleted file mode 100644 index c1b815c2..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/executables.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/help.svg b/src/stylesheets/Paper/Dark/Toolbar/help.svg deleted file mode 100644 index 7f0024d5..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/help.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/instances.svg b/src/stylesheets/Paper/Dark/Toolbar/instances.svg deleted file mode 100644 index c5e58489..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/instances.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/nexus.svg b/src/stylesheets/Paper/Dark/Toolbar/nexus.svg deleted file mode 100644 index 9bf09e14..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/nexus.svg +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/problems.svg b/src/stylesheets/Paper/Dark/Toolbar/problems.svg deleted file mode 100644 index 6cb095aa..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/problems.svg +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/profiles.svg b/src/stylesheets/Paper/Dark/Toolbar/profiles.svg deleted file mode 100644 index 3679449a..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/profiles.svg +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/settings.svg b/src/stylesheets/Paper/Dark/Toolbar/settings.svg deleted file mode 100644 index c58bd3dd..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/settings.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/tools.svg b/src/stylesheets/Paper/Dark/Toolbar/tools.svg deleted file mode 100644 index ac492405..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/tools.svg +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/Toolbar/update.svg b/src/stylesheets/Paper/Dark/Toolbar/update.svg deleted file mode 100644 index a6c272be..00000000 --- a/src/stylesheets/Paper/Dark/Toolbar/update.svg +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/backup.svg b/src/stylesheets/Paper/Dark/backup.svg deleted file mode 100644 index f0010ea9..00000000 --- a/src/stylesheets/Paper/Dark/backup.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/check-alt.svg b/src/stylesheets/Paper/Dark/check-alt.svg deleted file mode 100644 index 90b924f0..00000000 --- a/src/stylesheets/Paper/Dark/check-alt.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/check.svg b/src/stylesheets/Paper/Dark/check.svg deleted file mode 100644 index 2d113294..00000000 --- a/src/stylesheets/Paper/Dark/check.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/cross.svg b/src/stylesheets/Paper/Dark/cross.svg deleted file mode 100644 index ac286c08..00000000 --- a/src/stylesheets/Paper/Dark/cross.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/dots.svg b/src/stylesheets/Paper/Dark/dots.svg deleted file mode 100644 index a746ec83..00000000 --- a/src/stylesheets/Paper/Dark/dots.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/folder.svg b/src/stylesheets/Paper/Dark/folder.svg deleted file mode 100644 index 84cebc66..00000000 --- a/src/stylesheets/Paper/Dark/folder.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/heart.svg b/src/stylesheets/Paper/Dark/heart.svg deleted file mode 100644 index 5a4cc1f1..00000000 --- a/src/stylesheets/Paper/Dark/heart.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/highlight.svg b/src/stylesheets/Paper/Dark/highlight.svg deleted file mode 100644 index 9dee51d8..00000000 --- a/src/stylesheets/Paper/Dark/highlight.svg +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/refresh.svg b/src/stylesheets/Paper/Dark/refresh.svg deleted file mode 100644 index a81507d5..00000000 --- a/src/stylesheets/Paper/Dark/refresh.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/restore-alt.svg b/src/stylesheets/Paper/Dark/restore-alt.svg deleted file mode 100644 index 8f8c2a43..00000000 --- a/src/stylesheets/Paper/Dark/restore-alt.svg +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/restore.svg b/src/stylesheets/Paper/Dark/restore.svg deleted file mode 100644 index 0511e182..00000000 --- a/src/stylesheets/Paper/Dark/restore.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/run.svg b/src/stylesheets/Paper/Dark/run.svg deleted file mode 100644 index 008b2ccc..00000000 --- a/src/stylesheets/Paper/Dark/run.svg +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/shortcut.svg b/src/stylesheets/Paper/Dark/shortcut.svg deleted file mode 100644 index 27a3497a..00000000 --- a/src/stylesheets/Paper/Dark/shortcut.svg +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/sort.svg b/src/stylesheets/Paper/Dark/sort.svg deleted file mode 100644 index 372bc1cd..00000000 --- a/src/stylesheets/Paper/Dark/sort.svg +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/unchecked-alt.svg b/src/stylesheets/Paper/Dark/unchecked-alt.svg deleted file mode 100644 index 4f5bbd04..00000000 --- a/src/stylesheets/Paper/Dark/unchecked-alt.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/unchecked-disabled.svg b/src/stylesheets/Paper/Dark/unchecked-disabled.svg deleted file mode 100644 index befdeba1..00000000 --- a/src/stylesheets/Paper/Dark/unchecked-disabled.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Dark/unchecked.svg b/src/stylesheets/Paper/Dark/unchecked.svg deleted file mode 100644 index 41c91aa6..00000000 --- a/src/stylesheets/Paper/Dark/unchecked.svg +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Arrows/down.svg b/src/stylesheets/Paper/Light/Arrows/down.svg deleted file mode 100644 index 106b64a5..00000000 --- a/src/stylesheets/Paper/Light/Arrows/down.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Arrows/left.svg b/src/stylesheets/Paper/Light/Arrows/left.svg deleted file mode 100644 index 063012ef..00000000 --- a/src/stylesheets/Paper/Light/Arrows/left.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Arrows/right.svg b/src/stylesheets/Paper/Light/Arrows/right.svg deleted file mode 100644 index 21d222e2..00000000 --- a/src/stylesheets/Paper/Light/Arrows/right.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Arrows/up.svg b/src/stylesheets/Paper/Light/Arrows/up.svg deleted file mode 100644 index 967f3185..00000000 --- a/src/stylesheets/Paper/Light/Arrows/up.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/archives.svg b/src/stylesheets/Paper/Light/Toolbar/archives.svg deleted file mode 100644 index cdc3d5a8..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/archives.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/executables.svg b/src/stylesheets/Paper/Light/Toolbar/executables.svg deleted file mode 100644 index 64985f45..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/executables.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/help.svg b/src/stylesheets/Paper/Light/Toolbar/help.svg deleted file mode 100644 index 929cd6cd..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/help.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/instances.svg b/src/stylesheets/Paper/Light/Toolbar/instances.svg deleted file mode 100644 index 78361fe9..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/instances.svg +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/nexus.svg b/src/stylesheets/Paper/Light/Toolbar/nexus.svg deleted file mode 100644 index 1715b626..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/nexus.svg +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/problems.svg b/src/stylesheets/Paper/Light/Toolbar/problems.svg deleted file mode 100644 index 9d398ee2..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/problems.svg +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/profiles.svg b/src/stylesheets/Paper/Light/Toolbar/profiles.svg deleted file mode 100644 index 5c074237..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/profiles.svg +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/settings.svg b/src/stylesheets/Paper/Light/Toolbar/settings.svg deleted file mode 100644 index c0d8ed7e..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/settings.svg +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/tools.svg b/src/stylesheets/Paper/Light/Toolbar/tools.svg deleted file mode 100644 index aae91d30..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/tools.svg +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/Toolbar/update.svg b/src/stylesheets/Paper/Light/Toolbar/update.svg deleted file mode 100644 index 0d017fa3..00000000 --- a/src/stylesheets/Paper/Light/Toolbar/update.svg +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/backup.svg b/src/stylesheets/Paper/Light/backup.svg deleted file mode 100644 index 37240d7e..00000000 --- a/src/stylesheets/Paper/Light/backup.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/check-alt.svg b/src/stylesheets/Paper/Light/check-alt.svg deleted file mode 100644 index c566a383..00000000 --- a/src/stylesheets/Paper/Light/check-alt.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/check-white.svg b/src/stylesheets/Paper/Light/check-white.svg deleted file mode 100644 index d3f7893d..00000000 --- a/src/stylesheets/Paper/Light/check-white.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/check.svg b/src/stylesheets/Paper/Light/check.svg deleted file mode 100644 index 70701759..00000000 --- a/src/stylesheets/Paper/Light/check.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/cross.svg b/src/stylesheets/Paper/Light/cross.svg deleted file mode 100644 index 399525b0..00000000 --- a/src/stylesheets/Paper/Light/cross.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/dots.svg b/src/stylesheets/Paper/Light/dots.svg deleted file mode 100644 index 02e61276..00000000 --- a/src/stylesheets/Paper/Light/dots.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/folder.svg b/src/stylesheets/Paper/Light/folder.svg deleted file mode 100644 index 888464f7..00000000 --- a/src/stylesheets/Paper/Light/folder.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/heart.svg b/src/stylesheets/Paper/Light/heart.svg deleted file mode 100644 index ffff0a5b..00000000 --- a/src/stylesheets/Paper/Light/heart.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/refresh.svg b/src/stylesheets/Paper/Light/refresh.svg deleted file mode 100644 index 5a1ee108..00000000 --- a/src/stylesheets/Paper/Light/refresh.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/restore-alt.svg b/src/stylesheets/Paper/Light/restore-alt.svg deleted file mode 100644 index 9f53e511..00000000 --- a/src/stylesheets/Paper/Light/restore-alt.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/restore.svg b/src/stylesheets/Paper/Light/restore.svg deleted file mode 100644 index 33aea044..00000000 --- a/src/stylesheets/Paper/Light/restore.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/run.svg b/src/stylesheets/Paper/Light/run.svg deleted file mode 100644 index 008b2ccc..00000000 --- a/src/stylesheets/Paper/Light/run.svg +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/shortcut.svg b/src/stylesheets/Paper/Light/shortcut.svg deleted file mode 100644 index 45c9a748..00000000 --- a/src/stylesheets/Paper/Light/shortcut.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/sort.svg b/src/stylesheets/Paper/Light/sort.svg deleted file mode 100644 index 94b5a4f1..00000000 --- a/src/stylesheets/Paper/Light/sort.svg +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/unchecked-alt.svg b/src/stylesheets/Paper/Light/unchecked-alt.svg deleted file mode 100644 index ae3c96b4..00000000 --- a/src/stylesheets/Paper/Light/unchecked-alt.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/unchecked-disabled.svg b/src/stylesheets/Paper/Light/unchecked-disabled.svg deleted file mode 100644 index 1c9fd840..00000000 --- a/src/stylesheets/Paper/Light/unchecked-disabled.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/unchecked-hover.svg b/src/stylesheets/Paper/Light/unchecked-hover.svg deleted file mode 100644 index 7e3683dc..00000000 --- a/src/stylesheets/Paper/Light/unchecked-hover.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/src/stylesheets/Paper/Light/unchecked.svg b/src/stylesheets/Paper/Light/unchecked.svg deleted file mode 100644 index 9e1f8b08..00000000 --- a/src/stylesheets/Paper/Light/unchecked.svg +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - - image/svg+xml - - - - - - - - - -- cgit v1.3.1 From 317730054d3de1184f20d827eef49391e9b631d4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 4 Dec 2019 21:53:41 -0500 Subject: moved logs back to the bottom of the list, they're not game related --- src/mainwindow.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 01f683a7..cb242b88 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4591,23 +4591,27 @@ QMenu *MainWindow::openFolderMenu() { QMenu *FolderMenu = new QMenu(this); + // game folders that are not necessarily MO-specific FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); FolderMenu->addAction(tr("Open INIs folder"), this, SLOT(openIniFolder())); FolderMenu->addSeparator(); + // MO-specific folders that are related to modding the game FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); FolderMenu->addAction(tr("Open Mods folder"), this, SLOT(openModsFolder())); FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); - FolderMenu->addAction(tr("Open Logs folder"), this, SLOT(openLogsFolder())); FolderMenu->addSeparator(); + // MO-specific folders that are not directly related to modding and are either + // in the installation folder or the instance FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); FolderMenu->addAction(tr("Open MO2 Stylesheets folder"), this, SLOT(openStylesheetsFolder())); + FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder())); return FolderMenu; } -- cgit v1.3.1 From 2c5603092af9cdce1748870176c5f4cd49a87b8d Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Fri, 6 Dec 2019 11:06:50 -0700 Subject: Add source game column to the download list --- src/downloadlist.cpp | 9 ++++++++- src/downloadlist.h | 1 + src/downloadlistsortproxy.cpp | 2 ++ src/downloadlistwidget.cpp | 1 + src/downloadmanager.cpp | 13 +++++++++++++ src/downloadmanager.h | 8 ++++++++ 6 files changed, 33 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 6957f270..99347a79 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -77,6 +77,7 @@ QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int case COL_SIZE: return tr("Size"); case COL_STATUS: return tr("Status"); case COL_FILETIME: return tr("Filetime"); + case COL_SOURCEGAME: return tr("Source Game"); default: return QVariant(); } } else { @@ -118,10 +119,16 @@ QVariant DownloadList::data(const QModelIndex &index, int role) const if (m_Manager->isInfoIncomplete(index.row())) { return {}; } else { - const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row()); return QString("%1").arg(m_Manager->getModID(index.row())); } } + case COL_SOURCEGAME: { + if (m_Manager->isInfoIncomplete(index.row())) { + return {}; + } else { + return QString("%1").arg(m_Manager->getDisplayGameName(index.row())); + } + } case COL_SIZE: return MOBase::localizedByteSize(m_Manager->getFileSize(index.row())); case COL_FILETIME: return m_Manager->getFileTime(index.row()); case COL_STATUS: diff --git a/src/downloadlist.h b/src/downloadlist.h index 51ab4541..eb2bbc55 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -43,6 +43,7 @@ public: COL_MODNAME, COL_VERSION, COL_ID, + COL_SOURCEGAME, // number of columns COL_COUNT diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index a69993c0..6209a721 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -96,6 +96,8 @@ bool DownloadListSortProxy::lessThan(const QModelIndex &left, return m_Manager->getFileSize(left.row()) < m_Manager->getFileSize(right.row()); } else if (left.column() == DownloadList::COL_FILETIME) { return m_Manager->getFileTime(left.row()) < m_Manager->getFileTime(right.row()); + } else if (left.column() == DownloadList::COL_SOURCEGAME) { + return m_Manager->getDisplayGameName(left.row()) < m_Manager->getDisplayGameName(right.row()); } else { return leftIndex < rightIndex; } diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 85d27831..ac37b0ee 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -135,6 +135,7 @@ void DownloadListWidget::setManager(DownloadManager *manager) header()->hideSection(DownloadList::COL_MODNAME); header()->hideSection(DownloadList::COL_VERSION); header()->hideSection(DownloadList::COL_ID); + header()->hideSection(DownloadList::COL_SOURCEGAME); } void DownloadListWidget::setSourceModel(DownloadList *sourceModel) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 361e7164..adfbc84d 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1239,6 +1239,19 @@ int DownloadManager::getModID(int index) const return m_ActiveDownloads.at(index)->m_FileInfo->modID; } +QString DownloadManager::getDisplayGameName(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("mod id: invalid download index %1").arg(index)); + } + QString gameName = m_ActiveDownloads.at(index)->m_FileInfo->gameName; + IPluginGame* gamePlugin = m_OrganizerCore->getGame(gameName); + if (gamePlugin) { + gameName = gamePlugin->gameName(); + } + return gameName; +} + QString DownloadManager::getGameName(int index) const { if ((index < 0) || (index >= m_ActiveDownloads.size())) { diff --git a/src/downloadmanager.h b/src/downloadmanager.h index f2ad15f4..4fc61cad 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -312,6 +312,14 @@ public: **/ int getModID(int index) const; + /** + * @brief retrieve the displayable game name of the download specified by the index + * + * @param index index of the file to look up + * @return the displayable game name + **/ + QString getDisplayGameName(int index) const; + /** * @brief retrieve the game name of the downlaod specified by the index * -- cgit v1.3.1 From b3331ef2c0b50ab2cea4c328e78f6ab58bca099d Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 6 Dec 2019 23:12:18 -0600 Subject: Separate conflict flags and render them in separate columns --- src/CMakeLists.txt | 3 + src/colortable.cpp | 61 +- src/mainwindow.cpp | 22 +- src/modconflicticondelegate.cpp | 160 ++ src/modconflicticondelegate.h | 36 + src/modflagicondelegate.cpp | 74 +- src/modflagicondelegate.h | 4 - src/modinfo.h | 25 +- src/modinfobackup.cpp | 1 + src/modinfodialogfwd.h | 1 + src/modinfooverwrite.cpp | 9 + src/modinfooverwrite.h | 1 + src/modinfowithconflictinfo.cpp | 4 +- src/modinfowithconflictinfo.h | 3 +- src/modlist.cpp | 40 +- src/modlist.h | 3 + src/modlistsortproxy.cpp | 21 + src/modlistsortproxy.h | 1 + src/organizer_en.ts | 3671 ++++++++++++++++++++++----------------- 19 files changed, 2390 insertions(+), 1750 deletions(-) create mode 100644 src/modconflicticondelegate.cpp create mode 100644 src/modconflicticondelegate.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a46908ef..d0215930 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,6 +114,7 @@ SET(organizer_SRCS previewdialog.cpp aboutdialog.cpp modflagicondelegate.cpp + modconflicticondelegate.cpp genericicondelegate.cpp organizerproxy.cpp viewmarkingscrollbar.cpp @@ -238,6 +239,7 @@ SET(organizer_HDRS previewdialog.h aboutdialog.h modflagicondelegate.h + modconflicticondelegate.h genericicondelegate.h organizerproxy.h viewmarkingscrollbar.h @@ -491,6 +493,7 @@ set(widgets loglist loghighlighter modflagicondelegate + modconflicticondelegate modidlineedit noeditdelegate qtgroupingproxy diff --git a/src/colortable.cpp b/src/colortable.cpp index 61c5ee5f..b1e4ef6c 100644 --- a/src/colortable.cpp +++ b/src/colortable.cpp @@ -1,5 +1,6 @@ #include "colortable.h" #include "modflagicondelegate.h" +#include "modconflicticondelegate.h" #include "settings.h" class ColorItem; @@ -52,21 +53,17 @@ public: } void paint( - QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override + QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override { paintBackground(m_table, painter, option, index); ModFlagIconDelegate::paintIcons(painter, option, index, getIcons(index)); } protected: - QList getIcons(const QModelIndex &index) const override + QList getIcons(const QModelIndex& index) const override { const auto flags = { - ModInfo::FLAG_CONFLICT_MIXED, - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, - ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED, ModInfo::FLAG_BACKUP, ModInfo::FLAG_NOTENDORSED, ModInfo::FLAG_NOTES, @@ -76,7 +73,48 @@ protected: return getIconsForFlags(flags, false); } - size_t getNumIcons(const QModelIndex &index) const override + size_t getNumIcons(const QModelIndex& index) const override + { + return getIcons(index).size(); + } + +private: + QTableWidget* m_table; +}; + + +// delegate for the icons column; paints the background and icons +// +class FakeModConflictIconDelegate : public ModConflictIconDelegate +{ +public: + explicit FakeModConflictIconDelegate(QTableWidget* table) + : m_table(table) + { + } + + void paint( + QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const override + { + paintBackground(m_table, painter, option, index); + ModFlagIconDelegate::paintIcons(painter, option, index, getIcons(index)); + } + +protected: + QList getIcons(const QModelIndex& index) const override + { + const auto flags = { + ModInfo::FLAG_CONFLICT_MIXED, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE, + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN, + ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED + }; + + return getIconsForFlags(flags, false); + } + + size_t getNumIcons(const QModelIndex& index) const override { return getIcons(index).size(); } @@ -183,11 +221,12 @@ void paintBackground( ColorTable::ColorTable(QWidget* parent) : QTableWidget(parent), m_settings(nullptr) { - setColumnCount(3); - setHorizontalHeaderLabels({"", "", ""}); + setColumnCount(4); + setHorizontalHeaderLabels({"", "", "", ""}); setItemDelegateForColumn(1, new ColoredBackgroundDelegate(this)); - setItemDelegateForColumn(2, new FakeModFlagIconDelegate(this)); + setItemDelegateForColumn(2, new FakeModConflictIconDelegate(this)); + setItemDelegateForColumn(3, new FakeModFlagIconDelegate(this)); connect( this, &QTableWidget::cellActivated, diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cb242b88..68627c90 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -61,6 +61,7 @@ along with Mod Organizer. If not, see . #include "filedialogmemory.h" #include "tutorialmanager.h" #include "modflagicondelegate.h" +#include "modconflicticondelegate.h" #include "genericicondelegate.h" #include "selectiondialog.h" #include "csvbuilder.h" @@ -552,7 +553,16 @@ void MainWindow::setupModList() flagDelegate, SLOT(columnResized(int,int,int))); + ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate( + ui->modList, ModList::COL_CONFLICTFLAGS, 120); + + connect( + ui->modList->header(), SIGNAL(sectionResized(int, int, int)), + conflictFlagDelegate, SLOT(columnResized(int, int, int))); + + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); + ui->modList->setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, conflictFlagDelegate); ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); @@ -2317,6 +2327,15 @@ void MainWindow::processUpdates(Settings& settings) { ui->downloadView->header()->hideSection(i); } } + + if (lastVersion < QVersionNumber(2, 2, 2)) { + bool lastHidden = true; + for (int i = ModList::COL_CONFLICTFLAGS; i < ui->modList->model()->columnCount(); ++i) { + bool hidden = ui->modList->header()->isSectionHidden(i); + ui->modList->header()->setSectionHidden(i, lastHidden); + lastHidden = hidden; + } + } } if (currentVersion < lastVersion) { @@ -3877,7 +3896,8 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; - case ModList::COL_FLAGS: tab = ModInfoTabIDs::Conflicts; break; + case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; + case ModList::COL_FLAGS: tab = ModInfoTabIDs::Flags; break; } displayModInformation(sourceIdx.row(), tab); diff --git a/src/modconflicticondelegate.cpp b/src/modconflicticondelegate.cpp new file mode 100644 index 00000000..2ccf3363 --- /dev/null +++ b/src/modconflicticondelegate.cpp @@ -0,0 +1,160 @@ +#include "modconflicticondelegate.h" +#include +#include + +using namespace MOBase; + +ModInfo::EConflictFlag ModConflictIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED + , ModInfo::FLAG_CONFLICT_OVERWRITE + , ModInfo::FLAG_CONFLICT_OVERWRITTEN + , ModInfo::FLAG_CONFLICT_REDUNDANT }; + +ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveLooseConflictFlags[2] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE + , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN }; + +ModInfo::EConflictFlag ModConflictIconDelegate::m_ArchiveConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED + , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE + , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN }; + +ModConflictIconDelegate::ModConflictIconDelegate(QObject *parent, int logicalIndex, int compactSize) + : IconDelegate(parent) + , m_LogicalIndex(logicalIndex) + , m_CompactSize(compactSize) + , m_Compact(false) +{ +} + +void ModConflictIconDelegate::columnResized(int logicalIndex, int, int newSize) +{ + if (logicalIndex == m_LogicalIndex) { + m_Compact = newSize < m_CompactSize; + } +} + +QList ModConflictIconDelegate::getIconsForFlags( + std::vector flags, bool compact) +{ + QList result; + + // Don't do flags for overwrite + if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE_CONFLICT) != flags.end()) + return result; + + // insert conflict icons to provide nicer alignment + { // insert loose file conflicts first + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ConflictFlags, m_ConflictFlags + 4); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + { // insert loose vs archive overwrite second + auto iter = std::find(flags.begin(), flags.end(), + ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + { // insert loose vs archive overwritten third + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + { // insert archive conflicts last + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else if (!compact) { + result.append(QString()); + } + } + + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + auto iconPath = getFlagIcon(*iter); + if (!iconPath.isEmpty()) + result.append(iconPath); + } + + return result; +} + +QList ModConflictIconDelegate::getIcons(const QModelIndex &index) const +{ + QVariant modid = index.data(Qt::UserRole + 1); + + if (modid.isValid()) { + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); + return getIconsForFlags(info->getConflictFlags(), m_Compact); + } + + return {}; +} + +QString ModConflictIconDelegate::getFlagIcon(ModInfo::EConflictFlag flag) +{ + switch (flag) { + case ModInfo::FLAG_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/archive_conflict_mixed"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_conflict_winner"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_conflict_loser"); + case ModInfo::FLAG_OVERWRITE_CONFLICT: return QString(); + default: + log::warn("ModInfo flag {} has no defined icon", flag); + return QString(); + } +} + +size_t ModConflictIconDelegate::getNumIcons(const QModelIndex &index) const +{ + unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); + if (modIdx < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); + std::vector flags = info->getConflictFlags(); + size_t count = flags.size(); + if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) { + ++count; + } + return count; + } else { + return 0; + } +} + + +QSize ModConflictIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const +{ + size_t count = getNumIcons(modelIndex); + unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); + QSize result; + if (index < ModInfo::getNumMods()) { + result = QSize(static_cast(count) * 40, 20); + } else { + result = QSize(1, 20); + } + if (option.rect.width() > 0) { + result.setWidth(std::min(option.rect.width(), result.width())); + } + return result; +} + diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h new file mode 100644 index 00000000..d36477c6 --- /dev/null +++ b/src/modconflicticondelegate.h @@ -0,0 +1,36 @@ +#ifndef MODCONFLICTICONDELEGATE_H +#define MODCONFLICTICONDELEGATE_H + +#include "icondelegate.h" + +class ModConflictIconDelegate : public IconDelegate +{ + Q_OBJECT; + +public: + explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 120); + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + static QList getIconsForFlags( + std::vector flags, bool compact); + + static QString getFlagIcon(ModInfo::EConflictFlag flag); + +public slots: + void columnResized(int logicalIndex, int oldSize, int newSize); + +protected: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; + +private: + static ModInfo::EConflictFlag m_ConflictFlags[4]; + static ModInfo::EConflictFlag m_ArchiveLooseConflictFlags[2]; + static ModInfo::EConflictFlag m_ArchiveConflictFlags[3]; + + int m_LogicalIndex; + int m_CompactSize; + bool m_Compact; +}; + +#endif // MODCONFLICTICONDELEGATE_H diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index a5e9aa22..6e1df147 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -4,18 +4,6 @@ using namespace MOBase; -ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED - , ModInfo::FLAG_CONFLICT_OVERWRITE - , ModInfo::FLAG_CONFLICT_OVERWRITTEN - , ModInfo::FLAG_CONFLICT_REDUNDANT }; - -ModInfo::EFlag ModFlagIconDelegate::m_ArchiveLooseConflictFlags[2] = { ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE - , ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN }; - -ModInfo::EFlag ModFlagIconDelegate::m_ArchiveConflictFlags[3] = { ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED - , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE - , ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN }; - ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent, int logicalIndex, int compactSize) : IconDelegate(parent) , m_LogicalIndex(logicalIndex) @@ -37,54 +25,9 @@ QList ModFlagIconDelegate::getIconsForFlags( QList result; // Don't do flags for overwrite - if (std::find(flags.begin(), flags.end(),ModInfo::FLAG_OVERWRITE) != flags.end()) + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) return result; - // insert conflict icons to provide nicer alignment - { // insert loose file conflicts first - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ConflictFlags, m_ConflictFlags + 4); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - - { // insert loose vs archive overwrite second - auto iter = std::find(flags.begin(), flags.end(), - ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - - { // insert loose vs archive overwritten third - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveLooseConflictFlags + 1, m_ArchiveLooseConflictFlags + 2); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - - { // insert archive conflicts last - auto iter = std::find_first_of(flags.begin(), flags.end(), - m_ArchiveConflictFlags, m_ArchiveConflictFlags + 3); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else if (!compact) { - result.append(QString()); - } - } - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { auto iconPath = getFlagIcon(*iter); if (!iconPath.isEmpty()) @@ -113,15 +56,6 @@ QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) case ModInfo::FLAG_INVALID: return QStringLiteral(":/MO/gui/problem"); case ModInfo::FLAG_NOTENDORSED: return QStringLiteral(":/MO/gui/emblem_notendorsed"); case ModInfo::FLAG_NOTES: return QStringLiteral(":/MO/gui/emblem_notes"); - case ModInfo::FLAG_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/emblem_conflict_mixed"); - case ModInfo::FLAG_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/emblem_conflict_overwrite"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/emblem_conflict_overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return QStringLiteral(":/MO/gui/emblem_conflict_redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwrite"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_loose_conflict_overwritten"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return QStringLiteral(":/MO/gui/archive_conflict_mixed"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return QStringLiteral(":/MO/gui/archive_conflict_winner"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return QStringLiteral(":/MO/gui/archive_conflict_loser"); case ModInfo::FLAG_ALTERNATE_GAME: return QStringLiteral(":/MO/gui/alternate_game"); case ModInfo::FLAG_FOREIGN: return QString(); case ModInfo::FLAG_SEPARATOR: return QString(); @@ -140,11 +74,7 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const if (modIdx < ModInfo::getNumMods()) { ModInfo::Ptr info = ModInfo::getByIndex(modIdx); std::vector flags = info->getFlags(); - size_t count = flags.size(); - if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) { - ++count; - } - return count; + return flags.size(); } else { return 0; } diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index 4f22dd90..ecab7e95 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -24,10 +24,6 @@ protected: virtual size_t getNumIcons(const QModelIndex &index) const; private: - static ModInfo::EFlag m_ConflictFlags[4]; - static ModInfo::EFlag m_ArchiveLooseConflictFlags[2]; - static ModInfo::EFlag m_ArchiveConflictFlags[3]; - int m_LogicalIndex; int m_CompactSize; bool m_Compact; diff --git a/src/modinfo.h b/src/modinfo.h index 30a115c7..7c41e0a1 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -60,14 +60,7 @@ public: static QString s_HiddenExt; - enum EFlag { - FLAG_INVALID, - FLAG_BACKUP, - FLAG_SEPARATOR, - FLAG_OVERWRITE, - FLAG_FOREIGN, - FLAG_NOTENDORSED, - FLAG_NOTES, + enum EConflictFlag { FLAG_CONFLICT_OVERWRITE, FLAG_CONFLICT_OVERWRITTEN, FLAG_CONFLICT_MIXED, @@ -77,6 +70,17 @@ public: FLAG_ARCHIVE_CONFLICT_OVERWRITE, FLAG_ARCHIVE_CONFLICT_OVERWRITTEN, FLAG_ARCHIVE_CONFLICT_MIXED, + FLAG_OVERWRITE_CONFLICT, + }; + + enum EFlag { + FLAG_INVALID, + FLAG_BACKUP, + FLAG_SEPARATOR, + FLAG_OVERWRITE, + FLAG_FOREIGN, + FLAG_NOTENDORSED, + FLAG_NOTES, FLAG_PLUGIN_SELECTED, FLAG_ALTERNATE_GAME, FLAG_TRACKED, @@ -519,6 +523,11 @@ public: */ virtual std::vector getFlags() const = 0; + /** + * @return a list of conflict flags for this mod + */ + virtual std::vector getConflictFlags() const = 0; + /** * @return a list of content types contained in a mod */ diff --git a/src/modinfobackup.cpp b/src/modinfobackup.cpp index 6e307103..6a34b86a 100644 --- a/src/modinfobackup.cpp +++ b/src/modinfobackup.cpp @@ -1,5 +1,6 @@ #include "modinfobackup.h" + std::vector ModInfoBackup::getFlags() const { std::vector result = ModInfoRegular::getFlags(); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index 2147fc04..e4a61208 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -14,6 +14,7 @@ enum class ModInfoTabIDs Images, Esps, Conflicts, + Flags, Categories, Nexus, Notes, diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index fb110abb..9a6a22c1 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -37,6 +37,15 @@ std::vector ModInfoOverwrite::getFlags() const return result; } +std::vector ModInfoOverwrite::getConflictFlags() const +{ + std::vector result; + result.push_back(FLAG_OVERWRITE_CONFLICT); + for (auto flag : ModInfoWithConflictInfo::getConflictFlags()) + result.push_back(flag); + return result; +} + int ModInfoOverwrite::getHighlight() const { int highlight = (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; diff --git a/src/modinfooverwrite.h b/src/modinfooverwrite.h index c5f58c2e..ecbdbe3d 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -51,6 +51,7 @@ public: virtual QDateTime getExpires() const { return QDateTime(); } virtual std::vector getIniTweaks() const { return std::vector(); } virtual std::vector getFlags() const; + virtual std::vector getConflictFlags() const; virtual int getHighlight() const; virtual QString getDescription() const; virtual int getNexusFileStatus() const { return 0; } diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 7a2b0071..f5a243ae 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -14,9 +14,9 @@ void ModInfoWithConflictInfo::clearCaches() m_LastConflictCheck = QTime(); } -std::vector ModInfoWithConflictInfo::getFlags() const +std::vector ModInfoWithConflictInfo::getConflictFlags() const { - std::vector result; + std::vector result; switch (isConflicted()) { case CONFLICT_MIXED: { result.push_back(ModInfo::FLAG_CONFLICT_MIXED); diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 13d87d25..9869bc5a 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -12,7 +12,8 @@ public: ModInfoWithConflictInfo(PluginContainer *pluginContainer, MOShared::DirectoryEntry **directoryStructure); - std::vector getFlags() const; + std::vector getConflictFlags() const; + virtual std::vector getFlags() const { return std::vector(); }; /** * @brief clear all caches held for this mod diff --git a/src/modlist.cpp b/src/modlist.cpp index b7a9b0a1..c5b8c856 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -159,15 +159,6 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const output << QString("%1").arg(modInfo->notes()); return output.join(""); } - case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); - case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); - case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); - case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); case ModInfo::FLAG_ALTERNATE_GAME: return tr("
    This mod is for a different game, " "make sure it's compatible or it could cause crashes."); case ModInfo::FLAG_TRACKED: return tr("Mod is being tracked on the website"); @@ -176,6 +167,23 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const } +QString ModList::getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const +{ + switch (flag) { + case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites loose files"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten loose files"); + case ModInfo::FLAG_CONFLICT_MIXED: return tr("Loose files Overwrites & Overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITE: return tr("Overwrites an archive with loose files"); + case ModInfo::FLAG_ARCHIVE_LOOSE_CONFLICT_OVERWRITTEN: return tr("Archive is overwritten by loose files"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITE: return tr("Overwrites another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_OVERWRITTEN: return tr("Overwritten by another archive file"); + case ModInfo::FLAG_ARCHIVE_CONFLICT_MIXED: return tr("Archive files overwrites & overwritten"); + default: return ""; + } +} + + QVariantList ModList::contentsToIcons(const std::vector &contents) const { QVariantList result; @@ -218,7 +226,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { if ((column == COL_FLAGS) - || (column == COL_CONTENT)) { + || (column == COL_CONTENT) + || (column == COL_CONFLICTFLAGS)) { return QVariant(); } else if (column == COL_NAME) { auto flags = modInfo->getFlags(); @@ -443,6 +452,15 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const result += getFlagText(flag, modInfo); } + return result; + } else if (column == COL_CONFLICTFLAGS) { + QString result; + + for (ModInfo::EConflictFlag flag : modInfo->getConflictFlags()) { + if (result.length() != 0) result += "
    "; + result += getConflictFlagText(flag, modInfo); + } + return result; } else if (column == COL_CONTENT) { return contentsToToolTip(modInfo->getContents()); @@ -1274,6 +1292,7 @@ void ModList::dropModeUpdate(bool dropOnItems) QString ModList::getColumnName(int column) { switch (column) { + case COL_CONFLICTFLAGS: return tr("Conflicts"); case COL_FLAGS: return tr("Flags"); case COL_CONTENT: return tr("Content"); case COL_NAME: return tr("Mod Name"); @@ -1299,6 +1318,7 @@ QString ModList::getColumnToolTip(int column) case COL_CATEGORY: return tr("Category of the mod."); case COL_GAME: return tr("The source game which was the origin of this mod."); case COL_MODID: return tr("Id of the mod as used on Nexus."); + case COL_CONFLICTFLAGS: return tr("Indicators of file conflicts between mods."); case COL_FLAGS: return tr("Emblems to highlight things that might require attention."); case COL_CONTENT: return tr("Depicts the content of the mod:
    " "
    Game plugins (esp/esm/esl)
    " diff --git a/src/modlist.h b/src/modlist.h index 631401c0..8841ec29 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -57,6 +57,7 @@ public: enum EColumn { COL_NAME, + COL_CONFLICTFLAGS, COL_FLAGS, COL_CONTENT, COL_CATEGORY, @@ -289,6 +290,8 @@ private: QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; + QString getConflictFlagText(ModInfo::EConflictFlag flag, ModInfo::Ptr modInfo) const; + static QString getColumnToolTip(int column); QVariantList contentsToIcons(const std::vector &content) const; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 64d5de42..440786c5 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -115,6 +115,17 @@ unsigned long ModListSortProxy::flagsId(const std::vector &flags return result; } +unsigned long ModListSortProxy::conflictFlagsId(const std::vector& flags) const +{ + unsigned long result = 0; + for (ModInfo::EConflictFlag flag : flags) { + if ((flag != ModInfo::FLAG_OVERWRITE_CONFLICT)) { + result += 1 << (int)flag; + } + } + return result; +} + bool ModListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { @@ -155,6 +166,16 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, lt = flagsId(leftFlags) < flagsId(rightFlags); } } break; + case ModList::COL_CONFLICTFLAGS: { + std::vector leftFlags = leftMod->getConflictFlags(); + std::vector rightFlags = rightMod->getConflictFlags(); + if (leftFlags.size() != rightFlags.size()) { + lt = leftFlags.size() < rightFlags.size(); + } + else { + lt = conflictFlagsId(leftFlags) < conflictFlagsId(rightFlags); + } + } break; case ModList::COL_CONTENT: { std::vector lContent = leftMod->getContents(); std::vector rContent = rightMod->getContents(); diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 46356fe9..d733b783 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -142,6 +142,7 @@ protected: private: unsigned long flagsId(const std::vector &flags) const; + unsigned long conflictFlagsId(const std::vector& flags) const; bool hasConflictFlag(const std::vector &flags) const; void updateFilterActive(); bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index e3531cb2..72972e4f 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -15,117 +15,122 @@ - - <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> + + usvfs: - + + <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer">GitHub</a>.</p></body></html> + + + + Used Software - + Thanks - + Lead Developers && Maintainers - + LePresidente (Project Lead) - + MO2 Developers && Contributors - + Translators - + Cyb3r (Dutch) - + fruttyx (French) - + Yoplala (French) - + Faron (German) - + yohru (Japanese) - + Mordan (Greek) - + Yoosk (Polish) - + Brgodfx (Portuguese) - + zDas (Portuguese) - + Jax (Swedish) - + Nubbie (Swedish) - + ...and all other contributors! - + Other Supporters && Contributors - + Tannin (Original Creator) - + Close - + No license @@ -173,17 +178,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -289,33 +294,43 @@ p, li { white-space: pre-wrap; } ConflictsTab - - &Hide + + &Execute - - &Unhide + + &Open - - &Open/Execute + + Open with &VFS - + &Preview - + + &Go to... + + + + Open in &Explorer - - &Go to... + + &Hide + + + + + &Unhide @@ -403,79 +418,84 @@ p, li { white-space: pre-wrap; } - - < game %1 mod %2 file %3 > + + Source Game - Unknown + < game %1 mod %2 file %3 > + Unknown + + + + Pending - + Started - + Canceling - + Pausing - + Canceled - + Paused - + Error - - - + + + Fetching Info - + Downloaded - + Installed - + Uninstalled - + Pending download - + Information missing, please select "Query Info" from the context menu to re-retrieve. @@ -483,156 +503,153 @@ p, li { white-space: pre-wrap; } DownloadListWidget - + Install - + Query Info - + Visit on Nexus - + Open File - - - + + + Show in Folder - - + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - + Resume - + Delete Installed Downloads... - Delete Installed... - + Delete Uninstalled Downloads... - Delete Uninstalled... - + Delete All Downloads... - Delete All... - + Hide Installed... - + Hide Uninstalled... - + Hide All... - + Un-Hide All... - - - - + + + + Delete Files? - + This will permanently delete the selected download. Are you absolutely sure you want to proceed? - + This will remove all finished downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all installed downloads from this list and from disk. Are you absolutely sure you want to proceed? - + This will remove all uninstalled downloads from this list and from disk. Are you absolutely sure you want to proceed? - - - + + + Hide Files? - + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + This will remove all uninstalled downloads from this list (but NOT from disk). @@ -645,37 +662,37 @@ Are you absolutely sure you want to proceed? - + Memory allocation error (in refreshing directory). - + failed to download %1: could not open output file: %2 - + Download again? - + A file with the same name "%1" has already been downloaded. Do you want to download it again? The new file will receive a different name. - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + There is already a download queued for this file. Mod %1 @@ -683,12 +700,12 @@ File %2 - + Already Queued - + There is already a download started for this file. Mod %1: %2 @@ -696,276 +713,277 @@ File %3: %4 - + Already Started - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - - + + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + Hashing download file '%1' - + Cancel - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -998,19 +1016,18 @@ Canceling download "%2"... - + Remove the selected executable - + Remove - @@ -1018,7 +1035,11 @@ Canceling download "%2"... - + + Up + + + @@ -1026,261 +1047,339 @@ Canceling download "%2"... - + + Down + + + + Adds the executables provided by the game plugin and moves any existing executables out of the way - + Reset - + List of configured executables - + This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. - + Title - + Name of the executable. This is only for display purposes. - + Binary - + Binary to run - + Browse filesystem - + Browse filesystem for the executable to run. - - + + ... - + Start in - + Arguments - + Arguments to pass to the application - + Allow the Steam AppID to be used for this executable to be changed. - + 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. - + Overwrite Steam AppID - + Steam AppID to use for this executable that differs from the games AppID. - + 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. - + If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. - - Create Files in Mod instead of Overwrite (*) + + Create files in mod instead of overwrite (*) - + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - - Force Load Libraries (*) + + Force load libraries (*) - + Configure Libraries - - Use Application's Icon for desktop shortcuts - Use Application's Icon for shortcuts + + Use application's icon for desktop shortcuts + + + + + + This executable will not appear in the list, on the toolbar or in the menu. It will still be visible in this dialog. + + + + + Hide in user interface + + + + + (*) Profile specific + + + + + Add from file... + + + + + Add empty - - (*) Profile Specific + + Clone selected - + Reset plugin executables - + 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. - - + + New Executable - - Select a binary + + Select a directory - - Executable (%1) + + Executables (*.exe *.bat *.jar) - - Select a directory + + All Files (*.*) - - Java (32-bit) required + + Select an executable - - MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. + + Java required + + + + + MO requires Java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. FileTreeTab - + &New Folder - + &Open/Execute - + + Open with &VFS + + + + &Preview - + Open in &Explorer - + &Rename - + &Delete - + &Hide - + &Unhide - - + + New Folder - + Failed to create "%1" - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 + + + &Execute + + + + + &Open + + + + + FilterList + + + Not + + + + + Filter separators + + + + + Show separators + + + + + Hide separators + + + + + <Contains %1> + + FindDialog @@ -1416,9 +1515,16 @@ Right now the only case I know of where this needs to be overwritten is for the Extracting files + + + + + Extraction failed: %1 + + - failed to create backup + Failed to create backup @@ -1511,29 +1617,6 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - - LockedDialog - - - Running virtualized processes - - - - - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - - - - - MO is locked while the executable is running. - - - - - Unlock - - - LogList @@ -1572,6 +1655,57 @@ This is likely due to a corrupted or incompatible download or unrecognized archi + + Loot + + + failed to start loot + + + + + Loot failed. Exit code was: %1 + + + + + LootDialog + + + LOOT + + + + + Progress + + + + + about:blank + + + + + Details + + + + + Open JSON report + + + + + Stopping LOOT... + + + + + Loot failed to run + + + MOApplication @@ -1598,17 +1732,17 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + failed to write to %1 - + file not found: %1 - + Save @@ -1632,48 +1766,59 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - - - Categories + + Filters - + Clear - - If checked, only mods that match all selected categories are displayed. + + Edit... + + + + + Display mods that match all selected categories. - + And - - If checked, all mods that match at least one of the selected categories are displayed. + + Display mods that match at least one of the selected categories - + Or - + + Filter: only show the separators that match the current filters +Show: always show separators +Hide: never show separators + + + + Profile - + Pick a module collection - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1683,84 +1828,84 @@ p, li { white-space: pre-wrap; } - + Open list options... - + Refresh list. This is usually not necessary unless you modified data outside the program. - + Show Open Folders menu... - - + + Restore Backup... - - - + + + Create Backup - - + + Active: - + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - - List of available mods. - - - - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - + Clear all Filters - + No groups - + + Categories + + + + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1770,12 +1915,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1784,17 +1929,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1803,32 +1948,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1837,27 +1982,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - - <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> + + <html><head/><body><p>Currently detected archives. (<a href="#">What is an archive?</a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1865,72 +2010,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1941,1170 +2086,1119 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Main ToolBar - + &File - - + + &Tools - - - + + + &Help - + &View - + &Toolbars - + &Run - - + + Log - + Install &Mod... - + Install &Mod - - + + Install a new mod from an archive - + Ctrl+M - + &Profiles... - + &Profiles - - + + Configure profiles - + Ctrl+P - + &Executables... - + &Executables - - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + &Tool Plugins - + Tools - + Ctrl+I - + &Settings... - + &Settings - - + + Configure settings and workarounds - + Ctrl+S - - + + Visit &Nexus - - + + Visit the Nexus website in your browser for more mods - + Ctrl+N - - + + &Update Mod Organizer - - + + Mod Organizer is up-to-date - + &Notifications... - - + + Open the notifications dialog - + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Show help options - + Ctrl+H - - + + &Endorse ModOrganizer - - - + + + Endorse Mod Organizer - + &Change Game... - + &Change Game - - + + Open the Instance selection dialog to manage a different Game - - + + E&xit - - + + Exits Mod Organizer - + M&ain Toolbar - + &Small Icons - + Lar&ge Icons - + &Icons Only - + &Text Only - + I&cons and Text - + M&edium Icons - + &Menu - + Status &bar - St&atus bar - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + + (no executables) + + + + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - - <Contains %1> + + failed to rename mod: %1 - - <Checked> + + Overwrite? - - <Unchecked> + + This will replace the existing mod "%1". Continue? - - <Update> + + failed to remove mod "%1" - - <Mod Backup> + + + + failed to rename "%1" to "%2" - - <Managed by MO> + + + + + Confirm - - <Managed outside MO> + + Remove the following mods?<br><ul>%1</ul> - - <No category> + + failed to remove mod: %1 - - <Conflicted> + + + + Failed - - <Not Endorsed> + + Installation file no longer exists - - failed to rename mod: %1 - - - - - Overwrite? - - - - - This will replace the existing mod "%1". Continue? - - - - - failed to remove mod "%1" - - - - - - - failed to rename "%1" to "%2" - - - - - - - - Confirm - - - - - Remove the following mods?<br><ul>%1</ul> - - - - - failed to remove mod: %1 - - - - - - - Failed - - - - - Installation file no longer exists - - - - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + + Open MO2 Stylesheets folder + + + + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - - <All> - - - - - <Multiple> - - - - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3112,12 +3206,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3125,349 +3219,330 @@ You can also use online editors and converters instead. - - Restarting MO + + Restart Mod Organizer - - Changing the managed game directory requires restarting MO. -Any pending downloads will be paused. - -Click OK to restart MO now. + + Mod Organizer must restart to finish configuration changes + + + + + Restart + + + + + Continue - + + Some things might be weird. + + + + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - - Please enter a name for the executable + + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - + Replace file? - + There already is a hidden version of this file. Replace it? - + File operation failed - + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - - Open/Execute + + &Execute - - Add as Executable + + &Open + + + + + Open with &VFS - + + &Add as Executable + + + + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - - Edit Categories... - - - - - Deselect filter + + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - - depends on missing "%1" - - - - - incompatible with "%1" - - - - - Please wait while LOOT is running - - - - - loot failed. Exit code was: %1 - - - - - failed to start loot - - - - - failed to run loot: %1 - - - - - Errors occurred - - - - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3484,101 +3559,100 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. ModInfo - + Plugins - + Textures - + Meshes - + Bethesda Archive - + UI Changes - + Sound Effects - + Scripts - + Script Extender - + Script Extender Files - + SkyProc Tools - + MCM Data - + INI files - + ModGroup files - + invalid content type: %1 - + invalid mod index: %1 - + remove: invalid mod index %1 - + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. - + You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. - You have mods that haven't been checked within 30 days using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. ModInfoBackup - + This is the backup of a mod @@ -3638,27 +3712,32 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. + Open with Preview Plugin + + + + Open in Explorer - + 0x0 - - + + Optional ESPs - + List of esps, esms, and esls that can not be loaded by the game. - + List of esps, esms, and esls contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. @@ -3666,135 +3745,132 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Move a file to the data directory. - + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of MO. - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - + Make the selected mod in the right list unavailable. - Make the selected mod in the lower list unavailable. - + The selected esp (in the right list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - + Available ESPs - + ESPs in the data directory and thus visible to the game. - + <html><head/><body><p>These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window.</p></body></html> - + Conflicts - + General - + The following conflicted files are provided by this mod - + The following conflicted files are provided by other mods - + The following files have no conflicts - + Advanced - + Whether files that have no conflicts should be visible in the list - + Show files that have no conflicts - + Shows all mods overwriting or being overwritten by this mod - + Show all conflicting mods - + Shows only the nearest conflicting mods, in order of priority - + Show nearest conflicting mod - + Filter - + Categories - + Primary Category - + Nexus Info - + Mod ID - + Mod ID for this mod on Nexus. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3803,22 +3879,22 @@ p, li { white-space: pre-wrap; } - + Source Game - + Source game for this mod. - + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3827,83 +3903,83 @@ p, li { white-space: pre-wrap; } - + Version - - + + Refresh - + Refresh all information from Nexus. - - + + Open in Browser - + Endorse - + Track - + about:blank - + Use Custom URL - + Notes - - - + + + Enter comments about the mod here. These are displayed in the notes column of the mod list. - - - + + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - + Filetree - + Open Mod in Explorer - + A directory view of this mod - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3913,17 +3989,17 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close @@ -3954,7 +4030,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) @@ -3983,318 +4059,328 @@ p, li { white-space: pre-wrap; } ModList - + Game Plugins (ESP/ESM/ESL) - + Interface - + Meshes - + Bethesda Archive - + Scripts (Papyrus) - + Script Extender Plugin - + SkyProc Patcher - + Sound or Music - + Textures - + MCM Configuration - + INI files - + ModGroup files - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + Separator - + No valid game data - + Not endorsed yet - + + <br>This mod is for a different game, make sure it's compatible or it could cause crashes. + + + + + Mod is being tracked on the website + + + + Overwrites loose files - + Overwritten loose files - + Loose files Overwrites & Overwritten - + Redundant - + Overwrites an archive with loose files - + Archive is overwritten by loose files - + Overwrites another archive file - + Overwritten by another archive file - + Archive files overwrites & overwritten - - <br>This mod is for a different game, make sure it's compatible or it could cause crashes. - - - - - Mod is being tracked on the website - - - - + Non-MO - + invalid - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + This file has been marked as "Old". There is most likely an updated version of this file available. - + This file has been marked as "Deleted"! You may want to check for an update or remove the nexus ID from this mod! - + %1 minute(s) and %2 second(s) - + This mod will be available to check in %2. - + Categories: <br> - + Invalid name - + Name is already in use by another mod - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + + Conflicts + + + + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - - Emblemes to highlight things that might require attention. + + Indicators of file conflicts between mods. + + + + + Emblems to highlight things that might require attention. - + Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr><tr><td><img src=":/MO/gui/content/modgroup" width=32/></td><td>ModGroup files</td></tr></table> - + Time this mod was installed - + User notes about the mod @@ -4302,7 +4388,7 @@ p, li { white-space: pre-wrap; } ModListSortProxy - + Drag&Drop is only supported when sorting by priority @@ -4345,31 +4431,31 @@ p, li { white-space: pre-wrap; } NexusInterface - Failed to guess mod id for "%1", please pick the correct one + Please pick the mod ID for "%1" - + You must authorize MO2 in Settings -> Nexus to use the Nexus API. - + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. - + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. - + empty response - + invalid response @@ -4452,7 +4538,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4460,212 +4546,207 @@ p, li { white-space: pre-wrap; } OrganizerCore - - Failed to write settings - - - - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + + Failed to write settings + + + + An error occurred trying to write back MO settings to %1: %2 - - + + Download started - + Download failed - - - - + + + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + The mod was not installed completely. - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - - Error - - - - - No profile set - - - - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + + Error + + + + The designated write target "%1" is not enabled. @@ -4673,12 +4754,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -4721,43 +4802,43 @@ Continue? - + mod not found: %1 - + Failed to delete "%1" - - - - + + + + Confirm - - + + Are you sure you want to delete "%1"? - - + + Are you sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" @@ -4765,12 +4846,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -4778,18 +4859,18 @@ Continue? PluginContainer - + Some plugins could not be loaded - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: @@ -4797,152 +4878,176 @@ Continue? PluginList - + Name - + Priority - + Mod Index - + Flags - - + + unknown - - Name of your mods + + Name of the plugin + + + + + Emblems to highlight things that might require attention. - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + + Load priority of plugins. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - - The modindex determines the formids of objects originating from this mods. + + Determines the formids of objects originating from this mods. - + failed to update esp info for file %1 (source id: %2), error: %3 - + Plugin not found: %1 - - + + Confirm - + Really enable all plugins? - + Really disable all plugins? - + The file containing locked plugin indices is broken - - - <b>Origin</b>: %1 + + Origin - - <br><b><i>This plugin can't be disabled (enforced by the game).</i></b> + + This plugin can't be disabled (enforced by the game). - + Author - + Description - + Missing Masters - + Enabled Masters - + Loads Archives - + There are Archives connected to this plugin. Their assets will be added to your game, overwriting in case of conflicts following the plugin order. Loose files will always overwrite assets from Archives. (This flag only checks for Archives from the same mod as the plugin) - + Loads INI settings - + There is an ini file connected to this plugin. Its settings will be added to your game settings, overwriting in case of conflicts. - + This ESP is flagged as an ESL. It will adhere to the ESP load order but the records will be loaded in ESL space. - - failed to restore load order for %1 + + Incompatible with %1 - - - PluginListSortProxy - - Drag&Drop is only supported when sorting by priority or mod index + + Depends on missing %1 - - - PreviewDialog - - Preview + + Warning + + + + + Error + + + + + failed to restore load order for %1 + + + + + PluginListSortProxy + + + Drag&Drop is only supported when sorting by priority or mod index + + + + + PreviewDialog + + + Preview @@ -4965,11 +5070,6 @@ Continue? p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Click a notification above to get more details...</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:7.8pt;"><br /></p></body></html> @@ -5326,33 +5426,53 @@ p, li { white-space: pre-wrap; } - QApplication + QObject - + + + + INI file is read-only - - - Mod Organizer is attempting to write to "%1" which is currently set to read-only. Clear the read-only flag to allow the write? + + + Mod Organizer is attempting to write to "%1" which is currently set to read-only. - - File is read-only + + + Clear the read-only flag + + + + + + Allow the write once + + + + + + The file will be set to read-only again. + + + + + + Skip this file - - - QObject - - - - + + + + + Error @@ -5379,8 +5499,8 @@ p, li { white-space: pre-wrap; } - - failed to open temporary file + + Failed to save '{}', could not create a temporary file: {} (error {}) @@ -5399,52 +5519,73 @@ p, li { white-space: pre-wrap; } - - + + Error %1 + + + + + failed to create directory "%1" - - + + failed to copy "%1" to "%2" - + %1 MB - + %1 GB - + %1 TB - + %1 KB - + %1 B/s - + %1 KB/s - + %1 MB/s + + + Regular + + + + + Premium + + + + + + None + + Failed to save custom categories @@ -5454,15 +5595,95 @@ p, li { white-space: pre-wrap; } - + invalid category index: %1 - + + <Active> + + + + + <Update available> + + + + + <Has category> + + + + + <Conflicted> + + + + + <Endorsed> + + + + + <Has backup> + + + + + <Managed> + + + + + <Has valid game data> + + + + + <Has Nexus ID> + + + + + <Tracked on Nexus> + + + + invalid category id: %1 + + + Is overwritten (loose files) + + + + + Is overwriting (loose files) + + + + + Is overwritten (archives) + + + + + Is overwriting (archives) + + + + + Mod contains selected plugin + + + + + Plugin is contained in selected mod + + invalid field name "%1" @@ -5504,34 +5725,40 @@ p, li { white-space: pre-wrap; } - + The hidden file "%1" already exists. Replace it? - + The visible file "%1" already exists. Replace it? - + Replace file? - - + + File operation failed - - Failed to remove "%1". Maybe you lack the required file permissions? + + Failed to remove "%1": %2 - - failed to rename %1 to %2 + + Failed to rename file: %1. + +Source: +"%2" + +Destination: +"%3" @@ -5670,122 +5897,214 @@ If the folder was still in use, restart MO and try again. - + failed to create %1 - + Data directory created - + New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings. - - + + General messages + + + + + Plugins + + + + + No messages. + + + + + Incompatibilities + + + + + Missing masters + + + + + Verified clean by %1 + + + + + %1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es). + + + + + + + Warning + + + + + failed to run loot: %1 + + + + + Checking masterlist existence + + + + + Updating masterlist + + + + + Loading lists + + + + + Reading plugins + + + + + Sorting plugins + + + + + Writing loadorder.txt + + + + + Parsing loot messages + + + + + Done + + + + + Failed to create "%1". Your user account probably lacks permission. - + Plugin to handle %1 no longer installed - - - + + + The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly. - + Could not use configuration settings for game "%1", path "%2". - - - + + + Please select the installation of %1 to manage - - - + + + Please select the game to manage - + Canceled finding %1 in "%2". - + Canceled finding game in "%1". - + %1 not identified in "%2". The directory is required to contain the game binary. - + No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul> - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - - + + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + + <Unmanaged> + + + + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5821,512 +6140,586 @@ If the folder was still in use, restart MO and try again. - - This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. + + Connecting to Nexus... - - This error typically happens because an antivirus is preventing Mod Organizer from starting programs. Add an exclusion for Mod Organizer's installation folder in your antivirus and try again. + + Waiting for Nexus... - - The file '%1' does not exist. + + Opened Nexus in browser. - - - - - Cannot start Steam + + Switch to your browser and accept the request. - - The path to the Steam executable cannot be found. You might try reinstalling Steam. + + Finished. - - - - Continue without starting Steam + + No answer from Nexus. - - - The program may fail to launch. + + + A firewall might be blocking Mod Organizer. - - Cannot launch program + + Nexus closed the connection. - - - - Cannot start %1 + + Cancelled. - - Cannot launch helper + + Failed to request %1 - - This program is requesting to run as administrator but Mod Organizer itself is not running as administrator. Running programs as administrator is typically unnecessary as long as the game and Mod Organizer have been installed outside "Program Files". - -You can restart Mod Organizer as administrator and try launching the program again. + + + Cancelled - - - Restart Mod Organizer as administrator + + Internal error - - - You must allow "helper.exe" to make changes to the system. + + HTTP code %1 - - Launch Steam + + Invalid JSON - - This program requires Steam + + Bad response - - Mod Organizer has detected that this program likely requires Steam to be running to function properly. + + API key is empty - - Start Steam + + SSL error - - - The program might fail to run. + + Timed out - - Steam is running as administrator + + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is likely to be incompatible with MO2's virtual filesystem. - - Running Steam as administrator is typically unnecessary and can cause problems when Mod Organizer itself is not running as administrator. - -You can restart Mod Organizer as administrator and try launching the program again. + + failed to initialize plugin %1: %2 - - - - Continue + + Plugin error - - Event Log not running + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? +(Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - - The Event Log service is not running + + failed to access %1 - - The Windows Event Log service is not running. This can prevent USVFS from running properly and your mods may not be recognized by the program being launched. + + failed to set file time %1 - - - Your mods might not work. + + + + No profile set - - Blacklisted program + + Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! - - The program %1 is blacklisted + + + attempt to store setting for unknown plugin "%1" - - The program you are attempting to launch is blacklisted in the virtual filesystem. This will likely prevent it from seeing any mods, INI files or any other virtualized files. + + Failed - - Change the blacklist + + Failed to start the helper application: %1 - - Waiting + + + Debug - - Please press OK once you're logged into steam. + + + Info (recommended) - - One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is incompatible with MO2's VFS system. + + Trace - - Select binary + + Mini (recommended) - - Binary + + Data - - failed to initialize plugin %1: %2 + + Full - - Plugin error + + Confirm? - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? -(Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) + + This will reset all the choices you made to dialogs and make them all visible again. Continue? - - failed to access %1 + + Connected. - - failed to set file time %1 + + Not connected. - - Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! + + Disconnected. - - - Elevation required + + Checking API key... - - This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. + + Received API key. - - Loading... + + Received user acount information - - &Save + + Linked with Nexus successfully. - - &Word wrap + + Failed to set API key - - &Open in Explorer + + + + + + + + + + + + Cancel - - Regular + + + + Enter API Key Manually - - Premium + + + + Connect to Nexus - - - None + + + + + + N/A - - - Connecting to Nexus... + + Failed to create "%1", you may not have the necessary permissions. Path remains unchanged. - - Waiting for Nexus... + + Select base directory - - Opened Nexus in browser. -Switch to your browser and accept the request. + + Select download directory - - - Finished. + + Select mod directory - - No answer from Nexus. -A firewall might be blocking Mod Organizer. + + Select cache directory - - Nexus closed the connection. + + Select profiles directory - - Cancelled. + + Select overwrite directory - - Invalid JSON + + Select game executable - - Bad response + + Executables Blacklist - - There was a timeout during the request + + Enter one executable per line to be blacklisted from the virtual file system. +Mods and other virtualized files will not be visible to these executables and +any executables launched by them. + +Example: + Chrome.exe + Firefox.exe - - Cancelled + + + + Restart Mod Organizer - - Failed to request %1 + + Geometries will be reset to their default values. - - - attempt to store setting for unknown plugin "%1" + + This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. - - Failed + + This error typically happens because an antivirus is preventing Mod Organizer from starting programs. Add an exclusion for Mod Organizer's installation folder in your antivirus and try again. - - Failed to start the helper application + + The file '%1' does not exist. - - Debug + + + + + Cannot start Steam - - Info (recommended) + + The path to the Steam executable cannot be found. You might try reinstalling Steam. - - Warning + + + + Continue without starting Steam - - Mini (recommended) + + + The program may fail to launch. - - Data + + Cannot launch program - - Full + + + + Cannot start %1 - - Confirm? + + Cannot launch helper + + + + + + Elevation required + + + + + This program is requesting to run as administrator but Mod Organizer itself is not running as administrator. Running programs as administrator is typically unnecessary as long as the game and Mod Organizer have been installed outside "Program Files". + +You can restart Mod Organizer as administrator and try launching the program again. + + + + + + Restart Mod Organizer as administrator + + + + + + You must allow "helper.exe" to make changes to the system. + + + + + Launch Steam + + + + + This program requires Steam + + + + + Mod Organizer has detected that this program likely requires Steam to be running to function properly. + + + + + Start Steam + + + + + + The program might fail to run. + + + + + Steam is running as administrator + + + + + Running Steam as administrator is typically unnecessary and can cause problems when Mod Organizer itself is not running as administrator. + +You can restart Mod Organizer as administrator and try launching the program again. + + + + + + + Continue + + + + + Event Log not running + + + + + The Event Log service is not running - - This will reset all the choices you made to dialogs and make them all visible again. Continue? + + The Windows Event Log service is not running. This can prevent USVFS from running properly and your mods may not be recognized by the program being launched. - - Disconnected. + + + Your mods might not work. - - Checking API key... + + Blacklisted program - - Received API key. + + The program %1 is blacklisted - - Linked with Nexus successfully. + + The program you are attempting to launch is blacklisted in the virtual filesystem. This will likely prevent it from seeing any mods, INI files or any other virtualized files. - - - - - - - - - - Cancel + + Change the blacklist - - - - Enter API Key Manually + + Waiting - - - - Connect to Nexus + + Please press OK once you're logged into steam. - - - - - - N/A + + Select binary - - Failed to create "%1", you may not have the necessary permissions. Path remains unchanged. + + Binary - - Select base directory + + This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. - - Select download directory + + Loading... - - Select mod directory + + &Save - - Select cache directory + + &Word wrap - - Select profiles directory + + &Open in Explorer - - Select overwrite directory + + Mod Organizer is locked while the application is running. - - Select game executable + + Mod Organizer is currently running an application. - - Executables Blacklist + + The application must run to completion because its output is required. - - Enter one executable per line to be blacklisted from the virtual file system. -Mods and other virtualized files will not be visible to these executables and -any executables launched by them. - -Example: - Chrome.exe - Firefox.exe + + Mod Organizer is waiting on application to close before exiting. - - Restart Mod Organizer? + + Unlock - - In order to reset the geometry, Mod Organizer must be restarted. -Restart now? + + Exit Now @@ -6445,58 +6838,58 @@ Restart now? - + New update available (%1) - + Do you want to install update? All your mods and setup will be left untouched. Select Show Details option to see the full change-log. - + Install - + Download failed - + Failed to find correct download, please try again later. - + Update - + Download in progress - + Download failed: %1 - + Failed to install update: %1 - - Failed to start %1 + + Failed to start %1: %2 - + Error @@ -6514,473 +6907,488 @@ Select Show Details option to see the full change-log. - + + User Interface + + + + Language - - The display language + + Style - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + + + Visual theme of the user interface. - - Style + + Explore... - - graphical style + + + The language of the user interface. - - graphical style of the MO user interface + + https://www.transifex.com/tannin/mod-organizer/ - - Update to non-stable releases. + + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> - - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. - If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas). - -Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports. - -If you use pre-releases, never contact me directly by e-mail or via private messages! + + + Dialogs will always be centered on the main window, but will remember their size. - - Install Pre-releases (Betas) + + Always center dialogs - - User interface + + Show confirmation when changing instance - - Colors + + Whether double-clicking on a file opens the preview window or launches the program associated with it. This applies to the Data tab as well as the Conflicts and Filetree tabs in the mod info window. - - - When this is enabled, the color defined for a separator will be shown on the mod list scrollbar at the location of the separator. This can be useful for quick navigation between separator sections or to a specific separator section. + + Open previews on double-click - - Show mod list separator colors on the scrollbar + + + Reset all choices made in dialogs. - - Plugin is Contained in selected Mod + + Reset Dialog Choices - - Is overwritten (loose files) + + + Modify the categories available to arrange your mods. - - Is overwriting (loose files) + + Configure Mod Categories - - Reset Colors + + Download List - - Mod Contains selected Plugin + + + Show meta information instead of file names in the download list. - - Is overwritten (archive files) + + Show Meta Information - - Is overwriting (archive files) + + + Make the download list more compact. - - - Modify the categories available to arrange your mods. + + Compact List - - Configure Mod Categories + + Colors - - Reset stored information from dialogs. + + + Colors set on separators will also be shown in the mod list scrollbar at the location of the separator. This can be useful for quickly navigating to a specific separator. - - This will make all dialogs show up again where you checked the "Remember selection"-box. + + Show mod list separator colors on the scrollbar - - If checked, the download interface will be more compact. + + + Reset all colors to their default value. - - Compact Download Interface + + Reset Colors - - If checked, the download list will display meta information instead of file names. + + Updates + + + + + + Check for Mod Organizer updates on Github on startup. - - Download Meta Information + + Check for updates + + + + + + Update to non-stable releases. + + + + + Install Pre-releases (Betas) - + Paths - - - - + + + + ... - + Caches - + Overwrite - - + + Directory where downloads are stored. - + Downloads - + Profiles - + Directory where mods are stored. - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Mods - + Managed Game - + Base Directory - + Use %BASE_DIR% to refer to the Base Directory. - + Important: All directories have to be writable! - + Nexus - - Connect to Nexus - - - - - Manually enter the API key and try to login + + Nexus Account - - Enter API Key Manually + + User ID: - - Clear the stored Nexus API key and force reauthorization. + + id - - Disconnect from Nexus + + Name: - - - <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> + + name - - Remove cache and cookies. + + Account: - - Clear Cache + + account - - Disable automatic internet features + + Statistics - - Reset Dialog Choices + + Daily requests: - - Nexus Account + + daily requests - - User ID: + + Hourly requests: - - id + + hourly requests - - Name: + + Nexus Connection - - name + + Connect to Nexus - - Account: + + Manually enter the API key and try to login - - account + + Enter API Key Manually - - Statistics + + Clear the stored Nexus API key and force reauthorization. - - Daily requests: + + Disconnect from Nexus - - daily requests + + Disable automatic internet features - - Hourly requests: + + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - - hourly requests + + Offline Mode - - Nexus Connection + + Use a proxy for network connections. - - Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) + + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - - Offline Mode + + Use HTTP Proxy (Uses System Settings) - - Use a proxy for network connections. + + Endorsement Integration - - Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. + + + <html><head/><body><p>By default, a counter is displayed in the bottom right corner. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> - - Use HTTP Proxy (Uses System Settings) + + Hide API Request Counter - - Endorsement Integration + + Associate with "Download with manager" links - - Hide API Request Counter + + Remove cache and cookies. - - Associate with "Download with manager" links + + Clear Cache - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + Username - + <html><head/><body><p>If you save your steam user ID and password here, they will be used when logging into steam.</p></body></html> - + Password - + Plugins - + Author: - + Version: - + Description: - + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -6996,17 +7404,17 @@ p, li { white-space: pre-wrap; } - + Load Mechanism - + Select loading mechanism. See help for details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -7017,28 +7425,28 @@ If you use the Steam version of Oblivion the default will NOT work. In this case - + Enforces that inactive ESPs and ESMs are never loaded. - + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + Hide inactive ESPs/ESMs - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -7046,66 +7454,66 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable parsing of Archives (Experimental Feature) - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -7114,81 +7522,81 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Configure Executables Blacklist - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - - Max Dumps To Keep + + Hint: right click link and copy link location - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + + + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> + and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. + Sending logs and/or crash dumps to the developers can help investigate issues. + It is recommended to compress large log and dmp files before sending. + - - - Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - Set "Crash Dumps" above to None to disable crash dump collection. - + + Log Level - - Hint: right click link and copy link location + + Decides the amount of data printed to "ModOrganizer.log" - + - Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> - and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. - Sending logs and/or crash dumps to the developers can help investigate issues. - It is recommended to compress large log and dmp files before sending. - + Decides the amount of data printed to "ModOrganizer.log". + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. + - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -7199,41 +7607,35 @@ programs you are intentionally running. - - Log Level + + Max Dumps To Keep - - Decides the amount of data printed to "ModOrganizer.log" + + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + - Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. + Set "Crash Dumps" above to None to disable crash dump collection. - - Restart Mod Organizer? + + LOOT Log Level - - In order to finish configuration changes, MO must be restarted. -Restart it now? - - - - + Confirm - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? @@ -7241,22 +7643,22 @@ Restart it now? SingleInstance - + SHM error: %1 - + failed to connect to running instance: %1 - + failed to communicate with running instance: %1 - + failed to receive data from secondary instance: %1 @@ -7441,7 +7843,7 @@ On Windows XP: UsvfsConnector - + Preparing vfs @@ -7449,41 +7851,29 @@ On Windows XP: ValidationProgressDialog - + Validating Nexus Connection - - Hide - - - - - WaitingOnCloseDialog - - - Waiting for virtualized processes - - - - - This dialog should disappear automatically if the application/game is done. + + + Connecting to Nexus... - - Virtualized processes are still running, it is prefered to keep MO running until they are finished. + + Cancel - - Close Now + + Hide - - Cancel + + Trying again... @@ -7801,7 +8191,6 @@ Please open the "Nexus"-tab Use this interface to obtain an API key from NexusMods. This is used for all API connections - downloads, updates etc. MO2 uses the Windows Credential Manager to store this data securely. If the SSO page on Nexus is failing, use the manual entry and copy the API key from your profile. - Use this interface to obtain an API key from NexusMods.This is used for all API connections - downloads, updatesetc. MO2 uses the Windows Credential Manager to storethis data securely. If the SSO page on Nexus is failing,use the manual entry and copy the API key from your profile. -- cgit v1.3.1 From d13ea08be82d88c4f0b845983a3390729723d0e1 Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 6 Dec 2019 23:18:37 -0600 Subject: Fix mistakenly added modinfo tab ID --- src/mainwindow.cpp | 1 - src/modinfodialogfwd.h | 1 - 2 files changed, 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 68627c90..ebeb7459 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3897,7 +3897,6 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; case ModList::COL_CONFLICTFLAGS: tab = ModInfoTabIDs::Conflicts; break; - case ModList::COL_FLAGS: tab = ModInfoTabIDs::Flags; break; } displayModInformation(sourceIdx.row(), tab); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h index e4a61208..2147fc04 100644 --- a/src/modinfodialogfwd.h +++ b/src/modinfodialogfwd.h @@ -14,7 +14,6 @@ enum class ModInfoTabIDs Images, Esps, Conflicts, - Flags, Categories, Nexus, Notes, -- cgit v1.3.1 From 267985a62dd90b1b66c14ba32436f80907f97edb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Dec 2019 02:29:39 -0500 Subject: removed unused on_clickBlankButton_clicked() --- src/mainwindow.cpp | 5 - src/mainwindow.h | 3 - src/organizer_en.ts | 364 ++++++++++++++++++++++++++-------------------------- 3 files changed, 182 insertions(+), 190 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ebeb7459..43d5b820 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6719,11 +6719,6 @@ void MainWindow::keyReleaseEvent(QKeyEvent *event) QMainWindow::keyReleaseEvent(event); } -void MainWindow::on_clickBlankButton_clicked() -{ - deselectFilters(); -} - void MainWindow::on_clearFiltersButton_clicked() { ui->modFilterEdit->clear(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 98573423..2020b1a3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -199,9 +199,6 @@ protected: private slots: void on_actionChange_Game_triggered(); -private slots: - void on_clickBlankButton_clicked(); - private: void cleanup(); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 72972e4f..61348380 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1851,7 +1851,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2027,8 +2027,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2313,7 +2313,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2434,8 +2434,8 @@ Error: %1 - - + + Endorse @@ -2616,16 +2616,16 @@ Error: %1 - - + + failed to rename "%1" to "%2" - - - + + + Confirm @@ -2714,7 +2714,7 @@ Error: %1 - + Create Mod... @@ -2759,7 +2759,7 @@ Please enter a name: - + Are you sure? @@ -2770,435 +2770,435 @@ Please enter a name: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3206,12 +3206,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3219,330 +3219,330 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6099,7 +6099,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> -- cgit v1.3.1 From eb55bea357f0f0009f7fb8fd66113312ac0d67c8 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 7 Dec 2019 02:08:57 -0700 Subject: Move isa to lead developers --- src/aboutdialog.ui | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 424a80f9..4a388d6d 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -215,6 +215,11 @@ LostDragonist + + + isanae + + @@ -241,11 +246,6 @@ erasmux - - - isanae - - Project579 -- cgit v1.3.1 From fbe0e8008a69bf43a8a89fe1f862b80c9b3d60ce Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 7 Dec 2019 02:11:35 -0700 Subject: Do not translate isa's name --- src/aboutdialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 4a388d6d..df59e43c 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -217,7 +217,7 @@ - isanae + isanae -- cgit v1.3.1 From 9d8861d92bab1b8f5e810c192f23f723beae86af Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 7 Dec 2019 04:44:55 -0500 Subject: bumped to rc1 --- src/version.rc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 963d1f11..3d431488 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,2,8 -#define VER_FILEVERSION_STR "2.2.2alpha8\0" +#define VER_FILEVERSION 2,2,2 +#define VER_FILEVERSION_STR "2.2.2rc1\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From f18e4db4fb30f8f88e30e691f14653327a31ad02 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 7 Dec 2019 14:29:51 -0700 Subject: Add Xahtax as a German translator --- src/aboutdialog.ui | 5 +++++ src/organizer_en.ts | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index df59e43c..48cb068c 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -327,6 +327,11 @@ pndrev (German) + + + Xahtax (German) + + yohru (Japanese) diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 61348380..faff25ac 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -45,7 +45,7 @@ - + MO2 Developers && Contributors @@ -75,57 +75,57 @@ - + yohru (Japanese) - + Mordan (Greek) - + Yoosk (Polish) - + Brgodfx (Portuguese) - + zDas (Portuguese) - + Jax (Swedish) - + Nubbie (Swedish) - + ...and all other contributors! - + Other Supporters && Contributors - + Tannin (Original Creator) - + Close -- cgit v1.3.1 From 2c59d13be645bb8c12adae883938c9a8652f3dee Mon Sep 17 00:00:00 2001 From: Silarn Date: Sat, 7 Dec 2019 19:31:20 -0600 Subject: Update transifex location --- src/settingsdialog.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 84ca5731..b836e321 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -102,10 +102,10 @@ - https://www.transifex.com/tannin/mod-organizer/ + https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/ - <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> + <a href="https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/">Help translate Mod Organizer</a> true -- cgit v1.3.1 From c9049bb07274aaefa79c9751ec40910bf8daf0ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 8 Dec 2019 11:33:03 -0500 Subject: conflicts tab: run exes unhooked by default --- src/mainwindow.cpp | 3 +- src/modinfodialogconflicts.cpp | 72 +++++++++++++++++++++++------------------- src/modinfodialogconflicts.h | 5 ++- src/modinfodialogfiletree.cpp | 3 +- src/processrunner.cpp | 70 ++++++++++++++++++++++++++-------------- src/processrunner.h | 8 +++-- 6 files changed, 97 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 43d5b820..532d8502 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5425,7 +5425,8 @@ void MainWindow::runDataFileHooked() const QFileInfo targetInfo(path); m_OrganizerCore.processRunner() - .setFromFile(this, targetInfo, true) + .setFromFile(this, targetInfo) + .setHooked(true) .setWaitForCompletion(ProcessRunner::Refresh) .run(); } diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 9c7ccc8c..81e8c7a3 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -542,45 +542,32 @@ void ConflictsTab::activateItems(QTreeView* tree) if (tryPreview && canPreviewFile(plugin(), item->isArchive(), path)) { previewItem(item); } else { - openItem(item); + openItem(item, false); } return true; }); } -void ConflictsTab::openItems(QTreeView* tree) +void ConflictsTab::openItems(QTreeView* tree, bool hooked) { // the menu item is only shown for a single selection, but handle all of them // in case this changes for_each_in_selection(tree, [&](const ConflictItem* item) { - openItem(item); + openItem(item, hooked); return true; }); } -void ConflictsTab::openItem(const ConflictItem* item) +void ConflictsTab::openItem(const ConflictItem* item, bool hooked) { core().processRunner() .setFromFile(parentWidget(), item->fileName()) + .setHooked(hooked) .setWaitForCompletion() .run(); } -void ConflictsTab::runItemsHooked(QTreeView* tree) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for_each_in_selection(tree, [&](const ConflictItem* item) { - core().processRunner() - .setFromFile(parentWidget(), item->fileName(), true) - .setWaitForCompletion() - .run(); - - return true; - }); -} - void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them @@ -615,30 +602,44 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) // open if (actions.open) { connect(actions.open, &QAction::triggered, [&]{ - openItems(tree); + openItems(tree, false); }); + } - menu.addAction(actions.open); + // preview + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewItems(tree); + }); + } + + if ((actions.open && actions.open->isEnabled()) && (actions.preview && actions.preview->isEnabled())) { + if (Settings::instance().interface().doubleClicksOpenPreviews()) { + menu.addAction(actions.preview); + menu.addAction(actions.open); + } else { + menu.addAction(actions.open); + menu.addAction(actions.preview); + } + } else { + if (actions.open) { + menu.addAction(actions.open); + } + + if (actions.preview) { + menu.addAction(actions.preview); + } } // run hooked if (actions.runHooked) { connect(actions.runHooked, &QAction::triggered, [&]{ - runItemsHooked(tree); + openItems(tree, true); }); menu.addAction(actions.runHooked); } - // preview - if (actions.preview) { - connect(actions.preview, &QAction::triggered, [&]{ - previewItems(tree); - }); - - menu.addAction(actions.preview); - } - // goto if (actions.gotoMenu) { menu.addMenu(actions.gotoMenu); @@ -681,9 +682,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.unhide); } - setDefaultActivationActionForFile(actions.open, actions.preview); - if (!menu.isEmpty()) { + if (actions.open || actions.preview || actions.runHooked) { + // bold the first option + auto* top = menu.actions()[0]; + auto f = top->font(); + f.setBold(true); + top->setFont(f); + } + menu.exec(tree->viewport()->mapToGlobal(pos)); } } @@ -769,6 +776,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) if (enableRun) { actions.open = new QAction(tr("&Execute"), parentWidget()); + actions.runHooked = new QAction(tr("Execute with &VFS"), parentWidget()); } else if (enableOpen) { actions.open = new QAction(tr("&Open"), parentWidget()); actions.runHooked = new QAction(tr("Open with &VFS"), parentWidget()); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 3ac8de23..1297e536 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -108,12 +108,11 @@ public: bool canHandleUnmanaged() const override; void activateItems(QTreeView* tree); - void openItems(QTreeView* tree); - void runItemsHooked(QTreeView* tree); + void openItems(QTreeView* tree, bool hooked); void previewItems(QTreeView* tree); void exploreItems(QTreeView* tree); - void openItem(const ConflictItem* item); + void openItem(const ConflictItem* item, bool hooked); void previewItem(const ConflictItem* item); void changeItemsVisibility(QTreeView* tree, bool visible); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index d1ae3823..0b9e8da4 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -183,7 +183,8 @@ void FileTreeTab::onRunHooked() } core().processRunner() - .setFromFile(parentWidget(), m_fs->filePath(selection), true) + .setFromFile(parentWidget(), m_fs->filePath(selection)) + .setHooked(true) .setWaitForCompletion() .run(); } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 46065d69..19aae632 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -421,8 +421,8 @@ ProcessRunner::ProcessRunner(OrganizerCore& core, IUserInterface* ui) : m_core(core), m_ui(ui), m_lockReason(UILocker::NoReason), m_waitFlags(NoFlags), m_handle(INVALID_HANDLE_VALUE), m_exitCode(-1) { - // all processes started in ProcessRunner are hooked - m_sp.hooked = true; + // all processes started in ProcessRunner are hooked by default + setHooked(true); } ProcessRunner& ProcessRunner::setBinary(const QFileInfo &binary) @@ -475,8 +475,14 @@ ProcessRunner& ProcessRunner::setWaitForCompletion( return *this; } +ProcessRunner& ProcessRunner::setHooked(bool b) +{ + m_sp.hooked = b; + return *this; +} + ProcessRunner& ProcessRunner::setFromFile( - QWidget* parent, const QFileInfo& targetInfo, bool forceHook) + QWidget* parent, const QFileInfo& targetInfo) { if (!parent && m_ui) { parent = m_ui->qtWidget(); @@ -500,24 +506,8 @@ ProcessRunner& ProcessRunner::setFromFile( case spawn::FileExecutionTypes::Other: // fall-through default: { - if (forceHook) { - auto assoc = env::getAssociation(targetInfo); - if (!assoc.executable.filePath().isEmpty()) { - setBinary(assoc.executable); - setArguments(assoc.formattedCommandLine); - setCurrentDirectory(assoc.executable.absoluteDir()); - - return *this; - } - - // if it fails, just use the regular shell open - } - - m_shellOpen = targetInfo.absoluteFilePath(); - - // picked up by postRun() - m_sp.hooked = false; - + m_shellOpen = targetInfo; + setHooked(false); break; } } @@ -649,11 +639,41 @@ ProcessRunner& ProcessRunner::setFromFileOrExecutable( return *this; } +bool ProcessRunner::shouldRunShell() const +{ + return !m_shellOpen.filePath().isEmpty(); +} + ProcessRunner::Results ProcessRunner::run() { + // check if setHooked() was called after setFromFile(); this needs to + // modify the settings to run the associated executable instead of using + // shell::Open() + + if (shouldRunShell() && m_sp.hooked) { + // this is a non-executable file, but it should be hooked; the associated + // executable needs to be retrieved and run instead + auto assoc = env::getAssociation(m_shellOpen); + if (!assoc.executable.filePath().isEmpty()) { + setBinary(assoc.executable); + setArguments(assoc.formattedCommandLine); + setCurrentDirectory(assoc.executable.absoluteDir()); + m_shellOpen = {}; + } else { + // if it fails, just use the regular shell open + log::error("failed to get the associated executable, running unhooked"); + m_sp.hooked = false; + } + } else if (!shouldRunShell() && !m_sp.hooked) { + // this is an executable that should not be hooked; just run it through + // the shell + m_shellOpen = m_sp.binary; + } + + std::optional r; - if (!m_shellOpen.isEmpty()) { + if (shouldRunShell()) { r = runShell(); } else { r = runBinary(); @@ -669,9 +689,11 @@ ProcessRunner::Results ProcessRunner::run() std::optional ProcessRunner::runShell() { - log::debug("executing from shell: '{}'", m_shellOpen); + const auto file = m_shellOpen.absoluteFilePath(); + + log::debug("executing from shell: '{}'", file); - auto r = shell::Open(m_shellOpen); + auto r = shell::Open(file); if (!r.success()) { return Error; } diff --git a/src/processrunner.h b/src/processrunner.h index d576216a..1bfdc465 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -67,6 +67,7 @@ public: ProcessRunner& setProfileName(const QString& profileName); ProcessRunner& setWaitForCompletion( WaitFlags flags=NoFlags, UILocker::Reasons reason=UILocker::LockUI); + ProcessRunner& setHooked(bool b); // - if the target is an executable file, runs it hooked // - if the target is a file: @@ -74,8 +75,7 @@ public: // - if forceHook is true, gets the executable associated with the file // and runs that hooked by passing the file as an argument // - ProcessRunner& setFromFile( - QWidget* parent, const QFileInfo& targetInfo, bool forceHook = false); + ProcessRunner& setFromFile(QWidget* parent, const QFileInfo& targetInfo); ProcessRunner& setFromExecutable(const Executable& exe); ProcessRunner& setFromShortcut(const MOShortcut& shortcut); @@ -150,11 +150,13 @@ private: QString m_profileName; UILocker::Reasons m_lockReason; WaitFlags m_waitFlags; - QString m_shellOpen; + QFileInfo m_shellOpen; env::HandlePtr m_handle; DWORD m_exitCode; + bool shouldRunShell() const; + // runs the command in m_shellOpen; returns empty if it can be waited for // std::optional runShell(); -- cgit v1.3.1 From 8c3a3e8257e63328298c91c71740ac3a550b70d6 Mon Sep 17 00:00:00 2001 From: Al Date: Sun, 8 Dec 2019 21:05:58 +0100 Subject: Changed default behavior for "Open Previews on double click" to true since it's probably the most desirable option. --- src/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 68dc19d9..5170a5de 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1973,7 +1973,7 @@ void InterfaceSettings::setShowChangeGameConfirmation(bool b) bool InterfaceSettings::doubleClicksOpenPreviews() const { - return get(m_Settings, "Settings", "double_click_previews", false); + return get(m_Settings, "Settings", "double_click_previews", true); } void InterfaceSettings::setDoubleClicksOpenPreviews(bool b) -- cgit v1.3.1 From df54f972fff57a7c7afe8dfb44efda43d76e8c78 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sun, 8 Dec 2019 16:15:40 -0600 Subject: Rework logic to parse by visual index --- src/mainwindow.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 43d5b820..274e07c1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2310,9 +2310,9 @@ void MainWindow::processUpdates(Settings& settings) { if (!settings.firstStart()) { if (lastVersion < QVersionNumber(2, 1, 3)) { bool lastHidden = true; - for (int i = ModList::COL_GAME; i < ui->modList->model()->columnCount(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(i); - ui->modList->header()->setSectionHidden(i, lastHidden); + for (int i = ui->modList->header()->visualIndex(ModList::COL_GAME); i < ui->modList->header()->count(); ++i) { + bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); + ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); lastHidden = hidden; } } @@ -2330,9 +2330,9 @@ void MainWindow::processUpdates(Settings& settings) { if (lastVersion < QVersionNumber(2, 2, 2)) { bool lastHidden = true; - for (int i = ModList::COL_CONFLICTFLAGS; i < ui->modList->model()->columnCount(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(i); - ui->modList->header()->setSectionHidden(i, lastHidden); + for (int i = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); i < ui->modList->header()->count(); ++i) { + bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); + ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); lastHidden = hidden; } } -- cgit v1.3.1 From 27c05cebd58ea33e0465b886d47a2dd9d8fc2ddc Mon Sep 17 00:00:00 2001 From: Al Date: Mon, 9 Dec 2019 00:56:10 +0100 Subject: Change compact size for the conflicts column to be shorter since there are less flags here. --- src/mainwindow.cpp | 2 +- src/modconflicticondelegate.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 274e07c1..8eff26c5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -554,7 +554,7 @@ void MainWindow::setupModList() ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate( - ui->modList, ModList::COL_CONFLICTFLAGS, 120); + ui->modList, ModList::COL_CONFLICTFLAGS, 80); connect( ui->modList->header(), SIGNAL(sectionResized(int, int, int)), diff --git a/src/modconflicticondelegate.h b/src/modconflicticondelegate.h index d36477c6..8645da12 100644 --- a/src/modconflicticondelegate.h +++ b/src/modconflicticondelegate.h @@ -8,7 +8,7 @@ class ModConflictIconDelegate : public IconDelegate Q_OBJECT; public: - explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 120); + explicit ModConflictIconDelegate(QObject *parent = 0, int logicalIndex = -1, int compactSize = 80); virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; static QList getIconsForFlags( -- cgit v1.3.1 From a4db0db94ff67b52a7a3b9b43eca5cce5fae5fc3 Mon Sep 17 00:00:00 2001 From: Al Date: Mon, 9 Dec 2019 01:17:27 +0100 Subject: Add alternatingRowColors to saves and data tab. --- src/mainwindow.ui | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 309e9f62..a7e7b937 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1076,6 +1076,9 @@ p, li { white-space: pre-wrap; } This is an overview of your data directory as visible to the game (and tools). + + true + true @@ -1165,6 +1168,9 @@ p, li { white-space: pre-wrap; } <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + true + QAbstractItemView::ExtendedSelection -- cgit v1.3.1 From 44084bfa415f906b8ba8b5d19e1be60a7612d692 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sun, 8 Dec 2019 20:47:45 -0600 Subject: Rework column insertion to move to the intended position post-facto --- src/main.cpp | 2 -- src/mainwindow.cpp | 41 ++++++++++++++++++++++++++--------------- src/mainwindow.h | 2 +- src/modlist.h | 5 ++--- 4 files changed, 29 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 1f6c57d1..eeabd497 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -735,8 +735,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); - mainWindow.processUpdates(settings); - // this must be before readSettings(), see DockFixer in mainwindow.cpp splash.finish(&mainWindow); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8eff26c5..585be402 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -178,6 +178,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #ifdef TEST_MODELS #include "modeltest.h" @@ -511,6 +512,7 @@ MainWindow::MainWindow(Settings &settings resetActionIcons(); updatePluginCount(); updateModCount(); + processUpdates(); } void MainWindow::setupModList() @@ -2299,7 +2301,8 @@ void MainWindow::readSettings() } } -void MainWindow::processUpdates(Settings& settings) { +void MainWindow::processUpdates() { + auto& settings = m_OrganizerCore.settings(); const auto earliest = QVersionNumber::fromString("2.1.2").normalized(); const auto lastVersion = settings.version().value_or(earliest); @@ -2308,15 +2311,6 @@ void MainWindow::processUpdates(Settings& settings) { settings.processUpdates(currentVersion, lastVersion); if (!settings.firstStart()) { - if (lastVersion < QVersionNumber(2, 1, 3)) { - bool lastHidden = true; - for (int i = ui->modList->header()->visualIndex(ModList::COL_GAME); i < ui->modList->header()->count(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); - ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); - lastHidden = hidden; - } - } - if (lastVersion < QVersionNumber(2, 1, 6)) { ui->modList->header()->setSectionHidden(ModList::COL_NOTES, true); } @@ -2329,11 +2323,28 @@ void MainWindow::processUpdates(Settings& settings) { } if (lastVersion < QVersionNumber(2, 2, 2)) { - bool lastHidden = true; - for (int i = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); i < ui->modList->header()->count(); ++i) { - bool hidden = ui->modList->header()->isSectionHidden(ui->modList->header()->logicalIndex(i)); - ui->modList->header()->setSectionHidden(ui->modList->header()->logicalIndex(i), lastHidden); - lastHidden = hidden; + int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); + int pos2 = 0; + if (pos1) { + ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); + pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); + ui->modList->header()->moveSection( + ui->modList->header()->visualIndexAt(pos2), + ui->modList->header()->visualIndexAt(pos1) + ); + } + } + } else { + { // Move conflict flags + int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); + int pos2 = 0; + if (pos1) { + ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); + pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); + ui->modList->header()->moveSection( + ui->modList->header()->visualIndexAt(pos2), + ui->modList->header()->visualIndexAt(pos1) + ); } } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 2020b1a3..fcbbe039 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -116,7 +116,7 @@ public: QWidget *parent = 0); ~MainWindow(); - void processUpdates(Settings& settings); + void processUpdates(); QWidget* qtWidget() override; diff --git a/src/modlist.h b/src/modlist.h index 8841ec29..ce439f7b 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -57,7 +57,6 @@ public: enum EColumn { COL_NAME, - COL_CONFLICTFLAGS, COL_FLAGS, COL_CONTENT, COL_CATEGORY, @@ -67,8 +66,8 @@ public: COL_INSTALLTIME, COL_PRIORITY, COL_NOTES, - - COL_LASTCOLUMN = COL_NOTES + COL_CONFLICTFLAGS, + COL_LASTCOLUMN = COL_CONFLICTFLAGS, }; typedef boost::signals2::signal SignalModStateChanged; -- cgit v1.3.1 From 852afd126329aa6e77c38c2b137bc2573cbd7203 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 09:34:18 -0500 Subject: filetree tab: run exes unhooked by default --- src/modinfodialogfiletree.cpp | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 0b9e8da4..607d2e5b 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -171,6 +171,7 @@ void FileTreeTab::onOpen() const auto path = m_fs->filePath(selection); core().processRunner() .setFromFile(parentWidget(), path) + .setHooked(false) .setWaitForCompletion() .run(); } @@ -182,8 +183,9 @@ void FileTreeTab::onRunHooked() return; } + const auto path = m_fs->filePath(selection); core().processRunner() - .setFromFile(parentWidget(), m_fs->filePath(selection)) + .setFromFile(parentWidget(), path) .setHooked(true) .setWaitForCompletion() .run(); @@ -453,17 +455,35 @@ void FileTreeTab::onContextMenu(const QPoint &pos) if (enableRun) { m_actions.open->setText(tr("&Execute")); - menu.addAction(m_actions.open); + m_actions.runHooked->setText(tr("Execute with &VFS")); } else if (enableOpen) { m_actions.open->setText(tr("&Open")); - menu.addAction(m_actions.open); - menu.addAction(m_actions.runHooked); + m_actions.runHooked->setText(tr("Open with &VFS")); } - menu.addAction(m_actions.preview); m_actions.preview->setEnabled(enablePreview); - setDefaultActivationActionForFile(m_actions.open, m_actions.preview); + if ((enableRun || enableOpen) && enablePreview) { + if (Settings::instance().interface().doubleClicksOpenPreviews()) { + menu.addAction(m_actions.preview); + menu.addAction(m_actions.open); + } else { + menu.addAction(m_actions.open); + menu.addAction(m_actions.preview); + } + } else { + if (enableOpen || enableRun) { + menu.addAction(m_actions.open); + } + + if (enablePreview) { + menu.addAction(m_actions.preview); + } + } + + if (enableOpen || enableRun) { + menu.addAction(m_actions.runHooked); + } menu.addAction(m_actions.explore); m_actions.explore->setEnabled(enableExplore); @@ -487,5 +507,16 @@ void FileTreeTab::onContextMenu(const QPoint &pos) menu.addAction(m_actions.unhide); m_actions.unhide->setEnabled(enableUnhide); + if (enableOpen || enableRun || enablePreview) { + // bold the first option, unbold all the others + for (int i=0; ifont(); + f.setBold(i == 0); + a->setFont(f); + } + } + } + menu.exec(ui->filetree->viewport()->mapToGlobal(pos)); } -- cgit v1.3.1 From 5521b12fb30c525b5d4b919e355266de9fb524ef Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 09:52:29 -0500 Subject: data tab: run exes unhooked by default --- src/mainwindow.cpp | 129 +++++++++---- src/mainwindow.h | 1 + src/organizer_en.ts | 518 +++++++++++++++++++++++++++------------------------- 3 files changed, 361 insertions(+), 287 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 532d8502..d3474838 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1827,27 +1827,6 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) } } -void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) -{ - const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); - const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); - - if (isArchive || isDirectory) { - return; - } - - const QString path = item->data(0, Qt::UserRole).toString(); - const QFileInfo targetInfo(path); - - const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); - - if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { - previewDataFile(item); - } else { - openDataFile(item); - } -} - bool MainWindow::refreshProfiles(bool selectProfile) { QComboBox* profileBox = findChild("profileBox"); @@ -5373,19 +5352,25 @@ void MainWindow::disableSelectedMods_clicked() } -void MainWindow::previewDataFile() +void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) { - if (m_ContextItem == nullptr) { + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { return; } - previewDataFile(m_ContextItem); -} + const QString path = item->data(0, Qt::UserRole).toString(); + const QFileInfo targetInfo(path); -void MainWindow::previewDataFile(QTreeWidgetItem* item) -{ - QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore.previewFileWithAlternatives(this, fileName); + const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); + + if (tryPreview && m_PluginContainer.previewGenerator().previewSupported(targetInfo.suffix())) { + previewDataFile(item); + } else { + openDataFile(item); + } } void MainWindow::openDataFile() @@ -5411,6 +5396,7 @@ void MainWindow::openDataFile(QTreeWidgetItem* item) m_OrganizerCore.processRunner() .setFromFile(this, targetInfo) + .setHooked(false) .setWaitForCompletion(ProcessRunner::Refresh) .run(); } @@ -5421,7 +5407,19 @@ void MainWindow::runDataFileHooked() return; } - const QString path = m_ContextItem->data(0, Qt::UserRole).toString(); + runDataFileHooked(m_ContextItem); +} + +void MainWindow::runDataFileHooked(QTreeWidgetItem* item) +{ + const auto isArchive = item->data(0, Qt::UserRole + 1).toBool(); + const auto isDirectory = item->data(0, Qt::UserRole + 3).toBool(); + + if (isArchive || isDirectory) { + return; + } + + const QString path = item->data(0, Qt::UserRole).toString(); const QFileInfo targetInfo(path); m_OrganizerCore.processRunner() @@ -5431,6 +5429,21 @@ void MainWindow::runDataFileHooked() .run(); } +void MainWindow::previewDataFile() +{ + if (m_ContextItem == nullptr) { + return; + } + + previewDataFile(m_ContextItem); +} + +void MainWindow::previewDataFile(QTreeWidgetItem* item) +{ + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); + m_OrganizerCore.previewFileWithAlternatives(this, fileName); +} + void MainWindow::openDataOriginExplorer_clicked() { if (m_ContextItem == nullptr) { @@ -5511,21 +5524,57 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) const auto isDirectory = m_ContextItem->data(0, Qt::UserRole + 3).toBool(); QAction* open = nullptr; + QAction* runHooked = nullptr; QAction* preview = nullptr; if (canRunFile(isArchive, fileName)) { - open = menu.addAction(tr("&Execute"), this, SLOT(openDataFile())); + open = new QAction(tr("&Execute"), ui->dataTree); + runHooked = new QAction(tr("Execute with &VFS"), ui->dataTree); } else if (canOpenFile(isArchive, fileName)) { - open = menu.addAction(tr("&Open"), this, SLOT(openDataFile())); - menu.addAction(tr("Open with &VFS"), this, SLOT(runDataFileHooked())); + open = new QAction(tr("&Open"), ui->dataTree); + runHooked = new QAction(tr("Open with &VFS"), ui->dataTree); } - menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); - if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - preview = menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + preview = new QAction(tr("Preview"), ui->dataTree); + } + + if (open) { + connect(open, &QAction::triggered, [&]{ openDataFile(); }); + } + + if (runHooked) { + connect(runHooked, &QAction::triggered, [&]{ runDataFileHooked(); }); + } + + if (preview) { + connect(preview, &QAction::triggered, [&]{ previewDataFile(); }); + } + + if (open && preview) { + if (m_OrganizerCore.settings().interface().doubleClicksOpenPreviews()) { + menu.addAction(preview); + menu.addAction(open); + } else { + menu.addAction(open); + menu.addAction(preview); + } + } else { + if (open) { + menu.addAction(open); + } + + if (preview) { + menu.addAction(preview); + } } + if (runHooked) { + menu.addAction(runHooked); + } + + menu.addAction(tr("&Add as Executable"), this, SLOT(addAsExecutable())); + if (!isArchive && !isDirectory) { menu.addAction("Open Origin in Explorer", this, SLOT(openDataOriginExplorer_clicked())); } @@ -5543,7 +5592,13 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) } } - setDefaultActivationActionForFile(open, preview); + if (open || preview || runHooked) { + // bold the first option + auto* top = menu.actions()[0]; + auto f = top->font(); + f.setBold(true); + top->setFont(f); + } } menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); diff --git a/src/mainwindow.h b/src/mainwindow.h index 2020b1a3..c462a186 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -439,6 +439,7 @@ private slots: void openDataFile(); void openDataFile(QTreeWidgetItem* item); void runDataFileHooked(); + void runDataFileHooked(QTreeWidgetItem* item); void addAsExecutable(); void previewDataFile(); void previewDataFile(QTreeWidgetItem* item); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index faff25ac..9f83278b 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -294,42 +294,47 @@ p, li { white-space: pre-wrap; } ConflictsTab - + &Execute - + + Execute with &VFS + + + + &Open - + Open with &VFS - + &Preview - + &Go to... - + Open in &Explorer - + &Hide - + &Unhide @@ -1278,6 +1283,7 @@ Right now the only case I know of where this needs to be overwritten is for the + Open with &VFS @@ -1323,32 +1329,37 @@ Right now the only case I know of where this needs to be overwritten is for the - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 - + &Execute - + + Execute with &VFS + + + + &Open @@ -1851,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2027,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2313,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2434,8 +2445,8 @@ Error: %1 - - + + Endorse @@ -2540,665 +2551,665 @@ Error: %1 - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3206,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3219,330 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + + Execute with &VFS + + + + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6098,13 +6114,13 @@ If the folder was still in use, restart MO and try again. - - + + <Manage...> - + failed to parse profile %1: %2 @@ -6263,9 +6279,9 @@ If the folder was still in use, restart MO and try again. - - - + + + No profile set @@ -6940,12 +6956,14 @@ Select Show Details option to see the full change-log. - https://www.transifex.com/tannin/mod-organizer/ + https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/ + https://www.transifex.com/tannin/mod-organizer/ - <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> + <a href="https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/">Help translate Mod Organizer</a> + <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> -- cgit v1.3.1 From 2bc544509cb594df229055c7aac89151921f9861 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 10:07:11 -0500 Subject: disable run hooked in filetree when mod is disabled --- src/modinfodialogfiletree.cpp | 17 ++++++++++++++++- src/organizer_en.ts | 8 ++++---- 2 files changed, 20 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 607d2e5b..c49c767f 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -453,6 +453,21 @@ void FileTreeTab::onContextMenu(const QPoint &pos) } } + bool enableRunHooked = false; + + if (enableRun || enableOpen) { + if (auto* p=core().currentProfile()) { + if (mod().canBeEnabled()) { + const auto index = ModInfo::getIndex(mod().name()); + if (index == UINT_MAX) { + log::error("mod '{}' not found (filetree)", mod().name()); + } else { + enableRunHooked = p->modEnabled(index); + } + } + } + } + if (enableRun) { m_actions.open->setText(tr("&Execute")); m_actions.runHooked->setText(tr("Execute with &VFS")); @@ -481,7 +496,7 @@ void FileTreeTab::onContextMenu(const QPoint &pos) } } - if (enableOpen || enableRun) { + if (enableRunHooked) { menu.addAction(m_actions.runHooked); } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 9f83278b..a7796942 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1283,7 +1283,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + Open with &VFS @@ -1349,17 +1349,17 @@ Right now the only case I know of where this needs to be overwritten is for the - + &Execute - + Execute with &VFS - + &Open -- cgit v1.3.1 From 37891f29ca6f5935d4148bf5cf1f1d5a4eaa746c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 10:10:41 -0500 Subject: removed dead setDefaultActivationActionForFile() --- src/mainwindow.cpp | 50 ---- src/modinfodialogconflicts.cpp | 4 - src/modinfodialogfiletree.cpp | 3 - src/organizer_en.ts | 598 ++++++++++++++++++++--------------------- 4 files changed, 299 insertions(+), 356 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d3474838..1ea2a612 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -199,56 +199,6 @@ QString UnmanagedModName() bool runLoot(QWidget* parent, OrganizerCore& core, bool didUpdateMasterList); -void setDefaultActivationActionForFile(QAction* open, QAction* preview) -{ - if (!open && !preview) { - return; - } - - QFont bold, notBold; - - if (open) { - bold = open->font(); - notBold = open->font(); - } else { - bold = preview->font(); - notBold = preview->font(); - } - - notBold.setBold(false); - bold.setBold(true); - - - const auto& s = Settings::instance(); - const auto openEnabled = (open && open->isEnabled()); - const auto previewEnabled = (preview && preview->isEnabled()); - - bool doPreview = false; - - // preview is bold if the file is previewable and [the preview on double-click - // option is enabled or the file can't be opened]; open is bold if the file - // can be opened and cannot be previewed - if (previewEnabled && s.interface().doubleClicksOpenPreviews()) { - doPreview = true; - } else if (openEnabled) { - doPreview = false; - } else if (previewEnabled) { - doPreview = true; - } else { - // shouldn't happen, checked above - return; - } - - if (open) { - open->setFont(doPreview ? notBold : bold); - } - - if (preview) { - preview->setFont(doPreview ? bold : notBold); - } -} - - MainWindow::MainWindow(Settings &settings , OrganizerCore &organizerCore , PluginContainer &pluginContainer diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 81e8c7a3..0fb8c5a6 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -12,10 +12,6 @@ using namespace MOBase; // checking whether menu items apply to them, just show all of them const std::size_t max_small_selection = 50; -// in mainwindow.cpp -void setDefaultActivationActionForFile(QAction* open, QAction* preview); - - class ConflictItem { public: diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index c49c767f..2c33a7f1 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -14,9 +14,6 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; -// in mainwindow.cpp -void setDefaultActivationActionForFile(QAction* open, QAction* preview); - FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { diff --git a/src/organizer_en.ts b/src/organizer_en.ts index a7796942..31612360 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -178,17 +178,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -294,47 +294,47 @@ p, li { white-space: pre-wrap; } ConflictsTab - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Preview - + &Go to... - + Open in &Explorer - + &Hide - + &Unhide @@ -1272,94 +1272,94 @@ Right now the only case I know of where this needs to be overwritten is for the FileTreeTab - + &New Folder - + &Open/Execute - - + + Open with &VFS - + &Preview - + Open in &Explorer - + &Rename - + &Delete - + &Hide - + &Unhide - - + + New Folder - + Failed to create "%1" - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 - + &Execute - + Execute with &VFS - + &Open @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2038,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2324,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2402,814 +2402,814 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4554,7 +4554,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4770,12 +4770,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -4862,12 +4862,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -6109,18 +6109,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 -- cgit v1.3.1 From e1d6f6380dc4f7719f369b48e327085b0e0f511b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 11:40:54 -0500 Subject: use paintEvent() to allow interactive resize because showEvent() is too early bumped to rc2 --- src/mainwindow.cpp | 16 +- src/mainwindow.h | 2 + src/organizer_en.ts | 672 ++++++++++++++++++++++++++-------------------------- src/version.rc | 2 +- 4 files changed, 350 insertions(+), 342 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f58473e9..eaecfca1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -207,6 +207,7 @@ MainWindow::MainWindow(Settings &settings : QMainWindow(parent) , ui(new Ui::MainWindow) , m_WasVisible(false) + , m_FirstPaint(true) , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) @@ -687,15 +688,12 @@ void MainWindow::allowListResize() for (int i = 0; i < ui->modList->header()->count(); ++i) { ui->modList->header()->setSectionResizeMode(i, QHeaderView::Interactive); } - //ui->modList->header()->setSectionResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); ui->modList->header()->setStretchLastSection(true); - // allow resize on plugin list for (int i = 0; i < ui->espList->header()->count(); ++i) { ui->espList->header()->setSectionResizeMode(i, QHeaderView::Interactive); } - //ui->espList->header()->setSectionResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); ui->espList->header()->setStretchLastSection(true); } @@ -1286,14 +1284,22 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().widgets().restoreIndex(ui->groupCombo); - allowListResize(); - m_OrganizerCore.settings().nexus().registerAsNXMHandler(false); m_WasVisible = true; updateProblemsButton(); } } +void MainWindow::paintEvent(QPaintEvent* event) +{ + if (m_FirstPaint) { + allowListResize(); + m_FirstPaint = false; + } + + QMainWindow::paintEvent(event); +} + void MainWindow::onBeforeClose() { storeSettings(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 4adab809..8f000983 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -189,6 +189,7 @@ signals: protected: virtual void showEvent(QShowEvent *event); + virtual void paintEvent(QPaintEvent* event); virtual void closeEvent(QCloseEvent *event); virtual bool eventFilter(QObject *obj, QEvent *event); virtual void resizeEvent(QResizeEvent *event); @@ -312,6 +313,7 @@ private: Ui::MainWindow *ui; bool m_WasVisible; + bool m_FirstPaint; // last separator on the toolbar, used to add spacer for right-alignment and // as an insert point for executables diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 31612360..e9578369 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1886,7 +1886,7 @@ p, li { white-space: pre-wrap; } - + Filter @@ -2037,9 +2037,9 @@ p, li { white-space: pre-wrap; } - - - + + + Refresh @@ -2049,44 +2049,44 @@ p, li { white-space: pre-wrap; } - + File - + Mod - + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -2097,337 +2097,337 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - + Main ToolBar - + &File - - + + &Tools - - - + + + &Help - + &View - + &Toolbars - + &Run - - + + Log - + Install &Mod... - + Install &Mod - - + + Install a new mod from an archive - + Ctrl+M - + &Profiles... - + &Profiles - - + + Configure profiles - + Ctrl+P - + &Executables... - + &Executables - - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + &Tool Plugins - + Tools - + Ctrl+I - + &Settings... - + &Settings - - + + Configure settings and workarounds - + Ctrl+S - - + + Visit &Nexus - - + + Visit the Nexus website in your browser for more mods - + Ctrl+N - - + + &Update Mod Organizer - - + + Mod Organizer is up-to-date - + &Notifications... - - + + Open the notifications dialog - + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - + + Show help options - + Ctrl+H - - + + &Endorse ModOrganizer - - - + + + Endorse Mod Organizer - + &Change Game... - + &Change Game - - + + Open the Instance selection dialog to manage a different Game - - + + E&xit - - + + Exits Mod Organizer - + M&ain Toolbar - + &Small Icons - + Lar&ge Icons - + &Icons Only - + &Text Only - + I&cons and Text - + M&edium Icons - + &Menu - + Status &bar - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 @@ -2445,8 +2445,8 @@ Error: %1 - - + + Endorse @@ -2516,700 +2516,700 @@ Error: %1 - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6088,23 +6088,23 @@ If the folder was still in use, restart MO and try again. - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + <Unmanaged> @@ -6114,13 +6114,13 @@ If the folder was still in use, restart MO and try again. - - + + <Manage...> - + failed to parse profile %1: %2 diff --git a/src/version.rc b/src/version.rc index 3d431488..1544451e 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc1\0" +#define VER_FILEVERSION_STR "2.2.2rc2\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From ea6c793ce6047e0c958c13b3743976bcf953bf1a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 12:32:17 -0500 Subject: move the conflicts column when resetting geometries --- src/mainwindow.cpp | 45 +++-- src/mainwindow.h | 1 + src/organizer_en.ts | 516 ++++++++++++++++++++++++++-------------------------- 3 files changed, 281 insertions(+), 281 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index eaecfca1..44011e5f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -528,6 +528,8 @@ void MainWindow::setupModList() ui->modList->header()->resizeSection(column, sectionSize); } } else { + fixConflictsColumn(); + // hide these columns by default ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); @@ -549,6 +551,25 @@ void MainWindow::setupModList() ui->modList->installEventFilter(m_OrganizerCore.modList()); } +void MainWindow::fixConflictsColumn() +{ + // the conflicts column should sit to the left of the flags column, but its + // enum is at the end to preserve compatibility + // + // this is called when updating from 2.2.1, or when there is no state saved + // for the mod list, so it's free to do whatever it wants with the column + + const auto flags = ui->modList->header()->visualIndex(ModList::COL_FLAGS); + const auto conflicts = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); + + // this can be called twice when migrating from 2.2.1: once in + // processUpdates() and again in setupModList() because the geometry names in + // the ini have changed; to a simple check to see if the column has been moved + if (conflicts > flags) { + ui->modList->header()->moveSection(conflicts, flags); + } +} + void MainWindow::resetActionIcons() { // this is a bit of a hack @@ -2258,29 +2279,7 @@ void MainWindow::processUpdates() { } if (lastVersion < QVersionNumber(2, 2, 2)) { - int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); - int pos2 = 0; - if (pos1) { - ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); - pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); - ui->modList->header()->moveSection( - ui->modList->header()->visualIndexAt(pos2), - ui->modList->header()->visualIndexAt(pos1) - ); - } - } - } else { - { // Move conflict flags - int pos1 = ui->modList->columnViewportPosition(ModList::COL_FLAGS); - int pos2 = 0; - if (pos1) { - ui->modList->showColumn(ModList::COL_CONFLICTFLAGS); - pos2 = ui->modList->columnViewportPosition(ModList::COL_CONFLICTFLAGS); - ui->modList->header()->moveSection( - ui->modList->header()->visualIndexAt(pos2), - ui->modList->header()->visualIndexAt(pos1) - ); - } + fixConflictsColumn(); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 8f000983..0d7edefd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -665,6 +665,7 @@ private slots: // ui slots void storeSettings(); void readSettings(); void setupModList(); + void fixConflictsColumn(); }; #endif // MAINWINDOW_H diff --git a/src/organizer_en.ts b/src/organizer_en.ts index e9578369..73e9f547 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2038,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2324,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2422,794 +2422,794 @@ p, li { white-space: pre-wrap; } - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6109,18 +6109,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 -- cgit v1.3.1 From 8d9a62a87dafd87381829b00b85753db510eef2b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 13:08:58 -0500 Subject: moved the conflicts column back to its proper position in the enum removed unnecessary compat code --- src/mainwindow.cpp | 26 --- src/mainwindow.h | 1 - src/modlist.h | 4 +- src/organizer_en.ts | 526 ++++++++++++++++++++++++++-------------------------- 4 files changed, 265 insertions(+), 292 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 44011e5f..b5900c6c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -178,7 +178,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #ifdef TEST_MODELS #include "modeltest.h" @@ -528,8 +527,6 @@ void MainWindow::setupModList() ui->modList->header()->resizeSection(column, sectionSize); } } else { - fixConflictsColumn(); - // hide these columns by default ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); @@ -551,25 +548,6 @@ void MainWindow::setupModList() ui->modList->installEventFilter(m_OrganizerCore.modList()); } -void MainWindow::fixConflictsColumn() -{ - // the conflicts column should sit to the left of the flags column, but its - // enum is at the end to preserve compatibility - // - // this is called when updating from 2.2.1, or when there is no state saved - // for the mod list, so it's free to do whatever it wants with the column - - const auto flags = ui->modList->header()->visualIndex(ModList::COL_FLAGS); - const auto conflicts = ui->modList->header()->visualIndex(ModList::COL_CONFLICTFLAGS); - - // this can be called twice when migrating from 2.2.1: once in - // processUpdates() and again in setupModList() because the geometry names in - // the ini have changed; to a simple check to see if the column has been moved - if (conflicts > flags) { - ui->modList->header()->moveSection(conflicts, flags); - } -} - void MainWindow::resetActionIcons() { // this is a bit of a hack @@ -2277,10 +2255,6 @@ void MainWindow::processUpdates() { ui->downloadView->header()->hideSection(i); } } - - if (lastVersion < QVersionNumber(2, 2, 2)) { - fixConflictsColumn(); - } } if (currentVersion < lastVersion) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 0d7edefd..8f000983 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -665,7 +665,6 @@ private slots: // ui slots void storeSettings(); void readSettings(); void setupModList(); - void fixConflictsColumn(); }; #endif // MAINWINDOW_H diff --git a/src/modlist.h b/src/modlist.h index ce439f7b..3626c50c 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -57,6 +57,7 @@ public: enum EColumn { COL_NAME, + COL_CONFLICTFLAGS, COL_FLAGS, COL_CONTENT, COL_CATEGORY, @@ -66,8 +67,7 @@ public: COL_INSTALLTIME, COL_PRIORITY, COL_NOTES, - COL_CONFLICTFLAGS, - COL_LASTCOLUMN = COL_CONFLICTFLAGS, + COL_LASTCOLUMN = COL_NOTES, }; typedef boost::signals2::signal SignalModStateChanged; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 73e9f547..d462f1b6 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2038,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2324,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2402,814 +2402,814 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6104,23 +6104,23 @@ If the folder was still in use, restart MO and try again. - + <Unmanaged> - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 -- cgit v1.3.1 From 930087ea92291bba1bc55f606aa5e1efcb622a75 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 9 Dec 2019 13:25:04 -0500 Subject: set default window size to 1300x800 --- src/mainwindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5900c6c..ea202064 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2201,7 +2201,10 @@ void MainWindow::readSettings() { const auto& s = m_OrganizerCore.settings(); - s.geometry().restoreGeometry(this); + if (!s.geometry().restoreGeometry(this)) { + resize(1300, 800); + } + s.geometry().restoreState(this); s.geometry().restoreDocks(this); s.geometry().restoreToolbars(this); -- cgit v1.3.1 From 923e514b0f01153a7938f88dfe6206075581e5ea Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 10 Dec 2019 14:56:56 -0600 Subject: Change the modinfo image preview button to a toolbutton --- src/modinfodialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 59263e1c..d7b03d70 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -268,7 +268,7 @@ 0 - + Open with Preview Plugin -- cgit v1.3.1 From 2001f2675782ffe8f987daaed099ce3ee3a57a1c Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 10 Dec 2019 15:25:31 -0600 Subject: Rework filter labels to separate bracket from translation --- src/categories.cpp | 22 +-- src/filterlist.cpp | 3 +- src/organizer_en.ts | 486 ++++++++++++++++++++++++++-------------------------- 3 files changed, 257 insertions(+), 254 deletions(-) (limited to 'src') diff --git a/src/categories.cpp b/src/categories.cpp index 3e005079..bb37483b 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -322,20 +322,22 @@ QString CategoryFactory::getCategoryName(unsigned int index) const QString CategoryFactory::getSpecialCategoryName(SpecialCategories type) const { + QString label; switch (type) { - case Checked: return QObject::tr(""); - case UpdateAvailable: return QObject::tr(""); - case HasCategory: return QObject::tr(""); - case Conflict: return QObject::tr(""); - case Endorsed: return QObject::tr(""); - case Backup: return QObject::tr(""); - case Managed: return QObject::tr(""); - case HasGameData: return QObject::tr(""); - case HasNexusID: return QObject::tr(""); - case Tracked: return QObject::tr(""); + case Checked: label = QObject::tr("Active"); break; + case UpdateAvailable: label = QObject::tr("Update available"); break; + case HasCategory: label = QObject::tr("Has category"); break; + case Conflict: label = QObject::tr("Conflicted"); break; + case Endorsed: label = QObject::tr("Endorsed"); break; + case Backup: label = QObject::tr("Has backup"); break; + case Managed: label = QObject::tr("Managed"); break; + case HasGameData: label = QObject::tr("Has valid game data"); break; + case HasNexusID: label = QObject::tr("Has Nexus ID"); break; + case Tracked: label = QObject::tr("Tracked on Nexus"); break; default: return {}; } + return QString("<%1>").arg(label); } QString CategoryFactory::getCategoryNameByID(int id) const diff --git a/src/filterlist.cpp b/src/filterlist.cpp index b345d21d..6a85bcaa 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -239,8 +239,9 @@ QTreeWidgetItem* FilterList::addCriteriaItem( void FilterList::addContentCriteria() { for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { + QString filterName = tr("Contains %1").arg(ModInfo::getContentTypeName(i)); addCriteriaItem( - nullptr, tr("").arg(ModInfo::getContentTypeName(i)), + nullptr, QString("<%1>").arg(filterName), i, ModListSortProxy::TypeContent); } } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index d462f1b6..ca82a996 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1387,8 +1387,8 @@ Right now the only case I know of where this needs to be overwritten is for the - - <Contains %1> + + Contains %1 @@ -1862,7 +1862,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2038,8 +2038,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2324,7 +2324,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2445,8 +2445,8 @@ Error: %1 - - + + Endorse @@ -2571,645 +2571,645 @@ Error: %1 - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3217,12 +3217,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3230,335 +3230,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -5611,62 +5611,62 @@ p, li { white-space: pre-wrap; } - + invalid category index: %1 - - - <Active> - - - <Update available> + Active - <Has category> + Update available - <Conflicted> + Has category - <Endorsed> + Conflicted - <Has backup> + Endorsed - <Managed> + Has backup - <Has valid game data> + Managed - <Has Nexus ID> + Has valid game data - <Tracked on Nexus> + Has Nexus ID + + + + + Tracked on Nexus - + invalid category id: %1 @@ -6115,7 +6115,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> -- cgit v1.3.1 From 5a266b8e268163f3f25063aa823dd1fc72ee7c56 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 11 Dec 2019 12:06:36 -0600 Subject: When cancelling a fomod extraction, don't generate an error. --- src/installationmanager.cpp | 13 +++++++++++-- src/organizer_en.ts | 39 ++++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index dd5cfb55..8ea2bac7 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -307,7 +307,16 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool QCoreApplication::processEvents(); } while (!future.isFinished() || m_InstallationProgress->isVisible()); if (!future.result()) { - throw MyException(tr("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); + if (m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { + if (!m_ErrorMessage.isEmpty()) { + throw MyException(tr("Extraction failed: %1").arg(m_ErrorMessage)); + } else { + return QStringList(); + } + } + else { + throw MyException(tr("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); + } } return result; @@ -839,7 +848,7 @@ bool InstallationManager::install(const QString &fileName, } } - { // custom case + if (installResult != IPluginInstaller::RESULT_CANCELED) { // custom case IPluginInstallerCustom *installerCustom = dynamic_cast(installer); if ((installerCustom != nullptr) diff --git a/src/organizer_en.ts b/src/organizer_en.ts index ca82a996..d8995973 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1527,90 +1527,91 @@ Right now the only case I know of where this needs to be overwritten is for the - - - + + + + Extraction failed: %1 - + Failed to create backup - + Mod Name - + Name - + Invalid name - + The name you entered is invalid, please enter a different one. - + File format "%1" not supported - + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - + unknown archive error -- cgit v1.3.1 From 19bb86a870a88b65cce0eca2b6b71348d2769f51 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 11 Dec 2019 14:02:33 -0600 Subject: Update installer status handling --- src/installationmanager.cpp | 52 +++++++++---------- src/installationmanager.h | 4 +- src/organizer_en.ts | 123 +++++++++++++++++++++++++------------------- src/organizercore.cpp | 16 ++++-- 4 files changed, 107 insertions(+), 88 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 8ea2bac7..1bfc846e 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -328,14 +328,9 @@ IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValu // a problem if this is called by the bundle installer and the bundled installer adds additional names that then end up being used, // because the caller will then not have the right name. bool iniTweaks; - if (install(archiveName, modName, iniTweaks, modId)) { - return IPluginInstaller::RESULT_SUCCESS; - } else { - return IPluginInstaller::RESULT_FAILED; - } + return install(archiveName, modName, iniTweaks, modId); } - DirectoryTree *InstallationManager::createFilesTree() { FileData* const *data; @@ -573,18 +568,18 @@ bool InstallationManager::ensureValidModName(GuessedValue &name) const return true; } -bool InstallationManager::doInstall(GuessedValue &modName, QString gameName, int modID, +IPluginInstaller::EInstallResult InstallationManager::doInstall(GuessedValue &modName, QString gameName, int modID, const QString &version, const QString &newestVersion, int categoryID, int fileCategoryID, const QString &repository) { if (!ensureValidModName(modName)) { - return false; + return IPluginInstaller::RESULT_FAILED; } bool merge = false; // determine target directory if (!testOverwrite(modName, &merge)) { - return false; + return IPluginInstaller::RESULT_FAILED; } QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); @@ -627,7 +622,7 @@ bool InstallationManager::doInstall(GuessedValue &modName, QString game if (!m_ErrorMessage.isEmpty()) { throw MyException(tr("Extraction failed: %1").arg(m_ErrorMessage)); } else { - return false; + return IPluginInstaller::RESULT_CANCELED; } } else { throw MyException(tr("Extraction failed: %1").arg(m_ArchiveHandler->getLastError())); @@ -671,7 +666,7 @@ bool InstallationManager::doInstall(GuessedValue &modName, QString game settingsFile.endGroup(); } - return true; + return IPluginInstaller::RESULT_SUCCESS; } @@ -721,7 +716,7 @@ void InstallationManager::postInstallCleanup() } } -bool InstallationManager::install(const QString &fileName, +IPluginInstaller::EInstallResult InstallationManager::install(const QString &fileName, GuessedValue &modName, bool &hasIniTweaks, int modID) @@ -732,7 +727,7 @@ bool InstallationManager::install(const QString &fileName, QFileInfo fileInfo(fileName); if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) { reportError(tr("File format \"%1\" not supported").arg(fileInfo.suffix())); - return false; + return IPluginInstaller::RESULT_FAILED; } modName.setFilter(&fixDirectoryName); @@ -840,10 +835,7 @@ bool InstallationManager::install(const QString &fileName, mapToArchive(filesTree.data()); // the simple installer only prepares the installation, the rest // works the same for all installers - if (!doInstall(modName, gameName, modID, version, newestVersion, categoryID, - fileCategoryID, repository)) { - installResult = IPluginInstaller::RESULT_FAILED; - } + installResult = doInstall(modName, gameName, modID, version, newestVersion, categoryID, fileCategoryID, repository); } } } @@ -876,28 +868,34 @@ bool InstallationManager::install(const QString &fileName, // act upon the installation result. at this point the files have already been // extracted to the correct location switch (installResult) { - case IPluginInstaller::RESULT_CANCELED: case IPluginInstaller::RESULT_FAILED: { - return false; + QMessageBox::information(qApp->activeWindow(), tr("Installation failed"), + tr("Something went wrong while installing this mod."), + QMessageBox::Ok); + return installResult; } break; case IPluginInstaller::RESULT_SUCCESS: case IPluginInstaller::RESULT_SUCCESSCANCEL: { if (filesTree != nullptr) { DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks")); hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - return true; - } else { - return false; + ((*iniTweakNode)->numLeafs() != 0); } + return installResult; } break; + case IPluginInstaller::RESULT_NOTATTEMPTED: { + continue; + } + default: + return installResult; } } + if (installResult == IPluginInstaller::RESULT_NOTATTEMPTED) { + reportError(tr("None of the available installer plugins were able to handle that archive.\n" + "This is likely due to a corrupted or incompatible download or unrecognized archive format.")); + } - reportError(tr("None of the available installer plugins were able to handle that archive.\n" - "This is likely due to a corrupted or incompatible download or unrecognized archive format.")); - - return false; + return installResult; } diff --git a/src/installationmanager.h b/src/installationmanager.h index 6e4f5925..199a0f82 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -82,7 +82,7 @@ public: * @return true if the archive was installed, false if installation failed or was refused * @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged) **/ - bool install(const QString &fileName, MOBase::GuessedValue &modName, bool &hasIniTweaks, int modID = 0); + MOBase::IPluginInstaller::EInstallResult install(const QString &fileName, MOBase::GuessedValue &modName, bool &hasIniTweaks, int modID = 0); /** * @return true if the installation was canceled @@ -175,7 +175,7 @@ private: bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node *node, bool bainStyle); MOBase::DirectoryTree::Node *getSimpleArchiveBase(MOBase::DirectoryTree *dataTree); - bool doInstall(MOBase::GuessedValue &modName, QString gameName, + MOBase::IPluginInstaller::EInstallResult doInstall(MOBase::GuessedValue &modName, QString gameName, int modID, const QString &version, const QString &newestVersion, int categoryID, int fileCategoryID, const QString &repository); //QString generateBackupName(const QString &directoryName) const; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index d8995973..95854200 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1529,89 +1529,100 @@ Right now the only case I know of where this needs to be overwritten is for the + - Extraction failed: %1 - + Failed to create backup - + Mod Name - + Name - + Invalid name - + The name you entered is invalid, please enter a different one. - + File format "%1" not supported - + + Installation failed + + + + + Something went wrong while installing this mod. + The mod was not installed completely. + + + + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - + unknown archive error @@ -4600,170 +4611,174 @@ p, li { white-space: pre-wrap; } - - - + Installation cancelled - + Another installation is currently in progress. - + Installation successful - + Configure Mod - + This mod contains ini tweaks. Do you want to configure them now? - + mod not found: %1 + + + + Extraction cancelled + + - - The mod was not installed completely. + + The installation was cancelled while extracting files. If this was prior to a FOMOD setup, this warning may be ignored. However, if this was during installation, the mod will likely be missing files. - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + Error - + The designated write target "%1" is not enabled. diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 55cb82ff..4ba38dbb 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -735,9 +735,12 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, reportError(tr("mod not found: %1").arg(qUtf8Printable(modName))); } } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"), - tr("The mod was not installed completely."), + QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"), + tr("The installation was cancelled while extracting files. " + "If this was prior to a FOMOD setup, this warning may be ignored. " + "However, if this was during installation, the mod will likely be missing files."), QMessageBox::Ok); + refreshModList(); } return nullptr; } @@ -803,9 +806,12 @@ void OrganizerCore::installDownload(int index) emit modInstalled(modName); } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information( - qApp->activeWindow(), tr("Installation cancelled"), - tr("The mod was not installed completely."), QMessageBox::Ok); + QMessageBox::information(qApp->activeWindow(), tr("Extraction cancelled"), + tr("The installation was cancelled while extracting files. " + "If this was prior to a FOMOD setup, this warning may be ignored. " + "However, if this was during installation, the mod will likely be missing files."), + QMessageBox::Ok); + refreshModList(); } } catch (const std::exception &e) { reportError(e.what()); -- cgit v1.3.1 From 1d0b202ee64fe12d23922e816812749e8fe40bf6 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 11 Dec 2019 14:28:42 -0600 Subject: Check the enum --- src/installationmanager.cpp | 2 +- src/organizercore.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 1bfc846e..a759913e 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -881,7 +881,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) && ((*iniTweakNode)->numLeafs() != 0); } - return installResult; + return IPluginInstaller::RESULT_SUCCESS; } break; case IPluginInstaller::RESULT_NOTATTEMPTED: { continue; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 4ba38dbb..df505202 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -710,7 +710,7 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, } m_CurrentProfile->writeModlistNow(); m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + if (m_InstallationManager.install(fileName, modName, hasIniTweaks) == IPluginInstaller::RESULT_SUCCESS) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); refreshModList(); @@ -778,7 +778,7 @@ void OrganizerCore::installDownload(int index) bool hasIniTweaks = false; m_InstallationManager.setModsDirectory(m_Settings.paths().mods()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + if (m_InstallationManager.install(fileName, modName, hasIniTweaks) == IPluginInstaller::RESULT_SUCCESS) { MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); refreshModList(); -- cgit v1.3.1 From c3c1183308dbe00a14b8cf5e3c58e92bd9edfaf6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Dec 2019 14:47:35 -0500 Subject: fixed alt colors in saves list for dark.qss translated some sanity checks warnings fixed filter list not refreshing selection correctly --- src/filterlist.cpp | 38 +++++++++++++++++++------------------- src/filterlist.h | 1 + src/organizer_en.ts | 17 +++++++++++++++++ src/sanitychecks.cpp | 21 ++++++++++++++------- src/stylesheets/dark.qss | 2 +- 5 files changed, 52 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/filterlist.cpp b/src/filterlist.cpp index 6a85bcaa..69aca4c5 100644 --- a/src/filterlist.cpp +++ b/src/filterlist.cpp @@ -275,10 +275,7 @@ void FilterList::addSpecialCriteria(int type) void FilterList::refresh() { - QStringList selectedItems; - for (QTreeWidgetItem *item : ui->filters->selectedItems()) { - selectedItems.append(item->text(0)); - } + const auto oldSelection = selectedCriteria(); ui->filters->clear(); @@ -315,33 +312,30 @@ void FilterList::refresh() } addCategoryCriteria(nullptr, categoriesUsed, 0); - - for (const QString &item : selectedItems) { - QList matches = ui->filters->findItems( - item, Qt::MatchFixedString | Qt::MatchRecursive); - - if (matches.size() > 0) { - matches.at(0)->setSelected(true); - } - } + setSelection(oldSelection); } void FilterList::setSelection(const std::vector& criteria) { for (int i = 0; i < ui->filters->topLevelItemCount(); ++i) { - const auto* item = dynamic_cast( - ui->filters->topLevelItem(i)); - + auto* item = dynamic_cast(ui->filters->topLevelItem(i)); if (!item) { continue; } + bool found = false; + for (auto&& c : criteria) { if (item->type() == c.type && item->id() == c.id) { - ui->filters->setCurrentItem(ui->filters->topLevelItem(i)); + item->setState(c.inverse ? CriteriaItem::Inverted : CriteriaItem::Active); + found = true; break; } } + + if (!found) { + item->setState(CriteriaItem::Inactive); + } } } @@ -378,7 +372,7 @@ bool FilterList::cycleItem(QTreeWidgetItem* item, int direction) return true; } -void FilterList::checkCriteria() +std::vector FilterList::selectedCriteria() const { std::vector criteria; @@ -395,7 +389,12 @@ void FilterList::checkCriteria() } } - emit criteriaChanged(criteria); + return criteria; +} + +void FilterList::checkCriteria() +{ + emit criteriaChanged(selectedCriteria()); } void FilterList::editCategories() @@ -404,6 +403,7 @@ void FilterList::editCategories() if (dialog.exec() == QDialog::Accepted) { dialog.commitChanges(); + refresh(); } } diff --git a/src/filterlist.h b/src/filterlist.h index 72cbe8bf..b0ebc9a4 100644 --- a/src/filterlist.h +++ b/src/filterlist.h @@ -39,6 +39,7 @@ private: void editCategories(); void checkCriteria(); + std::vector selectedCriteria() const; bool cycleItem(QTreeWidgetItem* item, int direction); QTreeWidgetItem* addCriteriaItem( diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 95854200..6101c8e7 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -6754,6 +6754,23 @@ You can restart Mod Organizer as administrator and try launching the program aga Exit Now + + + '%1': file is blocked (%2) + '%1': file is blocked ('%2') + + + + + '%1' seems to be missing, an antivirus may have deleted it + + + + + %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. + %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. (%2) + + QueryOverwriteDialog diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index 330735ed..bdf762d9 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -110,7 +110,11 @@ bool isFileBlocked(const QFileInfo& fi) } // file is blocked - log::warn("'{}': file is blocked, zone id is {}", path, toString(z)); + log::warn("{}", QObject::tr( + "'%1': file is blocked (%2)") + .arg(path) + .arg(toString(z))); + return true; } @@ -199,9 +203,9 @@ int checkMissingFiles() const QFileInfo file(dir + "/" + name); if (!file.exists()) { - log::warn( - "'{}' seems to be missing, an antivirus may have deleted it", - file.absoluteFilePath()); + log::warn("{}", QObject::tr( + "'%1' seems to be missing, an antivirus may have deleted it") + .arg(file.absoluteFilePath())); ++n; } @@ -231,10 +235,13 @@ int checkIncompatibleModule(const env::Module& m) for (auto&& p : names) { if (file.fileName().compare(p.first, Qt::CaseInsensitive) == 0) { - log::warn( - "{} is loaded. This program is known to cause issues with " + log::warn("{}", QObject::tr( + "%1 is loaded. This program is known to cause issues with " "Mod Organizer, such as freezing or blank windows. Consider " - "uninstalling it. ({})", p.second, file.absoluteFilePath()); + "uninstalling it.") + .arg(p.second)); + + log::warn("{}", file.absoluteFilePath()); ++n; } diff --git a/src/stylesheets/dark.qss b/src/stylesheets/dark.qss index 9d11109d..91f808bc 100644 --- a/src/stylesheets/dark.qss +++ b/src/stylesheets/dark.qss @@ -349,7 +349,7 @@ QToolButton:hover padding-bottom: 2px; } -QTreeView +QTreeView, QListView { color: #E9E6E4; background-color: #3F4041; -- cgit v1.3.1 From 26158687a8a6d3f9eef38a8004f242cf8046268c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Dec 2019 14:51:42 -0500 Subject: added SS3DevProps.dll to checks --- src/organizer_en.ts | 2 +- src/sanitychecks.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 6101c8e7..593e4457 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -6766,7 +6766,7 @@ You can restart Mod Organizer as administrator and try launching the program aga - + %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. (%2) diff --git a/src/sanitychecks.cpp b/src/sanitychecks.cpp index bdf762d9..6da42e79 100644 --- a/src/sanitychecks.cpp +++ b/src/sanitychecks.cpp @@ -227,7 +227,8 @@ int checkIncompatibleModule(const env::Module& m) static const std::map names = { {"NahimicOSD.dll", "Nahimic"}, {"RTSSHooks64.dll", "RivaTuner Statistics Server"}, - {"SSAudioOSD.dll", "SteelSeries Audio"} + {"SSAudioOSD.dll", "SteelSeries Audio"}, + {"SS3DevProps.dll", "Sonic Suite 3"} }; const QFileInfo file(m.path()); -- cgit v1.3.1 From 0f67ee4fcd8a2735751eaed738244327e5571239 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 12 Dec 2019 15:07:02 -0500 Subject: bumped to rc3 --- src/version.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 1544451e..4220f50f 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc2\0" +#define VER_FILEVERSION_STR "2.2.2rc3\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 909e8254f7fe01121e32817337091c491abe2cf9 Mon Sep 17 00:00:00 2001 From: Al Date: Fri, 13 Dec 2019 13:36:12 +0100 Subject: Avoid opening folders in explorer when double-clicking on them in the filetree. As a side effect Enter and Return no longer do anything on folders either. --- src/modinfodialogfiletree.cpp | 5 +++++ src/organizer_en.ts | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 2c33a7f1..cc0e6493 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -148,6 +148,11 @@ void FileTreeTab::onActivated() return; } + // Don't open explorer on directories as we just want them to be expanded instead. + if (m_fs->isDir(selection)) { + return; + } + const auto path = m_fs->filePath(selection); const auto tryPreview = core().settings().interface().doubleClicksOpenPreviews(); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 593e4457..633073a0 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1283,7 +1283,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + Open with &VFS @@ -1329,37 +1329,37 @@ Right now the only case I know of where this needs to be overwritten is for the - + Are you sure you want to delete "%1"? - + Are you sure you want to delete the selected files? - + Confirm - + Failed to delete %1 - + &Execute - + Execute with &VFS - + &Open -- cgit v1.3.1 From db59ebdba062de05292b648a5da48e6c4630d680 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sat, 14 Dec 2019 02:23:40 -0600 Subject: Fix tutorial targets --- src/settingsdialog.ui | 2 +- src/tutorials/tutorial_primer_main.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index b836e321..2ceb91db 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -722,7 +722,7 @@ - + Nexus Connection diff --git a/src/tutorials/tutorial_primer_main.js b/src/tutorials/tutorial_primer_main.js index 7972cca4..95faa33d 100644 --- a/src/tutorials/tutorial_primer_main.js +++ b/src/tutorials/tutorial_primer_main.js @@ -80,7 +80,7 @@ function setupTooptips() { tooltipWidget("displayCategoriesBtn", qsTr("Show/hide the category pane.")) tooltipWidget("modFilterEdit", qsTr("Quickly filter the mod list as you type.")) tooltipWidget("qt_tabwidget_tabbar", qsTr("Switch between information views."), 0, true) - tooltipWidget("categoriesList", qsTr("This shows mod categories and some meta categories (in angle-brackets). Select some to filter the mod list. For example select \"\" to show only active mods.")) + tooltipWidget("categoriesGroup", qsTr("This shows mod categories and some meta categories (in angle-brackets). Select some to filter the mod list. For example select \"\" to show only active mods.")) tooltipWidget("executablesListBox", qsTr("Customizable list for choosing the program to run.")) tooltipWidget("startButton", qsTr("When this button is clicked, Mod Organizer creates a virtual directory structure then runs the program selected to the left.")) tooltipWidget("linkButton", qsTr("Will create a shortcut for quick access. The shortcut can be placed in the toolbar at the top, in the Start Menu or on the Windows Desktop.")) -- cgit v1.3.1 From 4c7b626e7748cae0f7c1933989bc1bcb755664f7 Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sat, 14 Dec 2019 06:52:34 -0700 Subject: Fix requesting a manual install --- src/installationmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index a759913e..72397de7 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -886,6 +886,9 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil case IPluginInstaller::RESULT_NOTATTEMPTED: { continue; } + case IPluginInstaller::RESULT_MANUALREQUESTED: { + continue; + } default: return installResult; } -- cgit v1.3.1 From 7af0e164f778ee8b2135e097856a597f5d68b873 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 14 Dec 2019 13:49:26 -0500 Subject: don't activate the root data item --- src/mainwindow.cpp | 4 ++ src/organizer_en.ts | 134 ++++++++++++++++++++++++++-------------------------- 2 files changed, 71 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ea202064..a29ea8ab 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5305,6 +5305,10 @@ void MainWindow::activateDataTreeItem(QTreeWidgetItem *item, int column) } const QString path = item->data(0, Qt::UserRole).toString(); + if (path.isEmpty()) { + return; + } + const QFileInfo targetInfo(path); const auto tryPreview = m_OrganizerCore.settings().interface().doubleClicksOpenPreviews(); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 633073a0..00bb771c 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1576,53 +1576,53 @@ Right now the only case I know of where this needs to be overwritten is for the - + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - + unknown archive error @@ -2051,7 +2051,7 @@ p, li { white-space: pre-wrap; } - + Refresh @@ -2336,7 +2336,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2782,7 +2782,7 @@ Please enter a name: - + Are you sure? @@ -3123,13 +3123,13 @@ You can also use online editors and converters instead. - + Enable selected - + Disable selected @@ -3205,13 +3205,13 @@ You can also use online editors and converters instead. - + Exception: - + Unknown exception @@ -3331,7 +3331,7 @@ You can also use online editors and converters instead. - + Set Priority @@ -3341,236 +3341,236 @@ You can also use online editors and converters instead. - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods -- cgit v1.3.1 From fde5fa279746c90180223942a44f865859f85cb3 Mon Sep 17 00:00:00 2001 From: Silarn Date: Sat, 14 Dec 2019 14:12:00 -0600 Subject: Streamline manual install fix, bump RC --- src/installationmanager.cpp | 4 +--- src/organizer_en.ts | 20 ++++++++++---------- src/version.rc | 2 +- 3 files changed, 12 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 72397de7..c3aeec88 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -883,9 +883,7 @@ IPluginInstaller::EInstallResult InstallationManager::install(const QString &fil } return IPluginInstaller::RESULT_SUCCESS; } break; - case IPluginInstaller::RESULT_NOTATTEMPTED: { - continue; - } + case IPluginInstaller::RESULT_NOTATTEMPTED: case IPluginInstaller::RESULT_MANUALREQUESTED: { continue; } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 00bb771c..271599e4 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1576,53 +1576,53 @@ Right now the only case I know of where this needs to be overwritten is for the - + None of the available installer plugins were able to handle that archive. This is likely due to a corrupted or incompatible download or unrecognized archive format. - + no error - + 7z.dll not found - + 7z.dll isn't valid - + archive not found - + failed to open archive - + unsupported archive type - + internal library error - + archive invalid - + unknown archive error diff --git a/src/version.rc b/src/version.rc index 4220f50f..913d1ead 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc3\0" +#define VER_FILEVERSION_STR "2.2.2rc4\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 51e3e078be10a085702014b4b873d69c502e8b0a Mon Sep 17 00:00:00 2001 From: Silarn Date: Sat, 14 Dec 2019 18:11:57 -0600 Subject: Fix problem with translated unmanaged mods and origin names - (Also adds translatable strings to directoryentry.cpp) --- src/lootdialog.h | 1 + src/modinfoforeign.cpp | 8 +++---- src/modinfoforeign.h | 5 ++-- src/organizer_en.ts | 53 +++++++++++++++++++++++++++++++++++++++---- src/shared/directoryentry.cpp | 19 ++++++++-------- 5 files changed, 66 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/lootdialog.h b/src/lootdialog.h index bc8c01fb..3cec15c6 100644 --- a/src/lootdialog.h +++ b/src/lootdialog.h @@ -42,6 +42,7 @@ protected: class LootDialog : public QDialog { + Q_OBJECT; public: LootDialog(QWidget* parent, OrganizerCore& core, Loot& loot); ~LootDialog(); diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp index 7312d5b7..84199eae 100644 --- a/src/modinfoforeign.cpp +++ b/src/modinfoforeign.cpp @@ -8,11 +8,6 @@ using namespace MOBase; using namespace MOShared; -QString ModInfoForeign::name() const -{ - return m_Name; -} - QDateTime ModInfoForeign::creationTime() const { return m_CreationTime; @@ -59,11 +54,14 @@ ModInfoForeign::ModInfoForeign(const QString &modName, switch (modType) { case ModInfo::EModType::MOD_DLC: m_Name = tr("DLC: ") + modName; + m_InternalName = QString("DLC: ") + modName; break; case ModInfo::EModType::MOD_CC: m_Name = tr("Creation Club: ") + modName; + m_InternalName = QString("Creation Club: ") + modName; break; default: m_Name = tr("Unmanaged: ") + modName; + m_InternalName = QString("Unmanaged: ") + modName; } } diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index 72fbb04f..3308d42f 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -35,8 +35,8 @@ public: virtual void track(bool) {} virtual void parseNexusInfo() {} virtual bool isEmpty() const { return false; } - virtual QString name() const; - virtual QString internalName() const { return name(); } + virtual QString name() const { return m_Name; } + virtual QString internalName() const { return m_InternalName; } virtual QString comments() const { return ""; } virtual QString notes() const { return ""; } virtual QDateTime creationTime() const; @@ -72,6 +72,7 @@ protected: private: QString m_Name; + QString m_InternalName; QString m_ReferenceFile; QStringList m_Archives; QDateTime m_CreationTime; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 271599e4..2540b1ba 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -4035,22 +4035,22 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. - + DLC: - + Creation Club: - + Unmanaged: @@ -6771,6 +6771,51 @@ You can restart Mod Organizer as administrator and try launching the program aga %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. (%2) + + + invalid origin name: + + + + + failed to change name lookup from {} to {} + + + + + failed to determine file time + + + + + invalid bsa file: + + + + + file "{}" not in directory "{}" + + + + + file "{}" not in directory "{}", directory empty + + + + + unexpected end of path + + + + + invalid file index for remove: {} + + + + + invalid file index for remove (for origin): {} + + QueryOverwriteDialog diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 2cdbac74..00bf319e 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -32,6 +32,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include namespace MOShared { @@ -83,7 +84,7 @@ public: return m_Origins[iter->second]; } else { std::ostringstream stream; - stream << "invalid origin name: " << ToString(name, false); + stream << QObject::tr("invalid origin name: ").toStdString() << ToString(name, true); throw std::runtime_error(stream.str()); } } @@ -106,7 +107,7 @@ public: m_OriginsNameMap.erase(iter); m_OriginsNameMap[newName] = idx; } else { - log::error("failed to change name lookup from {} to {}", oldName, newName); + log::error(QObject::tr("failed to change name lookup from {} to {}").toStdString(), oldName, newName); } } @@ -520,7 +521,7 @@ void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &di WIN32_FILE_ATTRIBUTE_DATA fileData; if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) { - throw windows_error("failed to determine file time"); + throw windows_error(QObject::tr("failed to determine file time").toStdString()); } FILETIME now; ::GetSystemTimeAsFileTime(&now); @@ -542,7 +543,7 @@ void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &di BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str(), false); if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { std::ostringstream stream; - stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError(); + stream << QObject::tr("invalid bsa file: ").toStdString() << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError(); throw std::runtime_error(stream.str()); } @@ -718,12 +719,12 @@ void DirectoryEntry::removeFile(FileEntry::Index index) m_Files.erase(iter); } else { log::error( - "file \"{}\" not in directory \"{}\"", + QObject::tr("file \"{}\" not in directory \"{}\"").toStdString(), m_FileRegister->getFile(index)->getName(), this->getName()); } } else { log::error( - "file \"{}\" not in directory \"{}\", directory empty", + QObject::tr("file \"{}\" not in directory \"{}\", directory empty").toStdString(), m_FileRegister->getFile(index)->getName(), this->getName()); } } @@ -847,7 +848,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry *temp = findSubDirectory(pathComponent); if (temp != nullptr) { if (len >= path.size()) { - log::error("unexpected end of path"); + log::error(QObject::tr("unexpected end of path").toStdString()); return FileEntry::Ptr(); } return temp->searchFile(path.substr(len + 1), directory); @@ -991,7 +992,7 @@ bool FileRegister::removeFile(FileEntry::Index index) m_Files.erase(index); return true; } else { - log::error("invalid file index for remove: {}", index); + log::error(QObject::tr("invalid file index for remove: {}").toStdString(), index); return false; } } @@ -1005,7 +1006,7 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) m_Files.erase(iter); } } else { - log::error("invalid file index for remove (for origin): {}", index); + log::error(QObject::tr("invalid file index for remove (for origin): {}").toStdString(), index); } } -- cgit v1.3.1 From c9d33dad4667e6087ced5155805e88984cc6b3fd Mon Sep 17 00:00:00 2001 From: Silarn Date: Sat, 14 Dec 2019 19:05:11 -0600 Subject: Allow content tooltip to translate --- src/modlist.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index c5b8c856..5cfa7022 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -66,18 +66,18 @@ ModList::ModList(PluginContainer *pluginContainer, QObject *parent) , m_DropOnItems(false) , m_PluginContainer(pluginContainer) { - m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)")); - m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface")); - m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes")); - m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive")); - m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)")); - m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin")); - m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher")); - m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music")); - m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures")); - m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration")); - m_ContentIcons[ModInfo::CONTENT_INI] = std::make_tuple(":/MO/gui/content/inifile", tr("INI files")); - m_ContentIcons[ModInfo::CONTENT_MODGROUP] = std::make_tuple(":/MO/gui/content/modgroup", tr("ModGroup files")); + m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", QT_TR_NOOP("Game Plugins (ESP/ESM/ESL)")); + m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", QT_TR_NOOP("Interface")); + m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", QT_TR_NOOP("Meshes")); + m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", QT_TR_NOOP("Bethesda Archive")); + m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", QT_TR_NOOP("Scripts (Papyrus)")); + m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", QT_TR_NOOP("Script Extender Plugin")); + m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", QT_TR_NOOP("SkyProc Patcher")); + m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", QT_TR_NOOP("Sound or Music")); + m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", QT_TR_NOOP("Textures")); + m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", QT_TR_NOOP("MCM Configuration")); + m_ContentIcons[ModInfo::CONTENT_INI] = std::make_tuple(":/MO/gui/content/inifile", QT_TR_NOOP("INI files")); + m_ContentIcons[ModInfo::CONTENT_MODGROUP] = std::make_tuple(":/MO/gui/content/modgroup", QT_TR_NOOP("ModGroup files")); m_LastCheck.start(); } @@ -207,7 +207,7 @@ QString ModList::contentsToToolTip(const std::vector &content if (contentsSet.find(iter->first) != contentsSet.end()) { result.append(QString("" "") - .arg(std::get<0>(iter->second)).arg(std::get<1>(iter->second))); + .arg(std::get<0>(iter->second)).arg(tr(std::get<1>(iter->second).toStdString().c_str()))); } } result.append("
    %2
    "); -- cgit v1.3.1 From dcf88361d49da262acaac335d1317a5d7ef42ded Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 15 Dec 2019 21:54:27 -0500 Subject: don't display the overlay on dialog boxes handle non-modal windows too fixed crash when closing an overlayed dialog --- src/uilocker.cpp | 84 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/uilocker.cpp b/src/uilocker.cpp index 8c4e0c2c..af9b9b96 100644 --- a/src/uilocker.cpp +++ b/src/uilocker.cpp @@ -20,6 +20,9 @@ public: ~UILockerInterface() { + if (m_topLevel) { + delete m_topLevel.data(); + } } void checkTarget() @@ -31,14 +34,8 @@ public: bool set() { - QWidget* newTarget = nullptr; - - newTarget = m_mainUI; - if (auto* w = QApplication::activeModalWidget()) { - newTarget = w; - } - - if (newTarget == m_target) { + QWidget* newTarget = findTarget(); + if (m_topLevel && newTarget == m_target) { return false; } @@ -98,8 +95,8 @@ public: QWidget* topLevel() { - return m_topLevel.get(); - } + return m_topLevel.data(); + } private: class Filter : public QObject @@ -129,7 +126,7 @@ private: std::unique_ptr m_timer; QWidget* m_mainUI; QWidget* m_target; - std::unique_ptr m_topLevel; + QPointer m_topLevel; QLabel* m_message; QLabel* m_info; QStringList m_labels; @@ -143,6 +140,55 @@ private: return (m_target != nullptr); } + QWidget* findTarget() + { + auto isValidTarget = [](QWidget* w) { + // skip message boxes + if (dynamic_cast(w)) { + return false; + } + + // skip invisible widgets + if (!w->isVisible()) { + return false; + } + + // skip windows that are too small + if (w->height() < 150) { + return false; + } + + return true; + }; + + + // find a modal dialog + QWidget* w = QApplication::activeModalWidget(); + + while (w && w != m_mainUI) { + if (isValidTarget(w)) { + return w; + } + + w = w->parentWidget(); + } + + // find a non-modal dialog that's a child of the main window + if (m_mainUI) { + const auto topLevels = QApplication::topLevelWidgets(); + + for (auto* w : topLevels) { + if (w && w->parentWidget() == m_mainUI) { + if (isValidTarget(w)) { + return w; + } + } + } + } + + return m_mainUI; + } + QWidget* createTransparentWidget(QWidget* parent=nullptr) { auto* w = new QWidget(parent); @@ -156,7 +202,12 @@ private: QFrame* createOverlay(QWidget* mainUI) { - m_topLevel.reset(createTransparentWidget(mainUI)); + if (m_topLevel) { + delete m_topLevel.data(); + m_topLevel.clear(); + } + + m_topLevel = createTransparentWidget(mainUI); m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); m_topLevel->setGeometry(mainUI->rect()); @@ -171,7 +222,12 @@ private: QFrame* createDialog() { - m_topLevel.reset(new QDialog); + if (m_topLevel) { + delete m_topLevel.data(); + m_topLevel.clear(); + } + + m_topLevel = new QDialog; return createFrame(); } @@ -195,7 +251,7 @@ private: ly->setContentsMargins(0, 0, 0, 0); } - auto* grid = new QGridLayout(m_topLevel.get()); + auto* grid = new QGridLayout(m_topLevel.data()); grid->addWidget(createTransparentWidget(), 0, 1); grid->addWidget(createTransparentWidget(), 2, 1); grid->addWidget(createTransparentWidget(), 1, 0); -- cgit v1.3.1 From f3c5cebb6e9262625105d4339a54a810d7816811 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 15 Dec 2019 21:56:50 -0500 Subject: fixed exiting before QThread joins when pressing the X twice --- src/mainwindow.cpp | 21 ++++++++++++++------- src/processrunner.cpp | 31 +++++++++++++++++++++---------- src/shared/util.cpp | 11 ++++++++++- src/shared/util.h | 1 + 4 files changed, 46 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a29ea8ab..d1578d85 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1317,18 +1317,25 @@ void MainWindow::closeEvent(QCloseEvent* event) // // for 2), the settings have been saved and the window can just close - if (ModOrganizerExiting()) { + if (ModOrganizerCanCloseNow()) { // the user has confirmed if necessary and all settings have been saved, // just close it QMainWindow::closeEvent(event); - } else { - // never close the window because settings might need to be changed - event->ignore(); + return; + } - // start the process of exiting, which may require confirmation by calling - // canExit(), among other things - ExitModOrganizer(); + if (ModOrganizerExiting()) { + // ignore repeated attempts + event->ignore(); + return; } + + // never close the window because settings might need to be changed + event->ignore(); + + // start the process of exiting, which may require confirmation by calling + // canExit(), among other things + ExitModOrganizer(); } bool MainWindow::canExit() diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 19aae632..aead42d1 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -225,7 +225,7 @@ const std::chrono::milliseconds Infinite(-1); // std::optional timedWait( HANDLE handle, DWORD pid, UILocker::Session& ls, - std::chrono::milliseconds wait) + std::chrono::milliseconds wait, std::atomic& interrupt) { using namespace std::chrono; @@ -234,7 +234,7 @@ std::optional timedWait( start = high_resolution_clock::now(); } - for (;;) { + while (!interrupt) { // wait for a very short while, allows for processing events below const auto r = singleWait(handle, pid); @@ -286,10 +286,13 @@ std::optional timedWait( } } } + + log::debug("waiting for {} interrupted", pid); + return ProcessRunner::ForceUnlocked; } ProcessRunner::Results waitForProcessesThreadImpl( - HANDLE job, UILocker::Session& ls) + HANDLE job, UILocker::Session& ls, std::atomic& interrupt) { using namespace std::chrono; @@ -301,7 +304,7 @@ ProcessRunner::Results waitForProcessesThreadImpl( const milliseconds defaultWait(50); auto wait = defaultWait; - for (;;) { + while (!interrupt) { auto ip = getInterestingProcess(job); if (!ip.handle) { // nothing to wait on @@ -325,7 +328,7 @@ ProcessRunner::Results waitForProcessesThreadImpl( wait = Infinite; } - const auto r = timedWait(ip.handle.get(), ip.p.pid(), ls, wait); + const auto r = timedWait(ip.handle.get(), ip.p.pid(), ls, wait, interrupt); if (r) { if (*r == ProcessRunner::Results::Completed) { // process completed, check another one, reset the wait time to find @@ -344,9 +347,10 @@ ProcessRunner::Results waitForProcessesThreadImpl( } void waitForProcessesThread( - ProcessRunner::Results& result, HANDLE job, UILocker::Session& ls) + ProcessRunner::Results& result, HANDLE job, UILocker::Session& ls, + std::atomic& interrupt) { - result = waitForProcessesThreadImpl(job, ls); + result = waitForProcessesThreadImpl(job, ls, interrupt); ls.unlock(); } @@ -379,9 +383,11 @@ ProcessRunner::Results waitForProcesses( } auto results = ProcessRunner::Running; + std::atomic interrupt(false); auto* t = QThread::create( - waitForProcessesThread, std::ref(results), job.get(), std::ref(ls)); + waitForProcessesThread, + std::ref(results), job.get(), std::ref(ls), std::ref(interrupt)); QEventLoop events; QObject::connect(t, &QThread::finished, [&]{ @@ -391,6 +397,11 @@ ProcessRunner::Results waitForProcesses( t->start(); events.exec(); + if (t->isRunning()) { + interrupt = true; + t->wait(); + } + delete t; return results; @@ -861,7 +872,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; withLock([&](auto& ls) { - for (;;) { + for (;;) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { break; @@ -878,7 +889,7 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( } r = Completed; - }); + }); return r; } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 32eb825c..baceddeb 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -294,6 +294,7 @@ QString getUsvfsVersionString() static bool g_exiting = false; +static bool g_canClose = false; MainWindow* findMainWindow() { @@ -312,6 +313,9 @@ bool ExitModOrganizer(ExitFlags e) return true; } + g_exiting = true; + MOBase::Guard g([&]{ g_exiting = false; }); + if (!e.testFlag(Exit::Force)) { if (auto* mw=findMainWindow()) { if (!mw->canExit()) { @@ -320,7 +324,7 @@ bool ExitModOrganizer(ExitFlags e) } } - g_exiting = true; + g_canClose = true; const int code = (e.testFlag(Exit::Restart) ? RestartExitCode : 0); qApp->exit(code); @@ -328,6 +332,11 @@ bool ExitModOrganizer(ExitFlags e) return true; } +bool ModOrganizerCanCloseNow() +{ + return g_canClose; +} + bool ModOrganizerExiting() { return g_exiting; diff --git a/src/shared/util.h b/src/shared/util.h index e87244b6..e8a58549 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -65,6 +65,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(ExitFlags); bool ExitModOrganizer(ExitFlags e=Exit::Normal); bool ModOrganizerExiting(); +bool ModOrganizerCanCloseNow(); void ResetExitFlag(); #endif // UTIL_H -- cgit v1.3.1 From 05231eab45f86e3d0d342c429e35c8f7c813ea42 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 15 Dec 2019 22:35:02 -0500 Subject: temporary fix to keep MO locked for all processes when closing save main window settings in closeEvent() --- src/main.cpp | 1 - src/mainwindow.cpp | 22 ++++++++++++++++++++++ src/processrunner.cpp | 26 ++++++++++++++++++-------- src/spawn.cpp | 6 ++++-- src/uilocker.cpp | 5 +++++ src/uilocker.h | 1 + 6 files changed, 50 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index eeabd497..29d2d02c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -745,7 +745,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, splash.finish(&mainWindow); res = application.exec(); - mainWindow.onBeforeClose(); mainWindow.close(); NexusInterface::instance(&pluginContainer) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d1578d85..284c33e3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1306,6 +1306,21 @@ void MainWindow::onBeforeClose() void MainWindow::closeEvent(QCloseEvent* event) { + if (isVisible()) { + // this is messy + // + // the main problem this is solving is when closing MO, then getting the + // lock overlay because processes are still running, then pressing the X + // again + // + // in this case, closeEvent() is _not_ called for the second event and the + // window is immediately hidden + // + // this always saves the settings here; in the event where a lock overlay + // is then shown, it might save settings multiple times, but it's harmless + onBeforeClose(); + } + // this happens for two reasons: // 1) the user requested to close the window, such as clicking the X // 2) close() is called in runApplication() after application.exec() @@ -1324,6 +1339,13 @@ void MainWindow::closeEvent(QCloseEvent* event) return; } + if (UILocker::instance().locked()) { + // don't bother asking the user to confirm if the ui is already locked + event->ignore(); + ExitModOrganizer(Exit::Force); + return; + } + if (ModOrganizerExiting()) { // ignore repeated attempts event->ignore(); diff --git a/src/processrunner.cpp b/src/processrunner.cpp index aead42d1..945d61c3 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -344,6 +344,9 @@ ProcessRunner::Results waitForProcessesThreadImpl( // processes wait = std::min(wait * 2, milliseconds(2000)); } + + log::debug("waiting for processes interrupted"); + return ProcessRunner::ForceUnlocked; } void waitForProcessesThread( @@ -374,9 +377,12 @@ ProcessRunner::Results waitForProcesses( if (!::AssignProcessToJobObject(job.get(), h)) { const auto e = GetLastError(); - log::error( - "can't assign process to job to wait for processes, {}", - formatSystemMessage(e)); + // this happens when closing MO while multiple processes are running, + // so the logging is disabled until it gets fixed + + //log::error( + // "can't assign process to job to wait for processes, {}", + // formatSystemMessage(e)); // keep going } @@ -871,11 +877,12 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( auto r = Error; - withLock([&](auto& ls) { for (;;) { + withLock([&](auto& ls) { const auto processes = getRunningUSVFSProcesses(); if (processes.empty()) { - break; + r = Completed; + return; } r = waitForProcesses(processes, ls); @@ -886,11 +893,14 @@ ProcessRunner::Results ProcessRunner::waitForAllUSVFSProcessesWithLock( } // this process is completed, check for others - } - - r = Completed; + r = Running; }); + if (r != Running) { + break; + } + } + return r; } diff --git a/src/spawn.cpp b/src/spawn.cpp index f95846c8..33bdbc05 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -498,14 +498,16 @@ DWORD spawn(const SpawnParameters& sp, HANDLE& processHandle) const auto wcommandLine = commandLine.toStdWString(); const auto wcwd = cwd.toStdWString(); + const DWORD flags = CREATE_BREAKAWAY_FROM_JOB; + if (sp.hooked) { success = ::CreateProcessHooked( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); + inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); } else { success = ::CreateProcess( nullptr, const_cast(wcommandLine.c_str()), nullptr, nullptr, - inheritHandles, 0, nullptr, wcwd.c_str(), &si, &pi); + inheritHandles, flags, nullptr, wcwd.c_str(), &si, &pi); } const auto e = GetLastError(); diff --git a/src/uilocker.cpp b/src/uilocker.cpp index af9b9b96..07fe7f1b 100644 --- a/src/uilocker.cpp +++ b/src/uilocker.cpp @@ -461,6 +461,11 @@ std::shared_ptr UILocker::lock(Reasons reason) return ls; } +bool UILocker::locked() const +{ + return !m_sessions.empty(); +} + void UILocker::unlock(Session* s) { auto itor = m_sessions.begin(); diff --git a/src/uilocker.h b/src/uilocker.h index d8c22999..cc467184 100644 --- a/src/uilocker.h +++ b/src/uilocker.h @@ -70,6 +70,7 @@ public: void setUserInterface(QWidget* parent); std::shared_ptr lock(Reasons reason); + bool locked() const; Results result() const; -- cgit v1.3.1 From ca6f9990c691a5d20e2b8d97ad98e4ad80c13a9e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 17 Dec 2019 06:32:08 -0500 Subject: added text in when running loot stop indeterminate progress bar on errors add errors and warnings to report --- src/loot.cpp | 124 ++++++++++++++++++++++++++++++++++++++++++---------- src/loot.h | 17 +++++-- src/lootdialog.cpp | 18 +++++--- src/organizer_en.ts | 50 +++++++++++++++------ 4 files changed, 164 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/loot.cpp b/src/loot.cpp index a86cdcff..3feb95c9 100644 --- a/src/loot.cpp +++ b/src/loot.cpp @@ -231,11 +231,31 @@ log::Levels levelFromLoot(lootcli::LogLevels level) } } - QString Loot::Report::toMarkdown() const { QString s; + if (!okay) { + s += "## " + tr("Loot failed to run") + "\n"; + + if (errors.empty() && warnings.empty()) { + s += tr("No errors were reported. The log below might have more information.\n"); + } + } + + s += errorsMarkdown(); + + if (okay) { + s += "\n" + successMarkdown(); + } + + return s; +} + +QString Loot::Report::successMarkdown() const +{ + QString s; + if (!messages.empty()) { s += "### " + QObject::tr("General messages") + "\n"; @@ -268,6 +288,33 @@ QString Loot::Report::toMarkdown() const return s; } +QString Loot::Report::errorsMarkdown() const +{ + QString s; + + if (!errors.empty()) { + s += "### " + tr("Errors") + ":\n"; + + for (auto&& e : errors) { + s += " - " + e + "\n"; + } + } + + if (!warnings.empty()) { + if (!s.isEmpty()) { + s += "\n"; + } + + s += "### " + tr("Warnings") + ":\n"; + + for (auto&& w : warnings) { + s += " - " + w + "\n"; + } + } + + return s; +} + QString Loot::Stats::toMarkdown() const { return QString("`stats: %1s, lootcli %2, loot %3`") @@ -396,20 +443,13 @@ Loot::~Loot() m_thread->wait(); } - if (QFile::exists(LootReportPath)) { - log::debug("deleting temporary loot report '{}'", LootReportPath); - const auto r = shell::Delete(LootReportPath); - - if (!r) { - log::error( - "failed to remove temporary loot json report '{}': {}", - LootReportPath, r.toString()); - } - } + deleteReportFile(); } bool Loot::start(QWidget* parent, bool didUpdateMasterList) { + deleteReportFile(); + log::debug("starting loot"); m_pipe.reset(new AsyncPipe); @@ -506,6 +546,16 @@ const Loot::Report& Loot::report() const return m_report; } +const std::vector& Loot::errors() const +{ + return m_errors; +} + +const std::vector& Loot::warnings() const +{ + return m_warnings; +} + void Loot::lootThread() { try @@ -514,8 +564,9 @@ void Loot::lootThread() if (waitForCompletion()) { m_result = true; - processOutputFile(); } + + m_report = createReport(); } catch(...) { @@ -624,7 +675,15 @@ void Loot::processMessage(const lootcli::Message& m) { case lootcli::MessageType::Log: { - emit log(levelFromLoot(m.logLevel), QString::fromStdString(m.log)); + const auto level = levelFromLoot(m.logLevel); + + if (level == log::Error) { + m_errors.push_back(QString::fromStdString(m.log)); + } else if (level == log::Warning) { + m_warnings.push_back(QString::fromStdString(m.log)); + } + + emit log(level, QString::fromStdString(m.log)); break; } @@ -636,7 +695,36 @@ void Loot::processMessage(const lootcli::Message& m) } } -void Loot::processOutputFile() +Loot::Report Loot::createReport() const +{ + Report r; + + r.okay = m_result; + r.errors = m_errors; + r.warnings = m_warnings; + + if (m_result) { + processOutputFile(r); + } + + return r; +} + +void Loot::deleteReportFile() +{ + if (QFile::exists(LootReportPath)) { + log::debug("deleting temporary loot report '{}'", LootReportPath); + const auto r = shell::Delete(LootReportPath); + + if (!r) { + log::error( + "failed to remove temporary loot json report '{}': {}", + LootReportPath, r.toString()); + } + } +} + +void Loot::processOutputFile(Report& r) const { log::debug("parsing json output file at '{}'", LootReportPath); @@ -661,21 +749,13 @@ void Loot::processOutputFile() return; } - m_report = createReport(doc); -} - -Loot::Report Loot::createReport(const QJsonDocument& doc) const -{ requireObject(doc, "root"); - Report r; const QJsonObject object = doc.object(); r.messages = reportMessages(getOpt(object, "messages")); r.plugins = reportPlugins(getOpt(object, "plugins")); r.stats = reportStats(getWarn(object, "stats")); - - return r; } std::vector Loot::reportPlugins(const QJsonArray& plugins) const diff --git a/src/loot.h b/src/loot.h index 4ec06d6f..f9943626 100644 --- a/src/loot.h +++ b/src/loot.h @@ -71,11 +71,17 @@ public: struct Report { + bool okay = false; + std::vector errors, warnings; std::vector messages; std::vector plugins; Stats stats; QString toMarkdown() const; + + private: + QString successMarkdown() const; + QString errorsMarkdown() const; }; @@ -85,13 +91,16 @@ public: bool start(QWidget* parent, bool didUpdateMasterList); void cancel(); bool result() const; + const QString& outPath() const; const Report& report() const; + const std::vector& errors() const; + const std::vector& warnings() const; signals: void output(const QString& s); void progress(const lootcli::Progress p); - void log(MOBase::log::Levels level, const QString& s); + void log(MOBase::log::Levels level, const QString& s) const; void finished(); private: @@ -102,6 +111,7 @@ private: env::HandlePtr m_lootProcess; std::unique_ptr m_pipe; std::string m_outputBuffer; + std::vector m_errors, m_warnings; Report m_report; bool spawnLootcli( @@ -113,9 +123,10 @@ private: void processStdout(const std::string &lootOut); void processMessage(const lootcli::Message& m); - void processOutputFile(); + Report createReport() const; + void processOutputFile(Report& r) const; + void deleteReportFile(); - Report createReport(const QJsonDocument& doc) const; Message reportMessage(const QJsonObject& message) const; std::vector reportPlugins(const QJsonArray& plugins) const; Loot::Plugin reportPlugin(const QJsonObject& plugin) const; diff --git a/src/lootdialog.cpp b/src/lootdialog.cpp index ae3b1164..5ac65907 100644 --- a/src/lootdialog.cpp +++ b/src/lootdialog.cpp @@ -212,6 +212,8 @@ void LootDialog::createUI() ui->buttons->setStandardButtons(QDialogButtonBox::Cancel); + m_report.setText(tr("Running LOOT...")); + resize(650, 450); } @@ -243,9 +245,15 @@ void LootDialog::onFinished() close(); } else { log::debug("loot dialog: showing report"); + showReport(); + ui->openJsonReport->setEnabled(true); ui->buttons->setStandardButtons(QDialogButtonBox::Close); + + // if loot failed, the Done progress won't be received; this makes sure + // the progress bar is stopped + setProgress(lootcli::Progress::Done); } } @@ -262,16 +270,14 @@ void LootDialog::log(log::Levels lv, const QString& s) void LootDialog::showReport() { - if (m_loot.result()) { - const auto& lootReport = m_loot.report(); + const auto& lootReport = m_loot.report(); + if (m_loot.result()) { m_core.pluginList()->clearAdditionalInformation(); for (auto&& p : lootReport.plugins) { m_core.pluginList()->addLootReport(p.name, p); } - - m_report.setText(lootReport.toMarkdown()); - } else { - m_report.setText("**" + tr("Loot failed to run") + "**"); } + + m_report.setText(lootReport.toMarkdown()); } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 2540b1ba..32474a23 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1681,12 +1681,34 @@ This is likely due to a corrupted or incompatible download or unrecognized archi Loot - + + Loot failed to run + + + + + No errors were reported. The log below might have more information. + + No errors were reported. The log below might have more information. + + + + + Errors + + + + + Warnings + + + + failed to start loot - + Loot failed. Exit code was: %1 @@ -1724,8 +1746,8 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - - Loot failed to run + + Running LOOT...
    @@ -5500,7 +5522,7 @@ p, li { white-space: pre-wrap; } - + @@ -5945,49 +5967,49 @@ If the folder was still in use, restart MO and try again. - + General messages - + Plugins - + No messages. - + Incompatibilities - + Missing masters - + Verified clean by %1 - + %1 found %2 ITM record(s), %3 deleted reference(s) and %4 deleted navmesh(es). - + Warning - + failed to run loot: %1 -- cgit v1.3.1 From 932d32195600cf297250dd2ddda635eee13464fa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 17 Dec 2019 15:14:23 -0500 Subject: bumped to rc5 added reedts to contributors --- src/aboutdialog.ui | 5 + src/organizer_en.ts | 516 ++++++++++++++++++++++++++-------------------------- src/version.rc | 2 +- 3 files changed, 264 insertions(+), 259 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 48cb068c..ec2b754d 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -474,6 +474,11 @@ outdatedtv + + + reedts + + Schilduin diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 32474a23..d05ada70 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -125,7 +125,7 @@ - + Close @@ -1787,7 +1787,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Save @@ -1896,7 +1896,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2072,8 +2072,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2358,7 +2358,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2479,8 +2479,8 @@ Error: %1 - - + + Endorse @@ -2550,700 +2550,700 @@ Error: %1 - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3251,12 +3251,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3264,335 +3264,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - + &Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6126,18 +6126,18 @@ If the folder was still in use, restart MO and try again. - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance @@ -6152,13 +6152,13 @@ If the folder was still in use, restart MO and try again. - - + + <Manage...> - + failed to parse profile %1: %2 @@ -6317,9 +6317,9 @@ If the folder was still in use, restart MO and try again. - - - + + + No profile set @@ -6437,7 +6437,7 @@ If the folder was still in use, restart MO and try again. - + Cancel @@ -6702,22 +6702,22 @@ You can restart Mod Organizer as administrator and try launching the program aga - + Waiting - + Please press OK once you're logged into steam. - + Select binary - + Binary @@ -6747,32 +6747,32 @@ You can restart Mod Organizer as administrator and try launching the program aga - + Mod Organizer is locked while the application is running. - + Mod Organizer is currently running an application. - + The application must run to completion because its output is required. - + Mod Organizer is waiting on application to close before exiting. - + Unlock - + Exit Now diff --git a/src/version.rc b/src/version.rc index 913d1ead..0cf44243 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc4\0" +#define VER_FILEVERSION_STR "2.2.2rc5\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 31cd5531d030838a30d55bcd63cadfff4ecd50ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 18 Dec 2019 17:21:52 -0500 Subject: windows 7 doesn't play well with job objects, so just wait on individual handles fixed getProcessTreeFromProcess() not behaving like getProcessTreeFromJob() --- src/envmodule.cpp | 13 +++++-------- src/processrunner.cpp | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/envmodule.cpp b/src/envmodule.cpp index 8d348b5e..5be52de6 100644 --- a/src/envmodule.cpp +++ b/src/envmodule.cpp @@ -545,23 +545,20 @@ void findChildren(Process& parent, const std::vector& processes) Process getProcessTreeFromProcess(HANDLE h) { + Process root; + const auto parentPID = ::GetProcessId(h); const auto v = getRunningProcesses(); - Process root; for (auto&& p : v) { if (p.pid() == parentPID) { - root = p; + Process child = p; + findChildren(child, v); + root.addChild(child); break; } } - if (root.pid() == 0) { - return {}; - } - - findChildren(root, v); - return root; } diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 945d61c3..3f1e3a3b 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -360,6 +360,11 @@ void waitForProcessesThread( ProcessRunner::Results waitForProcesses( const std::vector& initialProcesses, UILocker::Session& ls) { + if (initialProcesses.empty()) { + // nothing to wait for + return ProcessRunner::Completed; + } + // using a job so any child process started by any of those processes can also // be captured and monitored env::HandlePtr job(CreateJobObjectW(nullptr, nullptr)); @@ -373,8 +378,12 @@ ProcessRunner::Results waitForProcesses( return ProcessRunner::Error; } + bool oneWorked = false; + for (auto&& h : initialProcesses) { - if (!::AssignProcessToJobObject(job.get(), h)) { + if (::AssignProcessToJobObject(job.get(), h)) { + oneWorked = true; + } else { const auto e = GetLastError(); // this happens when closing MO while multiple processes are running, @@ -388,12 +397,21 @@ ProcessRunner::Results waitForProcesses( } } + HANDLE monitor = INVALID_HANDLE_VALUE; + + if (oneWorked) { + monitor = job.get(); + } else { + // none of the handles could be added to the job, just monitor the first one + monitor = initialProcesses[0]; + } + auto results = ProcessRunner::Running; std::atomic interrupt(false); auto* t = QThread::create( waitForProcessesThread, - std::ref(results), job.get(), std::ref(ls), std::ref(interrupt)); + std::ref(results), monitor, std::ref(ls), std::ref(interrupt)); QEventLoop events; QObject::connect(t, &QThread::finished, [&]{ -- cgit v1.3.1 From dc6653e1009a8081cd4bae7ba15751b9cc6b5e4b Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 19 Dec 2019 10:52:45 -0600 Subject: Fix conflict filter --- src/modlistsortproxy.cpp | 6 +++--- src/modlistsortproxy.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 440786c5..162b0653 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -269,9 +269,9 @@ void ModListSortProxy::updateFilter(const QString& filter) invalidate(); } -bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const +bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const { - for (ModInfo::EFlag flag : flags) { + for (ModInfo::EConflictFlag flag : flags) { if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || @@ -382,7 +382,7 @@ bool ModListSortProxy::categoryMatchesMod( case CategoryFactory::Conflict: { - b = (hasConflictFlag(info->getFlags())); + b = (hasConflictFlag(info->getConflictFlags())); break; } diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index d733b783..3a29b7f7 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -143,7 +143,7 @@ private: unsigned long flagsId(const std::vector &flags) const; unsigned long conflictFlagsId(const std::vector& flags) const; - bool hasConflictFlag(const std::vector &flags) const; + bool hasConflictFlag(const std::vector &flags) const; void updateFilterActive(); bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; bool filterMatchesModOr(ModInfo::Ptr info, bool enabled) const; -- cgit v1.3.1 From 52cc3a41c73851d88983223361c03b800db8c2a2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 29 Dec 2019 07:16:09 -0500 Subject: refresh after manually unlocking the ui fixed nexus connect button staying as "cancel" in case of errors fixed logging of duplicate dll loading bumped to rc6 --- src/env.cpp | 1 + src/nxmaccessmanager.cpp | 2 +- src/organizer_en.ts | 6 +++--- src/processrunner.cpp | 56 +++++++++++++++++++++++++++++++++++++----------- src/processrunner.h | 1 + src/version.rc | 2 +- 6 files changed, 50 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/env.cpp b/src/env.cpp index 0098456e..4c0aeb86 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -101,6 +101,7 @@ void ModuleNotification::fire(QString path, std::size_t fileSize) { if (m_loaded.contains(path)) { // don't notify if it's been loaded before + return; } m_loaded.insert(path); diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index e5f1fffe..20540593 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -333,8 +333,8 @@ void NexusSSOLogin::onDisconnected() void NexusSSOLogin::onError(QAbstractSocket::SocketError e) { if (m_active) { - setState(Error, m_socket.errorString()); close(); + setState(Error, m_socket.errorString()); } } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index d05ada70..fe4dfd32 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -6317,9 +6317,9 @@ If the folder was still in use, restart MO and try again. - - - + + + No profile set diff --git a/src/processrunner.cpp b/src/processrunner.cpp index 3f1e3a3b..91443750 100644 --- a/src/processrunner.cpp +++ b/src/processrunner.cpp @@ -795,6 +795,48 @@ std::optional ProcessRunner::runBinary() return {}; } +bool ProcessRunner::shouldRefresh(Results r) const +{ + // afterRun() is only called with the Refresh flag; it refreshes the + // directory structure and notifies plugins + // + // refreshing is not always required and can actually cause problems: + // + // 1) running shortcuts doesn't need refreshing because MO closes right + // after + // + // 2) the mod info dialog is not set up to deal with refreshes, so that + // it will crash because the old DirectoryEntry's are still being used + // in the list + if (!m_waitFlags.testFlag(Refresh)) { + log::debug("not refreshing because the flag isn't set"); + return false; + } + + switch (r) + { + case Completed: + { + log::debug("refreshing because the process completed"); + return true; + } + + case ForceUnlocked: + { + log::debug("refreshing because the ui was force unlocked"); + return true; + } + + case Error: // fall-through + case Cancelled: + case Running: + default: + { + return false; + } + } +} + ProcessRunner::Results ProcessRunner::postRun() { const bool mustWait = (m_waitFlags & ForceWait); @@ -841,19 +883,7 @@ ProcessRunner::Results ProcessRunner::postRun() r = waitForProcess(m_handle.get(), &m_exitCode, ls); }); - if (r == Completed && (m_waitFlags & Refresh)) { - // afterRun() is only called with the Refresh flag; it refreshes the - // directory structure and notifies plugins - // - // refreshing is not always required and can actually cause problems: - // - // 1) running shortcuts doesn't need refreshing because MO closes right - // after - // - // 2) the mod info dialog is not set up to deal with refreshes, so that - // it will crash because the old DirectoryEntry's are still being used - // in the list - // + if (shouldRefresh(r)) { m_core.afterRun(m_sp.binary, m_exitCode); } diff --git a/src/processrunner.h b/src/processrunner.h index 1bfdc465..d8ff0227 100644 --- a/src/processrunner.h +++ b/src/processrunner.h @@ -156,6 +156,7 @@ private: bool shouldRunShell() const; + bool shouldRefresh(Results r) const; // runs the command in m_shellOpen; returns empty if it can be waited for // diff --git a/src/version.rc b/src/version.rc index 0cf44243..f7b5f0d5 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc5\0" +#define VER_FILEVERSION_STR "2.2.2rc6\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 4d6cee82f42b5a4ff41c1360fbbbf90077c3b867 Mon Sep 17 00:00:00 2001 From: Chris Bessent Date: Wed, 1 Jan 2020 12:05:26 -0700 Subject: Update version to 2.2.2rc7 --- src/version.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index f7b5f0d5..659e3c11 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,2 -#define VER_FILEVERSION_STR "2.2.2rc6\0" +#define VER_FILEVERSION_STR "2.2.2rc7\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 19a20159a5c8ad72f6978331b268ee75a1cad94b Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Wed, 1 Jan 2020 21:38:18 -0700 Subject: Fix the API counter not being translated --- src/mainwindow.cpp | 3 +- src/organizer_en.ts | 654 ++++++++++++++++++++++++++-------------------------- src/statusbar.cpp | 4 +- 3 files changed, 328 insertions(+), 333 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 284c33e3..5da42b93 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -232,6 +232,7 @@ MainWindow::MainWindow(Settings &settings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(settings.paths().cache()); ui->setupUi(this); + languageChange(settings.interface().language()); ui->statusBar->setup(ui, settings); { @@ -259,8 +260,6 @@ MainWindow::MainWindow(Settings &settings ui->statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } - languageChange(settings.interface().language()); - m_CategoryFactory.loadCategories(); m_Filters.reset(new FilterList(ui, m_CategoryFactory)); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index fe4dfd32..d30057aa 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1572,7 +1572,6 @@ Right now the only case I know of where this needs to be overwritten is for the Something went wrong while installing this mod. - The mod was not installed completely. @@ -1689,7 +1688,6 @@ This is likely due to a corrupted or incompatible download or unrecognized archi No errors were reported. The log below might have more information. - No errors were reported. The log below might have more information. @@ -1896,7 +1894,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -2072,8 +2070,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2358,7 +2356,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2436,814 +2434,814 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + (no executables) - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Stylesheets folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3251,12 +3249,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3264,335 +3262,335 @@ You can also use online editors and converters instead. - + Restart Mod Organizer - + Mod Organizer must restart to finish configuration changes - + Restart - + Continue - + Some things might be weird. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + &Execute - + Execute with &VFS - + &Open - + Open with &VFS - - &Add as Executable + + Preview - - Preview + + &Add as Executable - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - + Thank you for endorsing MO2! :) - + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + <Multiple> - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -6147,18 +6145,18 @@ If the folder was still in use, restart MO and try again. - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -6328,6 +6326,21 @@ If the folder was still in use, restart MO and try again. Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! + + + '%1': file is blocked (%2) + + + + + '%1' seems to be missing, an antivirus may have deleted it + + + + + %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. + + @@ -6532,6 +6545,51 @@ Example: Geometries will be reset to their default values. + + + invalid origin name: + + + + + failed to change name lookup from {} to {} + + + + + failed to determine file time + + + + + invalid bsa file: + + + + + file "{}" not in directory "{}" + + + + + file "{}" not in directory "{}", directory empty + + + + + unexpected end of path + + + + + invalid file index for remove: {} + + + + + invalid file index for remove (for origin): {} + + This error typically happens because an antivirus has deleted critical files from Mod Organizer's installation folder or has made them generally inaccessible. Add an exclusion for Mod Organizer's installation folder in your antivirus, reinstall Mod Organizer and try again. @@ -6776,68 +6834,6 @@ You can restart Mod Organizer as administrator and try launching the program aga Exit Now - - - '%1': file is blocked (%2) - '%1': file is blocked ('%2') - - - - - '%1' seems to be missing, an antivirus may have deleted it - - - - - %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. - %1 is loaded. This program is known to cause issues with Mod Organizer, such as freezing or blank windows. Consider uninstalling it. (%2) - - - - - invalid origin name: - - - - - failed to change name lookup from {} to {} - - - - - failed to determine file time - - - - - invalid bsa file: - - - - - file "{}" not in directory "{}" - - - - - file "{}" not in directory "{}", directory empty - - - - - unexpected end of path - - - - - invalid file index for remove: {} - - - - - invalid file index for remove (for origin): {} - -
    QueryOverwriteDialog @@ -7057,13 +7053,11 @@ Select Show Details option to see the full change-log. https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/ - https://www.transifex.com/tannin/mod-organizer/ <a href="https://www.transifex.com/mod-organizer-2-team/mod-organizer-2/">Help translate Mod Organizer</a> - <a href="https://www.transifex.com/tannin/mod-organizer/">Help translate Mod Organizer</a> diff --git a/src/statusbar.cpp b/src/statusbar.cpp index c2c54862..4729c6ad 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -69,7 +69,9 @@ void StatusBar::setProgress(int percent) void StatusBar::setNotifications(bool hasNotifications) { - m_notifications->set(hasNotifications); + if (m_notifications) { + m_notifications->set(hasNotifications); + } } void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) -- cgit v1.3.1 From 72e99f7c5c41fcc1c60ba59397ea560436dbd93c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 6 Jan 2020 04:34:43 -0500 Subject: changed widget background color to opaque, fixes redrawing problems in the images tab --- src/stylesheets/dracula.qss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/stylesheets/dracula.qss b/src/stylesheets/dracula.qss index 537ff083..a78d2414 100644 --- a/src/stylesheets/dracula.qss +++ b/src/stylesheets/dracula.qss @@ -4,7 +4,7 @@ QWidget, QStackedWidget, QScrollArea, QAbstractScrollArea { - background-color: transparent; + background-color: #3c3f41; color: #bbbbbb; } -- cgit v1.3.1