From ed4eb2aae8fcf493c07a02cbca3df14f00c5e130 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 9 May 2019 12:04:03 -0500 Subject: Update NXM link handling to support premium link and validate user info --- src/mainwindow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.h b/src/mainwindow.h index d119f49c..727dd165 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -396,7 +396,7 @@ private: private slots: - void updateWindowTitle(const QString &accountName, bool premium); + void updateWindowTitle(const QString &accountName, int, bool premium); void showMessage(const QString &message); void showError(const QString &message); -- cgit v1.3.1 From f5330efd0d2692eb14738d8ccbe4e269ce165b46 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 May 2019 01:05:15 -0400 Subject: a copy of getBinaryExecuteInfo() and openDataFile() was in both mainwindow and modinfodialog, it's now moved to organizercore and renamed getFileExecutionContext() and executefile() getFileExecutionContext() is also changed to return an enum instead of a 0-1-2 int --- src/mainwindow.cpp | 116 +++++++++++++------------------------------------- src/mainwindow.h | 1 - src/modinfodialog.cpp | 79 +--------------------------------- src/modinfodialog.h | 1 - src/organizercore.cpp | 96 +++++++++++++++++++++++++++++++++++++++++ src/organizercore.h | 13 ++++++ 6 files changed, 140 insertions(+), 166 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1b95ea0b..88d7ba9d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5153,73 +5153,28 @@ void MainWindow::writeDataToFile() } } - -int MainWindow::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) -{ - 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())); - return 1; - } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { - binaryInfo = targetInfo; - return 1; - } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - // types that need to be injected into - std::wstring targetPathW = ToWString(targetInfo.absoluteFilePath()); - QString binaryPath; - - { // try to find java automatically - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(buffer, &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty() && (extension == "jar")) { - // second attempt: look to the registry - QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); - if (javaReg.contains("CurrentVersion")) { - QString currentVersion = javaReg.value("CurrentVersion").toString(); - binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - } - if (binaryPath.isEmpty()) { - binaryPath = QFileDialog::getOpenFileName(this, tr("Select binary"), QString(), tr("Binary") + " (*.exe)"); - } - if (binaryPath.isEmpty()) { - return 0; - } - binaryInfo = QFileInfo(binaryPath); - if (extension == "jar") { - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } else { - arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } - return 1; - } else { - return 2; +void MainWindow::addAsExecutable() +{ + if (m_ContextItem == nullptr) { + return; } -} + QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + QFileInfo binaryInfo; + QString arguments; + FileExecutionTypes type; -void MainWindow::addAsExecutable() -{ - if (m_ContextItem != nullptr) { - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { + if (!m_OrganizerCore.getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { + return; + } + + switch (type) + { + case FileExecutionTypes::executable: { QString name = QInputDialog::getText(this, tr("Enter Name"), tr("Please enter a name for the executable"), QLineEdit::Normal, targetInfo.baseName()); + if (!name.isEmpty()) { //Note: If this already exists, you'll lose custom settings m_OrganizerCore.executablesList()->addExecutable(name, @@ -5230,14 +5185,15 @@ void MainWindow::addAsExecutable() Executable::CustomExecutable); refreshExecutablesList(); } - } break; - case 2: { + + break; + } + + case FileExecutionTypes::other: // fall-through + default: { QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); - } break; - default: { - // nop - } break; - } + break; + } } } @@ -5420,26 +5376,12 @@ void MainWindow::previewDataFile() void MainWindow::openDataFile() { - if (m_ContextItem != nullptr) { - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - m_OrganizerCore.spawnBinaryDirect( - binaryInfo, arguments, m_OrganizerCore.currentProfile()->name(), - targetInfo.absolutePath(), "", ""); - } break; - case 2: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - } break; - default: { - // nop - } break; - } + if (m_ContextItem == nullptr) { + return; } + + QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + m_OrganizerCore.executeFile(this, targetInfo); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 727dd165..b4ad0bdb 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -260,7 +260,6 @@ private: size_t checkForProblems(); - int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type); void addContentFilters(); void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1304b27a..83c0169a 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1696,65 +1696,6 @@ void ModInfoDialog::unhideConflictFiles() changeConflictFilesVisibility(false); } -int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) -{ - 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())); - return 1; - } - else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { - binaryInfo = targetInfo; - return 1; - } - else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - // types that need to be injected into - std::wstring targetPathW = ToWString(targetInfo.absoluteFilePath()); - QString binaryPath; - - { // try to find java automatically - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(buffer, &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); - } - else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty() && (extension == "jar")) { - // second attempt: look to the registry - QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); - if (javaReg.contains("CurrentVersion")) { - QString currentVersion = javaReg.value("CurrentVersion").toString(); - binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - } - if (binaryPath.isEmpty()) { - binaryPath = QFileDialog::getOpenFileName(this, tr("Select binary"), QString(), tr("Binary") + " (*.exe)"); - } - if (binaryPath.isEmpty()) { - return 0; - } - binaryInfo = QFileInfo(binaryPath); - if (extension == "jar") { - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } - else { - arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } - return 1; - } - else { - return 2; - } -} - void ModInfoDialog::previewOverwriteDataFile() { // the menu item is only shown for a single selection, but check just in case @@ -1812,23 +1753,7 @@ void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) } QFileInfo targetInfo(item->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - m_OrganizerCore->spawnBinaryDirect( - binaryInfo, arguments, m_OrganizerCore->currentProfile()->name(), - targetInfo.absolutePath(), "", ""); - } break; - case 2: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - } break; - default: { - // nop - } break; - } + m_OrganizerCore->executeFile(this, targetInfo); } void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) @@ -2016,7 +1941,7 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po // note that it is possible for hidden files to appear if they override other // hidden files from another mod if (enableUnhide) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); + menu.addAction(tr("Unhide"), this, SLOT(unhideConflictFiles())); } if (enableOpen) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index dc04deb3..fdea2d4d 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -335,7 +335,6 @@ private slots: void unhideConflictFiles(); void previewOverwriteDataFile(); void openOverwriteDataFile(); - int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); void previewOverwrittenDataFile(); void openOverwrittenDataFile(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 8212d248..bdaf4ffc 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1220,6 +1220,102 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res; } + +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) { + // types that need to be injected into + std::wstring targetPathW = ToWString(targetInfo.absoluteFilePath()); + QString binaryPath; + + { // try to find java automatically + WCHAR buffer[MAX_PATH]; + if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(buffer, &binaryType)) { + qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); + } else if (binaryType == SCS_32BIT_BINARY) { + binaryPath = ToQString(buffer); + } + } + } + if (binaryPath.isEmpty() && (extension == "jar")) { + // second attempt: look to the registry + QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); + if (javaReg.contains("CurrentVersion")) { + QString currentVersion = javaReg.value("CurrentVersion").toString(); + binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); + } + } + if (binaryPath.isEmpty()) { + binaryPath = QFileDialog::getOpenFileName( + parent, QObject::tr("Select binary"), QString(), QObject::tr("Binary") + " (*.exe)"); + } + if (binaryPath.isEmpty()) { + return false; + } + binaryInfo = QFileInfo(binaryPath); + if (extension == "jar") { + arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + } else { + arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + } + + type = FileExecutionTypes::executable; + return true; + } else { + type = FileExecutionTypes::other; + return true; + } +} + +bool OrganizerCore::executeFile(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; +} + void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, diff --git a/src/organizercore.h b/src/organizercore.h index bfb72529..94cfa5ae 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -56,6 +56,13 @@ namespace MOBase { class IPluginGame; } +enum class FileExecutionTypes +{ + executable = 1, + other = 2 +}; + + class OrganizerCore : public QObject, public MOBase::IPluginDiagnose { @@ -140,6 +147,12 @@ public: void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } + static bool getFileExecutionContext( + QWidget* parent, const QFileInfo &targetInfo, + QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); + + bool executeFile(QWidget* parent, const QFileInfo& targetInfo); + void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), const QString &steamAppID = "", -- cgit v1.3.1 From 3c7b232361d01f79a1d48ae3d8200cb6a68bbf32 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 11:55:13 -0400 Subject: - always show statusbar, used by the menu and toolbar to display status tips - reduced margins around central widgets to align it with status bar text, removed bottom margins completely because the statusbar is enough - notifications: now always enabled, dialog shows a special item when empty, just change the tooltip and leave the text alone - added all toolbar actions to menu - non functional in menu for now: tools, help and endorse because they're menus - changed some of the action strings to be the same on both toolbar and menu, added missing status tips --- src/mainwindow.cpp | 46 ++++++------ src/mainwindow.h | 3 + src/mainwindow.ui | 195 ++++++++++++++++++++++++++++++++++++++++++++----- src/problemsdialog.cpp | 24 +++++- src/problemsdialog.h | 6 +- 5 files changed, 226 insertions(+), 48 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0ef9bc80..ff62e05e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -242,7 +242,6 @@ MainWindow::MainWindow(QSettings &initSettings m_RefreshProgress->setVisible(false); statusBar()->addWidget(m_RefreshProgress, 1000); statusBar()->clearMessage(); - statusBar()->hide(); updateProblemsButton(); @@ -674,8 +673,6 @@ void MainWindow::updateProblemsButton() { size_t numProblems = checkForProblems(); if (numProblems > 0) { - ui->actionNotifications->setEnabled(true); - ui->actionNotifications->setIconText(tr("Notifications")); ui->actionNotifications->setToolTip(tr("There are notifications to read")); QPixmap mergedIcon = QPixmap(":/MO/gui/warning").scaled(64, 64); @@ -686,8 +683,6 @@ void MainWindow::updateProblemsButton() } ui->actionNotifications->setIcon(QIcon(mergedIcon)); } else { - ui->actionNotifications->setEnabled(false); - ui->actionNotifications->setIconText(tr("No Notifications")); ui->actionNotifications->setToolTip(tr("There are no notifications")); ui->actionNotifications->setIcon(QIcon(":/MO/gui/warning")); } @@ -975,6 +970,13 @@ void MainWindow::showEvent(QShowEvent *event) void MainWindow::closeEvent(QCloseEvent* event) +{ + if (!exit()) { + event->ignore(); + } +} + +bool MainWindow::exit() { m_closing = true; @@ -982,8 +984,7 @@ void MainWindow::closeEvent(QCloseEvent* event) 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) { - event->ignore(); - return; + return false; } else { m_OrganizerCore.downloadManager()->pauseAll(); } @@ -996,12 +997,12 @@ void MainWindow::closeEvent(QCloseEvent* event) { m_OrganizerCore.waitForApplication(injected_process_still_running); if (!m_closing) { // if operation cancelled - event->ignore(); - return; + return false; } } setCursor(Qt::WaitCursor); + return true; } void MainWindow::cleanup() @@ -2292,11 +2293,9 @@ void MainWindow::refresher_progress(int percent) { if (percent == 100) { m_RefreshProgress->setVisible(false); - statusBar()->hide(); this->setEnabled(true); } else if (!m_RefreshProgress->isVisible()) { this->setEnabled(false); - statusBar()->show(); m_RefreshProgress->setVisible(true); m_RefreshProgress->setRange(0, 100); m_RefreshProgress->setValue(percent); @@ -2309,7 +2308,6 @@ void MainWindow::directory_refreshed() // now refreshDataTreeKeepExpandedNodes(); updateProblemsButton(); - statusBar()->hide(); } void MainWindow::esplist_changed() @@ -2930,7 +2928,6 @@ void MainWindow::untrack_clicked() void MainWindow::validationFailed(const QString &error) { qDebug("Nexus API validation failed: %s", qUtf8Printable(error)); - statusBar()->hide(); } void MainWindow::windowTutorialFinished(const QString &windowName) @@ -4115,7 +4112,6 @@ void MainWindow::checkModsForUpdates() QString apiKey; if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); - statusBar()->show(); NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); } else { qWarning("You are not currently authenticated with Nexus. Please do so under Settings -> Nexus."); @@ -5391,12 +5387,15 @@ void MainWindow::on_conflictsCheckBox_toggled(bool) refreshDataTreeKeepExpandedNodes(); } - void MainWindow::on_actionUpdate_triggered() { m_OrganizerCore.startMOUpdate(); } +void MainWindow::on_actionExit_triggered() +{ + exit(); +} void MainWindow::actionEndorseMO() { @@ -5974,16 +5973,15 @@ void MainWindow::on_actionNotifications_triggered() { updateProblemsButton(); ProblemsDialog problems(m_PluginContainer.plugins(), this); - if (problems.hasProblems()) { - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(problems.objectName()); - if (settings.contains(key)) { - problems.restoreGeometry(settings.value(key).toByteArray()); - } - problems.exec(); - settings.setValue(key, problems.saveGeometry()); - updateProblemsButton(); + + QSettings &settings = m_OrganizerCore.settings().directInterface(); + QString key = QString("geometry/%1").arg(problems.objectName()); + if (settings.contains(key)) { + problems.restoreGeometry(settings.value(key).toByteArray()); } + problems.exec(); + settings.setValue(key, problems.saveGeometry()); + updateProblemsButton(); } void MainWindow::on_actionChange_Game_triggered() diff --git a/src/mainwindow.h b/src/mainwindow.h index b4ad0bdb..1f0dd5ff 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -153,6 +153,8 @@ public: void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); + bool exit(); + virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); @@ -626,6 +628,7 @@ private slots: // ui slots void on_actionNotifications_triggered(); void on_actionSettings_triggered(); void on_actionUpdate_triggered(); + void on_actionExit_triggered(); void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_clearFiltersButton_clicked(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d876b54a..84d3c9e4 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -31,6 +31,18 @@ + + 6 + + + 6 + + + 6 + + + 0 + @@ -1381,13 +1393,67 @@ p, li { white-space: pre-wrap; } + + + + 0 + 0 + 926 + 21 + + + + + &File + + + + + + + + + + &Tools + + + + + + + + + &Help + + + + + + + + &View + + + + + + &Edit + + + + + + + + + :/MO/gui/resources/system-installer.png:/MO/gui/resources/system-installer.png - Install Mod + Install &Mod... Install &Mod @@ -1395,6 +1461,9 @@ p, li { white-space: pre-wrap; } Install a new mod from an archive + + Install a new mod from an archive + Ctrl+M @@ -1405,13 +1474,16 @@ p, li { white-space: pre-wrap; } :/MO/gui/profiles:/MO/gui/profiles - Profiles + &Profiles... &Profiles - Configure Profiles + Configure profiles + + + Configure profiles Ctrl+P @@ -1423,7 +1495,7 @@ p, li { white-space: pre-wrap; } :/MO/gui/icon_executable:/MO/gui/icon_executable - Executables + &Executables... &Executables @@ -1431,6 +1503,9 @@ p, li { white-space: pre-wrap; } Configure the executables that can be started through Mod Organizer + + Configure the executables that can be started through Mod Organizer + Ctrl+E @@ -1441,7 +1516,7 @@ p, li { white-space: pre-wrap; } :/MO/gui/plugins:/MO/gui/plugins - Tools + &Tools &Tools @@ -1459,7 +1534,7 @@ p, li { white-space: pre-wrap; } :/MO/gui/settings:/MO/gui/settings - Settings + &Settings... &Settings @@ -1467,6 +1542,9 @@ p, li { white-space: pre-wrap; } Configure settings and workarounds + + Configure settings and workarounds + Ctrl+S @@ -1477,10 +1555,16 @@ p, li { white-space: pre-wrap; } :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png - Nexus + Visit &Nexus + + + Visit &Nexus - Search nexus network for more mods + Visit the Nexus website in your browser for more mods + + + Visit the Nexus website in your browser for more mods Ctrl+N @@ -1495,25 +1579,34 @@ p, li { white-space: pre-wrap; } :/MO/gui/update:/MO/gui/update - Update + &Update Mod Organizer + + + &Update Mod Organizer Mod Organizer is up-to-date + + Mod Organizer is up-to-date + - - false - :/MO/gui/warning:/MO/gui/warning - No Notifications + Notifications... + + + Open the notifications dialog + + + Open the notifications dialog - This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. @@ -1522,7 +1615,10 @@ p, li { white-space: pre-wrap; } :/MO/gui/help:/MO/gui/help - Help + &Help + + + &Help Help @@ -1537,15 +1633,30 @@ p, li { white-space: pre-wrap; } :/MO/gui/icon_favorite:/MO/gui/icon_favorite - Endorse MO + &Endorse ModOrganizer + + + &Endorse ModOrganizer Endorse Mod Organizer + + Endorse Mod Organizer + - Copy Log to Clipboard + Copy &Log + + + Copy &Log + + + Copy log to clipboard + + + Copy log to clipboard @@ -1554,11 +1665,59 @@ p, li { white-space: pre-wrap; } :/MO/gui/instance_switch:/MO/gui/instance_switch - Change Game + &Change Game... + + + &Change Game Open the Instance selection dialog to manage a different Game + + Open the Instance selection dialog to manage a different Game + + + + + E&xit + + + E&xit + + + Exits Mod Organizer + + + Exits Mod Organizer + + + + + false + + + + :/MO/gui/plugins:/MO/gui/plugins + + + &Tools + + + + + false + + + &Help menu + + + + + false + + + Endorse Mod Organizer + diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 56109d34..1e8e800f 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -11,8 +11,9 @@ using namespace MOBase; -ProblemsDialog::ProblemsDialog(std::vector pluginObjects, QWidget *parent) - : QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginObjects(pluginObjects) +ProblemsDialog::ProblemsDialog(std::vector pluginObjects, QWidget *parent) : + QDialog(parent), ui(new Ui::ProblemsDialog), m_PluginObjects(pluginObjects), + m_hasProblems(false) { ui->setupUi(this); @@ -30,7 +31,9 @@ ProblemsDialog::~ProblemsDialog() void ProblemsDialog::runDiagnosis() { + m_hasProblems = false; ui->problemsWidget->clear(); + for(QObject *pluginObj : m_PluginObjects) { IPlugin *plugin = qobject_cast(pluginObj); if (plugin != nullptr && !plugin->isActive()) @@ -47,6 +50,7 @@ void ProblemsDialog::runDiagnosis() newItem->setData(0, Qt::UserRole, diagnose->fullDescription(key)); ui->problemsWidget->addTopLevelItem(newItem); + m_hasProblems = true; if (diagnose->hasGuidedFix(key)) { newItem->setText(1, tr("Fix")); @@ -60,11 +64,25 @@ void ProblemsDialog::runDiagnosis() } } } + + if (!m_hasProblems) { + auto* item = new QTreeWidgetItem; + + item->setText(0, tr("(There are no notifications)")); + item->setText(1, ""); + item->setData(0, Qt::UserRole, QString()); + + QFont font = item->font(0); + font.setItalic(true); + item->setFont(0, font); + + ui->problemsWidget->addTopLevelItem(item); + } } bool ProblemsDialog::hasProblems() const { - return ui->problemsWidget->topLevelItemCount() != 0; + return m_hasProblems; } void ProblemsDialog::selectionChanged() diff --git a/src/problemsdialog.h b/src/problemsdialog.h index a48a5de1..c211e4f5 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -21,20 +21,20 @@ public: ~ProblemsDialog(); bool hasProblems() const; -private: +private: void runDiagnosis(); private slots: - void selectionChanged(); void urlClicked(const QUrl &url); void startFix(); -private: +private: Ui::ProblemsDialog *ui; std::vector m_PluginObjects; + bool m_hasProblems; }; #endif // PROBLEMSDIALOG_H -- cgit v1.3.1 From 860eb49b45703d939196e92ba6e6d99f54ed3088 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 13:14:21 -0400 Subject: removed actionToToolButton(), which was replacing QAction's in the toolbar with QToolButton's, making it very difficult to have an equivalent in the main menu. QAction's can have a menu, so use that instead. the only place this doesn't work is with the nexus button, which can be replaced by a menu if there are IPluginModPage plugins adding items to it; registerModPage() works fine with the toolbar, but doesn't handle the main menu yet --- src/mainwindow.cpp | 117 ++++++++++++++++++++++------------------------------- src/mainwindow.h | 9 +++-- src/mainwindow.ui | 29 +++---------- 3 files changed, 59 insertions(+), 96 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ff62e05e..462eb5e0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -206,6 +206,7 @@ MainWindow::MainWindow(QSettings &initSettings , m_ContextItem(nullptr) , m_ContextAction(nullptr) , m_ContextRow(-1) + , m_browseModPage(nullptr) , m_CurrentSaveView(nullptr) , m_OrganizerCore(organizerCore) , m_PluginContainer(pluginContainer) @@ -248,17 +249,12 @@ MainWindow::MainWindow(QSettings &initSettings // Setup toolbar QWidget *spacer = new QWidget(ui->toolBar); spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - QWidget *widget = ui->toolBar->widgetForAction(ui->actionTool); - QToolButton *toolBtn = qobject_cast(widget); - if (toolBtn->menu() == nullptr) { - actionToToolButton(ui->actionTool); - } - - actionToToolButton(ui->actionHelp); - createHelpWidget(); + setupActionMenu(ui->actionTool); + setupActionMenu(ui->actionHelp); + setupActionMenu(ui->actionEndorseMO); - actionToToolButton(ui->actionEndorseMO); + createHelpMenu(); createEndorseWidget(); toggleMO2EndorseState(); @@ -611,28 +607,13 @@ static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex return result; } - -void MainWindow::actionToToolButton(QAction *&sourceAction) +void MainWindow::setupActionMenu(QAction* a) { - QToolButton *button = new QToolButton(ui->toolBar); - button->setObjectName(sourceAction->objectName()); - button->setIcon(sourceAction->icon()); - button->setText(sourceAction->text()); - button->setPopupMode(QToolButton::InstantPopup); - button->setToolButtonStyle(ui->toolBar->toolButtonStyle()); - button->setToolTip(sourceAction->toolTip()); - button->setShortcut(sourceAction->shortcut()); - QMenu *buttonMenu = new QMenu(sourceAction->text(), button); - button->setMenu(buttonMenu); - QAction *newAction = ui->toolBar->insertWidget(sourceAction, button); - newAction->setObjectName(sourceAction->objectName()); - newAction->setIcon(sourceAction->icon()); - newAction->setText(sourceAction->text()); - newAction->setToolTip(sourceAction->toolTip()); - newAction->setShortcut(sourceAction->shortcut()); - ui->toolBar->removeAction(sourceAction); - sourceAction->deleteLater(); - sourceAction = newAction; + a->setMenu(new QMenu(this)); + + auto* w = ui->toolBar->widgetForAction(a); + if (auto* tb=dynamic_cast(w)) + tb->setPopupMode(QToolButton::InstantPopup); } void MainWindow::updateToolBar() @@ -760,32 +741,34 @@ void MainWindow::createEndorseWidget() } -void MainWindow::createHelpWidget() +void MainWindow::createHelpMenu() { - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionHelp)); - QMenu *buttonMenu = toolBtn->menu(); - if (buttonMenu == nullptr) { + auto* menu = ui->actionHelp->menu(); + if (!menu) { + // this happens on startup because languageChanged() (which calls this) is + // called before the menus are actually created return; } - buttonMenu->clear(); - QAction *helpAction = new QAction(tr("Help on UI"), buttonMenu); + menu->clear(); + + QAction *helpAction = new QAction(tr("Help on UI"), menu); connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); - buttonMenu->addAction(helpAction); + menu->addAction(helpAction); - QAction *wikiAction = new QAction(tr("Documentation"), buttonMenu); + QAction *wikiAction = new QAction(tr("Documentation"), menu); connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); - buttonMenu->addAction(wikiAction); + menu->addAction(wikiAction); - QAction *discordAction = new QAction(tr("Chat on Discord"), buttonMenu); + QAction *discordAction = new QAction(tr("Chat on Discord"), menu); connect(discordAction, SIGNAL(triggered()), this, SLOT(discordTriggered())); - buttonMenu->addAction(discordAction); + menu->addAction(discordAction); - QAction *issueAction = new QAction(tr("Report Issue"), buttonMenu); + QAction *issueAction = new QAction(tr("Report Issue"), menu); connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); - buttonMenu->addAction(issueAction); + menu->addAction(issueAction); - QMenu *tutorialMenu = new QMenu(tr("Tutorials"), buttonMenu); + QMenu *tutorialMenu = new QMenu(tr("Tutorials"), menu); typedef std::vector > ActionList; @@ -823,9 +806,9 @@ void MainWindow::createHelpWidget() tutorialMenu->addAction(iter->second); } - buttonMenu->addMenu(tutorialMenu); - buttonMenu->addAction(tr("About"), this, SLOT(about())); - buttonMenu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); + menu->addMenu(tutorialMenu); + menu->addAction(tr("About"), this, SLOT(about())); + menu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); } void MainWindow::modFilterActive(bool filterActive) @@ -1128,21 +1111,20 @@ void MainWindow::modPagePluginInvoke() void MainWindow::registerPluginTool(IPluginTool *tool, QString name, QMenu *menu) { + if (!menu) { + menu = ui->actionTool->menu(); + } + if (name.isEmpty()) name = tool->displayName(); - QAction *action = new QAction(tool->icon(), name, ui->toolBar); + QAction *action = new QAction(tool->icon(), name, menu); action->setToolTip(tool->tooltip()); tool->setParentWidget(this); action->setData(qVariantFromValue((QObject*)tool)); connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); - if (menu == nullptr) { - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionTool)); - toolBtn->menu()->addAction(action); - } else { - menu->addAction(action); - } + menu->addAction(action); } void MainWindow::registerPluginTools(std::vector toolPlugins) @@ -1176,8 +1158,7 @@ void MainWindow::registerPluginTools(std::vector toolPlugins) for (auto info : submenuMap[submenuKey]) { registerPluginTool(info.second, info.first, submenu); } - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionTool)); - toolBtn->menu()->addMenu(submenu); + ui->actionTool->menu()->addMenu(submenu); } else { registerPluginTool(submenuMap[submenuKey].front().second); @@ -1188,25 +1169,23 @@ void MainWindow::registerPluginTools(std::vector toolPlugins) void MainWindow::registerModPage(IPluginModPage *modPage) { // turn the browser action into a drop-down menu if necessary - if (ui->actionNexus->menu() == nullptr) { - QAction *nexusAction = ui->actionNexus; - // TODO: use a different icon for nexus! - ui->actionNexus = new QAction(nexusAction->icon(), tr("Browse Mod Page"), ui->toolBar); - ui->toolBar->insertAction(nexusAction, ui->actionNexus); - ui->toolBar->removeAction(nexusAction); - actionToToolButton(ui->actionNexus); + if (!m_browseModPage) { + m_browseModPage = new QAction(ui->actionNexus->icon(), tr("Browse Mod Page"), this); + setupActionMenu(m_browseModPage); - QToolButton *browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - browserBtn->menu()->addAction(nexusAction); + m_browseModPage->menu()->addAction(ui->actionNexus); + + ui->toolBar->insertAction(ui->actionNexus, m_browseModPage); + ui->toolBar->removeAction(ui->actionNexus); } - QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); + QAction *action = new QAction(modPage->icon(), modPage->displayName(), this); modPage->setParentWidget(this); action->setData(qVariantFromValue(reinterpret_cast(modPage))); connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - toolBtn->menu()->addAction(action); + + m_browseModPage->menu()->addAction(action); } @@ -5096,7 +5075,7 @@ void MainWindow::languageChange(const QString &newLanguage) ui->profileBox->setItemText(0, QObject::tr("")); - createHelpWidget(); + createHelpMenu(); updateDownloadView(); updateProblemsButton(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 1f0dd5ff..7281aab7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -206,7 +206,9 @@ private: void cleanup(); - void actionToToolButton(QAction *&sourceAction); + void setupActionMenu(QAction* a); + void createHelpMenu(); + void createEndorseWidget(); void updateToolBar(); void activateSelectedProfile(); @@ -255,9 +257,6 @@ private: // remove invalid category-references from mods void fixCategories(); - void createEndorseWidget(); - void createHelpWidget(); - bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); size_t checkForProblems(); @@ -343,6 +342,8 @@ private: QTreeWidgetItem *m_ContextItem; QAction *m_ContextAction; + QAction* m_browseModPage; + CategoryFactory &m_CategoryFactory; bool m_LoginAttempted; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 84d3c9e4..a3eb8504 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1418,14 +1418,14 @@ p, li { white-space: pre-wrap; } - + &Help - + @@ -1621,7 +1621,10 @@ p, li { white-space: pre-wrap; } &Help - Help + Show help options + + + Show help options Ctrl+H @@ -1691,26 +1694,6 @@ p, li { white-space: pre-wrap; } Exits Mod Organizer - - - false - - - - :/MO/gui/plugins:/MO/gui/plugins - - - &Tools - - - - - false - - - &Help menu - - false -- cgit v1.3.1 From 746e9c08e68ed7e44e6ef4e29b250c73b9c21440 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 14:22:24 -0400 Subject: moved notification menu item to tools, was the only thing in view fixed endorse action to also work with the menu fixed changing endorsement integration setting not changing visibility of actions --- src/mainwindow.cpp | 32 +++++++++++++++++--------------- src/mainwindow.h | 2 +- src/mainwindow.ui | 20 ++++---------------- 3 files changed, 22 insertions(+), 32 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 462eb5e0..8e0d2624 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -255,7 +255,7 @@ MainWindow::MainWindow(QSettings &initSettings setupActionMenu(ui->actionEndorseMO); createHelpMenu(); - createEndorseWidget(); + createEndorseMenu(); toggleMO2EndorseState(); @@ -722,22 +722,23 @@ void MainWindow::about() } -void MainWindow::createEndorseWidget() +void MainWindow::createEndorseMenu() { - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionEndorseMO)); - QMenu *buttonMenu = toolBtn->menu(); - if (buttonMenu == nullptr) { + auto* menu = ui->actionEndorseMO->menu(); + if (!menu) { + // shouldn't happen return; } - buttonMenu->clear(); - QAction *endorseAction = new QAction(tr("Endorse"), buttonMenu); + menu->clear(); + + QAction *endorseAction = new QAction(tr("Endorse"), menu); connect(endorseAction, SIGNAL(triggered()), this, SLOT(actionEndorseMO())); - buttonMenu->addAction(endorseAction); + menu->addAction(endorseAction); - QAction *wontEndorseAction = new QAction(tr("Won't Endorse"), buttonMenu); + QAction *wontEndorseAction = new QAction(tr("Won't Endorse"), menu); connect(wontEndorseAction, SIGNAL(triggered()), this, SLOT(actionWontEndorseMO())); - buttonMenu->addAction(wontEndorseAction); + menu->addAction(wontEndorseAction); } @@ -5021,6 +5022,8 @@ void MainWindow::on_actionSettings_triggered() m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType(), settings.executablesBlacklist()); m_OrganizerCore.cycleDiagnostics(); + + toggleMO2EndorseState(); } @@ -5473,20 +5476,19 @@ void MainWindow::modUpdateCheck(std::multimap IDs) void MainWindow::toggleMO2EndorseState() { - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionEndorseMO)); if (Settings::instance().endorsementIntegration()) { ui->actionEndorseMO->setVisible(true); if (Settings::instance().directInterface().contains("endorse_state")) { - ui->actionEndorseMO->setEnabled(false); + ui->actionEndorseMO->menu()->setEnabled(false); if (Settings::instance().directInterface().value("endorse_state").toString() == "Endorsed") { ui->actionEndorseMO->setToolTip(tr("Thank you for endorsing MO2! :)")); - toolBtn->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!")); - toolBtn->setToolTip(tr("Please reconsider endorsing MO2 on Nexus!")); + ui->actionEndorseMO->setStatusTip(tr("Please reconsider endorsing MO2 on Nexus!")); } } else { - ui->actionEndorseMO->setEnabled(true); + ui->actionEndorseMO->menu()->setEnabled(true); } } else ui->actionEndorseMO->setVisible(false); diff --git a/src/mainwindow.h b/src/mainwindow.h index 7281aab7..96660aeb 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -208,7 +208,7 @@ private: void setupActionMenu(QAction* a); void createHelpMenu(); - void createEndorseWidget(); + void createEndorseMenu(); void updateToolBar(); void activateSelectedProfile(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index a3eb8504..03cb9ab7 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1417,8 +1417,11 @@ p, li { white-space: pre-wrap; } &Tools + + + @@ -1427,13 +1430,7 @@ p, li { white-space: pre-wrap; } - - - - - &View - - + @@ -1443,7 +1440,6 @@ p, li { white-space: pre-wrap; } - @@ -1694,14 +1690,6 @@ p, li { white-space: pre-wrap; } Exits Mod Organizer - - - false - - - Endorse Mod Organizer - - -- cgit v1.3.1 From 41ef9813dd7b3b7587afe8dfe3a16f2711200edb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:22:11 -0400 Subject: moved links to a new dedicated toolbar --- src/mainwindow.cpp | 56 ++++++++++++++++++++++++++++++------------------------ src/mainwindow.h | 4 +--- src/mainwindow.ui | 43 +++++++++++++++++++++++------------------ 3 files changed, 57 insertions(+), 46 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8e0d2624..09ecc3f9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -247,8 +247,6 @@ MainWindow::MainWindow(QSettings &initSettings updateProblemsButton(); // Setup toolbar - QWidget *spacer = new QWidget(ui->toolBar); - spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); setupActionMenu(ui->actionTool); setupActionMenu(ui->actionHelp); @@ -259,16 +257,6 @@ MainWindow::MainWindow(QSettings &initSettings toggleMO2EndorseState(); - for (QAction *action : ui->toolBar->actions()) { - if (action->isSeparator()) { - // insert spacers - ui->toolBar->insertWidget(action, spacer); - m_Sep = action; - // m_Sep would only use the last separator anyway, and we only have the one anyway? - break; - } - } - TaskProgressManager::instance().tryCreateTaskbar(); // set up mod list @@ -407,7 +395,7 @@ MainWindow::MainWindow(QSettings &initSettings 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->linksToolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(linksToolBar_customContextMenuRequested(QPoint))); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -618,29 +606,36 @@ void MainWindow::setupActionMenu(QAction* a) void MainWindow::updateToolBar() { - for (QAction *action : ui->toolBar->actions()) { - if (action->objectName().startsWith("custom__")) { - ui->toolBar->removeAction(action); - action->deleteLater(); - } + for (auto* a : ui->linksToolBar->actions()) { + ui->linksToolBar->removeAction(a); + a->deleteLater(); } + bool hasLinks = false; + std::vector::iterator begin, end; m_OrganizerCore.executablesList()->getExecutables(begin, end); + for (auto iter = begin; iter != end; ++iter) { if (iter->isShownOnToolbar()) { + hasLinks = true; + QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), iter->m_Title, ui->toolBar); + exeAction->setObjectName(QString("custom__") + iter->m_Title); if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { qDebug("failed to connect trigger?"); } - ui->toolBar->insertAction(m_Sep, exeAction); + + ui->linksToolBar->addAction(exeAction); } } -} + // don't show the toolbar if there are no links + ui->linksToolBar->setVisible(hasLinks); +} void MainWindow::scheduleUpdateButton() { @@ -649,7 +644,6 @@ void MainWindow::scheduleUpdateButton() } } - void MainWindow::updateProblemsButton() { size_t numProblems = checkForProblems(); @@ -1834,6 +1828,10 @@ void MainWindow::readSettings() restoreGeometry(settings.value("window_geometry").toByteArray()); } + if (settings.contains("window_state")) { + restoreState(settings.value("window_state").toByteArray()); + } + if (settings.contains("window_split")) { ui->splitter->restoreState(settings.value("window_split").toByteArray()); } @@ -1910,6 +1908,7 @@ void MainWindow::storeSettings(QSettings &settings) { if (settings.value("reset_geometry", false).toBool()) { settings.remove("window_geometry"); + settings.remove("window_state"); settings.remove("window_split"); settings.remove("log_split"); settings.remove("filters_visible"); @@ -1918,6 +1917,7 @@ void MainWindow::storeSettings(QSettings &settings) { settings.remove("reset_geometry"); } else { settings.setValue("window_geometry", saveGeometry()); + settings.setValue("window_state", saveState()); settings.setValue("window_split", ui->splitter->saveState()); settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); @@ -6062,17 +6062,23 @@ void MainWindow::removeFromToolbar() } -void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) +void MainWindow::linksToolBar_customContextMenuRequested(const QPoint &point) { - QAction *action = ui->toolBar->actionAt(point); + QAction *action = ui->linksToolBar->actionAt(point); + if (action != nullptr) { if (action->objectName().startsWith("custom_")) { m_ContextAction = action; QMenu menu; - menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); - menu.exec(ui->toolBar->mapToGlobal(point)); + menu.addAction(tr("Remove '%1' from the toolbar").arg(action->text()), this, SLOT(removeFromToolbar())); + menu.exec(ui->linksToolBar->mapToGlobal(point)); + return; } } + + // did not click a link button, show the default context menu + auto* m = createPopupMenu(); + m->exec(ui->linksToolBar->mapToGlobal(point)); } void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) diff --git a/src/mainwindow.h b/src/mainwindow.h index 96660aeb..8e50fa2e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -316,8 +316,6 @@ private: Ui::MainWindow *ui; - QAction *m_Sep; // Executable Shortcuts are added after this. Non owning. - bool m_WasVisible; MOBase::TutorialControl m_Tutorial; @@ -594,7 +592,7 @@ private slots: */ void allowListResize(); - void toolBar_customContextMenuRequested(const QPoint &point); + void linksToolBar_customContextMenuRequested(const QPoint &point); void removeFromToolbar(); void overwriteClosed(int); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 03cb9ab7..59d30a09 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1349,17 +1349,8 @@ p, li { white-space: pre-wrap; } - - true - - - Qt::CustomContextMenu - - Tool Bar - - - false + Main ToolBar @@ -1367,12 +1358,6 @@ p, li { white-space: pre-wrap; } 36 - - Qt::ToolButtonIconOnly - - - false - TopToolBarArea @@ -1380,9 +1365,11 @@ p, li { white-space: pre-wrap; } false - - + + + + @@ -1443,6 +1430,26 @@ p, li { white-space: pre-wrap; } + + + Qt::CustomContextMenu + + + Links ToolBar + + + + 42 + 36 + + + + TopToolBarArea + + + false + + -- cgit v1.3.1 From fab357ef4d1b65e2737fa2db93a42ede38653a59 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 15:57:52 -0400 Subject: toolbar size and button style are now configurable and remembered --- src/mainwindow.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/mainwindow.h | 3 +++ 2 files changed, 74 insertions(+) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 09ecc3f9..642b0c99 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -189,6 +189,9 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +const QSize SmallToolbarSize(24, 24); +const QSize LargeToolbarSize(42, 36); + MainWindow::MainWindow(QSettings &initSettings , OrganizerCore &organizerCore @@ -637,6 +640,61 @@ void MainWindow::updateToolBar() ui->linksToolBar->setVisible(hasLinks); } +QMenu* MainWindow::createPopupMenu() +{ + auto* m = QMainWindow::createPopupMenu(); + + m->addSeparator(); + + auto* a = new QAction(tr("Small Icons"), m); + connect(a, &QAction::triggered, [&]{ setToolbarSize(SmallToolbarSize); }); + a->setCheckable(true); + a->setChecked(ui->toolBar->iconSize() == SmallToolbarSize); + m->addAction(a); + + a = new QAction(tr("Large Icons"), m); + connect(a, &QAction::triggered, [&]{ setToolbarSize(LargeToolbarSize); }); + a->setCheckable(true); + a->setChecked(ui->toolBar->iconSize() == LargeToolbarSize); + m->addAction(a); + + m->addSeparator(); + + a = new QAction(tr("Icons only"), m); + connect(a, &QAction::triggered, [&]{ setToolbarButtonStyle(Qt::ToolButtonIconOnly); }); + a->setCheckable(true); + a->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonIconOnly); + m->addAction(a); + + a = new QAction(tr("Text only"), m); + connect(a, &QAction::triggered, [&]{ setToolbarButtonStyle(Qt::ToolButtonTextOnly); }); + a->setCheckable(true); + a->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextOnly); + m->addAction(a); + + a = new QAction(tr("Text and Icons"), m); + connect(a, &QAction::triggered, [&]{ setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon); }); + a->setCheckable(true); + a->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); + m->addAction(a); + + return m; +} + +void MainWindow::setToolbarSize(const QSize& s) +{ + for (auto* tb : findChildren()) { + tb->setIconSize(s); + } +} + +void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s) +{ + for (auto* tb : findChildren()) { + tb->setToolButtonStyle(s); + } +} + void MainWindow::scheduleUpdateButton() { if (!m_UpdateProblemsTimer.isActive()) { @@ -1832,6 +1890,15 @@ void MainWindow::readSettings() restoreState(settings.value("window_state").toByteArray()); } + if (settings.contains("toolbar_size")) { + setToolbarSize(settings.value("toolbar_size").toSize()); + } + + if (settings.contains("toolbar_button_style")) { + setToolbarButtonStyle(static_cast( + settings.value("toolbar_button_style").toInt())); + } + if (settings.contains("window_split")) { ui->splitter->restoreState(settings.value("window_split").toByteArray()); } @@ -1909,6 +1976,8 @@ void MainWindow::storeSettings(QSettings &settings) { 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("window_split"); settings.remove("log_split"); settings.remove("filters_visible"); @@ -1918,6 +1987,8 @@ void MainWindow::storeSettings(QSettings &settings) { } else { settings.setValue("window_geometry", saveGeometry()); settings.setValue("window_state", saveState()); + settings.setValue("toolbar_size", ui->toolBar->iconSize()); + settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); settings.setValue("window_split", ui->splitter->saveState()); settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); diff --git a/src/mainwindow.h b/src/mainwindow.h index 8e50fa2e..fa5912b8 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -211,6 +211,9 @@ private: void createEndorseMenu(); void updateToolBar(); + void setToolbarSize(const QSize& s); + void setToolbarButtonStyle(Qt::ToolButtonStyle s); + QMenu* createPopupMenu() override; void activateSelectedProfile(); void setExecutableIndex(int index); -- cgit v1.3.1 From e6706c7a5b82e68443de6ca262c77e1543986b1e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 16:28:09 -0400 Subject: the toolbar menu is now in the ui file instead of being created by hand it's also shared between the main menu and the context menu --- src/mainwindow.cpp | 82 +++++++++++++++++++++++++++++------------------ src/mainwindow.h | 10 ++++++ src/mainwindow.ui | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 153 insertions(+), 32 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 642b0c99..3da571f9 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -399,6 +399,7 @@ 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->linksToolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(linksToolBar_customContextMenuRequested(QPoint))); + connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ toolbarMenu_aboutToShow(); }); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); @@ -640,45 +641,66 @@ void MainWindow::updateToolBar() ui->linksToolBar->setVisible(hasLinks); } -QMenu* MainWindow::createPopupMenu() +void MainWindow::toolbarMenu_aboutToShow() { - auto* m = QMainWindow::createPopupMenu(); + // 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); - m->addSeparator(); + ui->actionToolBarMainToggle->setChecked(ui->toolBar->isVisible()); + ui->actionToolBarLinksToggle->setChecked(ui->linksToolBar->isVisible()); - auto* a = new QAction(tr("Small Icons"), m); - connect(a, &QAction::triggered, [&]{ setToolbarSize(SmallToolbarSize); }); - a->setCheckable(true); - a->setChecked(ui->toolBar->iconSize() == SmallToolbarSize); - m->addAction(a); + ui->actionToolBarLargeIcons->setChecked(ui->toolBar->iconSize() == LargeToolbarSize); + ui->actionToolBarSmallIcons->setChecked(ui->toolBar->iconSize() == SmallToolbarSize); - a = new QAction(tr("Large Icons"), m); - connect(a, &QAction::triggered, [&]{ setToolbarSize(LargeToolbarSize); }); - a->setCheckable(true); - a->setChecked(ui->toolBar->iconSize() == LargeToolbarSize); - m->addAction(a); + ui->actionToolBarIconsOnly->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonIconOnly); + ui->actionToolBarTextOnly->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextOnly); + ui->actionToolBarIconsAndText->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); +} - m->addSeparator(); +QMenu* MainWindow::createPopupMenu() +{ + return ui->menuToolbars; +} - a = new QAction(tr("Icons only"), m); - connect(a, &QAction::triggered, [&]{ setToolbarButtonStyle(Qt::ToolButtonIconOnly); }); - a->setCheckable(true); - a->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonIconOnly); - m->addAction(a); +void MainWindow::on_actionToolBarMainToggle_triggered() +{ + ui->toolBar->setVisible(!ui->toolBar->isVisible()); +} - a = new QAction(tr("Text only"), m); - connect(a, &QAction::triggered, [&]{ setToolbarButtonStyle(Qt::ToolButtonTextOnly); }); - a->setCheckable(true); - a->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextOnly); - m->addAction(a); +void MainWindow::on_actionToolBarLinksToggle_triggered() +{ + ui->linksToolBar->setVisible(!ui->linksToolBar->isVisible()); +} - a = new QAction(tr("Text and Icons"), m); - connect(a, &QAction::triggered, [&]{ setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon); }); - a->setCheckable(true); - a->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextUnderIcon); - m->addAction(a); +void MainWindow::on_actionToolBarLargeIcons_triggered() +{ + setToolbarSize(LargeToolbarSize); +} + +void MainWindow::on_actionToolBarSmallIcons_triggered() +{ + setToolbarSize(SmallToolbarSize); +} - return m; +void MainWindow::on_actionToolBarIconsOnly_triggered() +{ + setToolbarButtonStyle(Qt::ToolButtonIconOnly); +} + +void MainWindow::on_actionToolBarTextOnly_triggered() +{ + setToolbarButtonStyle(Qt::ToolButtonTextOnly); +} + +void MainWindow::on_actionToolBarIconsAndText_triggered() +{ + setToolbarButtonStyle(Qt::ToolButtonTextUnderIcon); } void MainWindow::setToolbarSize(const QSize& s) diff --git a/src/mainwindow.h b/src/mainwindow.h index fa5912b8..18aa525e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -213,6 +213,8 @@ private: void updateToolBar(); void setToolbarSize(const QSize& s); void setToolbarButtonStyle(Qt::ToolButtonStyle s); + void toolbarMenu_aboutToShow(); + QMenu* createPopupMenu() override; void activateSelectedProfile(); @@ -631,6 +633,14 @@ private slots: // ui slots void on_actionSettings_triggered(); void on_actionUpdate_triggered(); void on_actionExit_triggered(); + void on_actionToolBarMainToggle_triggered(); + void on_actionToolBarLinksToggle_triggered(); + void on_actionToolBarLargeIcons_triggered(); + void on_actionToolBarSmallIcons_triggered(); + void on_actionToolBarIconsOnly_triggered(); + void on_actionToolBarTextOnly_triggered(); + void on_actionToolBarIconsAndText_triggered(); + void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_clearFiltersButton_clicked(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 59d30a09..a4bd2888 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1358,6 +1358,9 @@ p, li { white-space: pre-wrap; } 36 + + Qt::ToolButtonTextUnderIcon + TopToolBarArea @@ -1405,10 +1408,8 @@ p, li { white-space: pre-wrap; } - - @@ -1425,8 +1426,30 @@ p, li { white-space: pre-wrap; } + + + View + + + + Toolbars + + + + + + + + + + + + + + + @@ -1697,6 +1720,72 @@ p, li { white-space: pre-wrap; } Exits Mod Organizer + + + Toolbar Size + + + + + Toolbar Buttons + + + + + true + + + &Main + + + + + true + + + &Links + + + + + true + + + &Small Icons + + + + + true + + + Lar&ge Icons + + + + + true + + + &Icons Only + + + + + true + + + &Text Only + + + + + true + + + I&cons and Text + + -- cgit v1.3.1 From 71a9f085138bf8f9dc75a27abd9b54cdb89a0876 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 16:45:58 -0400 Subject: added medium toolbar icon size --- src/mainwindow.cpp | 17 ++++++++++++----- src/mainwindow.h | 3 ++- src/mainwindow.ui | 9 ++++++--- 3 files changed, 20 insertions(+), 9 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3da571f9..753b6a5d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -190,6 +190,7 @@ using namespace MOBase; using namespace MOShared; const QSize SmallToolbarSize(24, 24); +const QSize MediumToolbarSize(32, 32); const QSize LargeToolbarSize(42, 36); @@ -655,8 +656,9 @@ void MainWindow::toolbarMenu_aboutToShow() ui->actionToolBarMainToggle->setChecked(ui->toolBar->isVisible()); ui->actionToolBarLinksToggle->setChecked(ui->linksToolBar->isVisible()); - ui->actionToolBarLargeIcons->setChecked(ui->toolBar->iconSize() == LargeToolbarSize); ui->actionToolBarSmallIcons->setChecked(ui->toolBar->iconSize() == SmallToolbarSize); + ui->actionToolBarMediumIcons->setChecked(ui->toolBar->iconSize() == MediumToolbarSize); + ui->actionToolBarLargeIcons->setChecked(ui->toolBar->iconSize() == LargeToolbarSize); ui->actionToolBarIconsOnly->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonIconOnly); ui->actionToolBarTextOnly->setChecked(ui->toolBar->toolButtonStyle() == Qt::ToolButtonTextOnly); @@ -678,14 +680,19 @@ void MainWindow::on_actionToolBarLinksToggle_triggered() ui->linksToolBar->setVisible(!ui->linksToolBar->isVisible()); } -void MainWindow::on_actionToolBarLargeIcons_triggered() +void MainWindow::on_actionToolBarSmallIcons_triggered() { - setToolbarSize(LargeToolbarSize); + setToolbarSize(SmallToolbarSize); } -void MainWindow::on_actionToolBarSmallIcons_triggered() +void MainWindow::on_actionToolBarMediumIcons_triggered() { - setToolbarSize(SmallToolbarSize); + setToolbarSize(MediumToolbarSize); +} + +void MainWindow::on_actionToolBarLargeIcons_triggered() +{ + setToolbarSize(LargeToolbarSize); } void MainWindow::on_actionToolBarIconsOnly_triggered() diff --git a/src/mainwindow.h b/src/mainwindow.h index 18aa525e..a0cce858 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -635,8 +635,9 @@ private slots: // ui slots void on_actionExit_triggered(); void on_actionToolBarMainToggle_triggered(); void on_actionToolBarLinksToggle_triggered(); - void on_actionToolBarLargeIcons_triggered(); void on_actionToolBarSmallIcons_triggered(); + void on_actionToolBarMediumIcons_triggered(); + void on_actionToolBarLargeIcons_triggered(); void on_actionToolBarIconsOnly_triggered(); void on_actionToolBarTextOnly_triggered(); void on_actionToolBarIconsAndText_triggered(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index a4bd2888..29a8ac76 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1358,9 +1358,6 @@ p, li { white-space: pre-wrap; } 36 - - Qt::ToolButtonTextUnderIcon - TopToolBarArea @@ -1438,6 +1435,7 @@ p, li { white-space: pre-wrap; } + @@ -1786,6 +1784,11 @@ p, li { white-space: pre-wrap; } I&cons and Text + + + M&edium Icons + + -- cgit v1.3.1 From 97026b4da03dbb9dda460c6046aaa09a23a927bf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 3 Jun 2019 18:30:23 -0400 Subject: added menubar toggle in the context menu removed two unused actions: actionToolbar_Size and actionToolbar_style added ways to make the menu reappear if you hide everything: - show the toolbar popup when right-clicking around the border of the main window - intercept the Alt key and make the main menu visible --- src/mainwindow.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/mainwindow.h | 4 +++- src/mainwindow.ui | 24 +++++++++++++----------- 3 files changed, 56 insertions(+), 12 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 01bb4fc1..1098b529 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -653,6 +653,7 @@ void MainWindow::toolbarMenu_aboutToShow() // 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->actionToolBarLinksToggle->setChecked(ui->linksToolBar->isVisible()); @@ -670,6 +671,11 @@ QMenu* MainWindow::createPopupMenu() return ui->menuToolbars; } +void MainWindow::on_actionMainMenuToggle_triggered() +{ + ui->menuBar->setVisible(!ui->menuBar->isVisible()); +} + void MainWindow::on_actionToolBarMainToggle_triggered() { ui->toolBar->setVisible(!ui->toolBar->isVisible()); @@ -724,6 +730,23 @@ void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s) } } +void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) +{ + // the custom context menu event bubbles up to here if widgets don't actually + // process this, which would show the menu when right-clicking button, labels, + // etc. + // + // only show the context menu when right-clicking on the central widget + // itself, which is basically just the outer edges of the main window + auto* w = childAt(pos); + if (w != ui->centralWidget) { + return; + } + + auto* m = createPopupMenu(); + m->exec(ui->centralWidget->mapToGlobal(pos)); +} + void MainWindow::scheduleUpdateButton() { if (!m_UpdateProblemsTimer.isActive()) { @@ -1928,6 +1951,10 @@ void MainWindow::readSettings() settings.value("toolbar_button_style").toInt())); } + if (settings.contains("menubar_visible")) { + ui->menuBar->setVisible(settings.value("menubar_visible").toBool()); + } + if (settings.contains("window_split")) { ui->splitter->restoreState(settings.value("window_split").toByteArray()); } @@ -2007,6 +2034,7 @@ void MainWindow::storeSettings(QSettings &settings) { 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("log_split"); @@ -2019,6 +2047,7 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("window_state", saveState()); settings.setValue("toolbar_size", ui->toolBar->iconSize()); settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); + settings.setValue("menubar_visible", ui->menuBar->isVisible()); settings.setValue("window_split", ui->splitter->saveState()); settings.setValue("window_monitor", QApplication::desktop()->screenNumber(this)); settings.setValue("log_split", ui->topLevelSplitter->saveState()); @@ -6815,6 +6844,17 @@ void MainWindow::dropEvent(QDropEvent *event) event->accept(); } +void MainWindow::keyPressEvent(QKeyEvent *event) +{ + // if the menubar is hidden, pressing Alt will make it visible + if (event->key() == Qt::Key_Alt) { + if (!ui->menuBar->isVisible()) { + ui->menuBar->setVisible(true); + } + } + + QMainWindow::keyPressEvent(event); +} void MainWindow::on_clickBlankButton_clicked() { diff --git a/src/mainwindow.h b/src/mainwindow.h index a0cce858..09aeb044 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -195,6 +195,7 @@ protected: virtual void resizeEvent(QResizeEvent *event); virtual void dragEnterEvent(QDragEnterEvent *event); virtual void dropEvent(QDropEvent *event); + void keyPressEvent(QKeyEvent *event) override; private slots: void on_actionChange_Game_triggered(); @@ -633,6 +634,7 @@ private slots: // ui slots void on_actionSettings_triggered(); void on_actionUpdate_triggered(); void on_actionExit_triggered(); + void on_actionMainMenuToggle_triggered(); void on_actionToolBarMainToggle_triggered(); void on_actionToolBarLinksToggle_triggered(); void on_actionToolBarSmallIcons_triggered(); @@ -642,7 +644,7 @@ private slots: // ui slots void on_actionToolBarTextOnly_triggered(); void on_actionToolBarIconsAndText_triggered(); - + void on_centralWidget_customContextMenuRequested(const QPoint &pos); void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_clearFiltersButton_clicked(); void on_btnRefreshData_clicked(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 9656cbf2..e25111e1 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -30,6 +30,9 @@ 0 + + Qt::CustomContextMenu + 6 @@ -1432,6 +1435,7 @@ p, li { white-space: pre-wrap; } &Toolbars + @@ -1719,22 +1723,12 @@ p, li { white-space: pre-wrap; } Exits Mod Organizer - - - Toolbar Size - - - - - Toolbar Buttons - - true - &Main + M&ain @@ -1793,6 +1787,14 @@ p, li { white-space: pre-wrap; } M&edium Icons + + + true + + + &Menu + + -- cgit v1.3.1 From 254304217d6cb00cbedf13f8c493dc6c80e11e24 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 3 Jun 2019 19:00:44 -0400 Subject: renamed some of the menus to remove underscores added Run menu with shortcuts, only visible when there are shortcuts to show fixed menubar visibility not being remembered --- src/mainwindow.cpp | 30 +++++++++++++++++++----------- src/mainwindow.h | 6 +++++- src/mainwindow.ui | 22 ++++++++++++++-------- 3 files changed, 38 insertions(+), 20 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1098b529..62971f00 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -201,6 +201,7 @@ MainWindow::MainWindow(QSettings &initSettings : QMainWindow(parent) , ui(new Ui::MainWindow) , m_WasVisible(false) + , m_menuBarVisible(true) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) , m_ModListGroupingProxy(nullptr) @@ -321,7 +322,7 @@ MainWindow::MainWindow(QSettings &initSettings resizeLists(modListAdjusted, pluginListAdjusted); QMenu *linkMenu = new QMenu(this); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar"), this, SLOT(linkToolbar())); + linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); @@ -470,7 +471,7 @@ MainWindow::MainWindow(QSettings &initSettings } refreshExecutablesList(); - updateToolBar(); + updatePinnedExecutables(); for (QAction *action : ui->toolBar->actions()) { // set the name of the widget to the name of the action to allow styling @@ -609,13 +610,15 @@ void MainWindow::setupActionMenu(QAction* a) tb->setPopupMode(QToolButton::InstantPopup); } -void MainWindow::updateToolBar() +void MainWindow::updatePinnedExecutables() { for (auto* a : ui->linksToolBar->actions()) { ui->linksToolBar->removeAction(a); a->deleteLater(); } + ui->menuRun->clear(); + bool hasLinks = false; std::vector::iterator begin, end; @@ -625,21 +628,23 @@ void MainWindow::updateToolBar() if (iter->isShownOnToolbar()) { hasLinks = true; - QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), - iter->m_Title, - ui->toolBar); + QAction *exeAction = new QAction( + iconForExecutable(iter->m_BinaryInfo.filePath()), iter->m_Title); exeAction->setObjectName(QString("custom__") + iter->m_Title); + if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { qDebug("failed to connect trigger?"); } ui->linksToolBar->addAction(exeAction); + ui->menuRun->addAction(exeAction); } } - // don't show the toolbar if there are no links + // don't show the toolbar or menu if there are no links ui->linksToolBar->setVisible(hasLinks); + ui->menuRun->menuAction()->setVisible(hasLinks); } void MainWindow::toolbarMenu_aboutToShow() @@ -674,6 +679,7 @@ QMenu* MainWindow::createPopupMenu() void MainWindow::on_actionMainMenuToggle_triggered() { ui->menuBar->setVisible(!ui->menuBar->isVisible()); + m_menuBarVisible = ui->menuBar->isVisible(); } void MainWindow::on_actionToolBarMainToggle_triggered() @@ -1952,7 +1958,8 @@ void MainWindow::readSettings() } if (settings.contains("menubar_visible")) { - ui->menuBar->setVisible(settings.value("menubar_visible").toBool()); + m_menuBarVisible = settings.value("menubar_visible").toBool(); + ui->menuBar->setVisible(m_menuBarVisible); } if (settings.contains("window_split")) { @@ -2047,7 +2054,7 @@ void MainWindow::storeSettings(QSettings &settings) { settings.setValue("window_state", saveState()); settings.setValue("toolbar_size", ui->toolBar->iconSize()); settings.setValue("toolbar_button_style", static_cast(ui->toolBar->toolButtonStyle())); - settings.setValue("menubar_visible", ui->menuBar->isVisible()); + settings.setValue("menubar_visible", m_menuBarVisible); settings.setValue("window_split", ui->splitter->saveState()); settings.setValue("window_monitor", QApplication::desktop()->screenNumber(this)); settings.setValue("log_split", ui->topLevelSplitter->saveState()); @@ -5001,7 +5008,7 @@ void MainWindow::linkToolbar() Executable &exe(getSelectedExecutable()); exe.showOnToolbar(!exe.isShownOnToolbar()); ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); - updateToolBar(); + updatePinnedExecutables(); } namespace { @@ -6189,7 +6196,7 @@ void MainWindow::removeFromToolbar() qDebug("executable doesn't exist any more"); } - updateToolBar(); + updatePinnedExecutables(); } @@ -6850,6 +6857,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event) if (event->key() == Qt::Key_Alt) { if (!ui->menuBar->isVisible()) { ui->menuBar->setVisible(true); + m_menuBarVisible = true; } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 09aeb044..4847d65c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -211,7 +211,7 @@ private: void createHelpMenu(); void createEndorseMenu(); - void updateToolBar(); + void updatePinnedExecutables(); void setToolbarSize(const QSize& s); void setToolbarButtonStyle(Qt::ToolButtonStyle s); void toolbarMenu_aboutToShow(); @@ -324,6 +324,10 @@ private: bool m_WasVisible; + // this has to be remembered because by the time storeSettings() is called, + // the window is closed and the menubar is hidden + bool m_menuBarVisible; + MOBase::TutorialControl m_Tutorial; int m_OldProfileIndex; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e25111e1..e093fd3f 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1392,7 +1392,7 @@ p, li { white-space: pre-wrap; } 21 - + &File @@ -1402,7 +1402,7 @@ p, li { white-space: pre-wrap; } - + &Tools @@ -1413,7 +1413,7 @@ p, li { white-space: pre-wrap; } - + &Help @@ -1421,7 +1421,7 @@ p, li { white-space: pre-wrap; } - + &Edit @@ -1450,11 +1450,17 @@ p, li { white-space: pre-wrap; } - - + + + &Run + + + + - - + + + -- cgit v1.3.1 From f9906825e7822771f2f3741b1c696e71bf1c76ce Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 3 Jun 2019 19:26:03 -0400 Subject: fixed exit menu item not working --- src/mainwindow.cpp | 8 +++++--- src/mainwindow.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f8c09b20..8eae1914 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1069,12 +1069,12 @@ void MainWindow::showEvent(QShowEvent *event) void MainWindow::closeEvent(QCloseEvent* event) { - if (!exit()) { + if (!confirmExit()) { event->ignore(); } } -bool MainWindow::exit() +bool MainWindow::confirmExit() { m_closing = true; @@ -5518,7 +5518,9 @@ void MainWindow::on_actionUpdate_triggered() void MainWindow::on_actionExit_triggered() { - exit(); + if (confirmExit()) { + qApp->exit(); + } } void MainWindow::actionEndorseMO() diff --git a/src/mainwindow.h b/src/mainwindow.h index 4847d65c..81b6a656 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -153,7 +153,7 @@ public: void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); - bool exit(); + bool confirmExit(); virtual bool closeWindow(); virtual void setWindowEnabled(bool enabled); -- cgit v1.3.1 From 369408eed067c1ca76759ef22ea5dfc4facdf082 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 18:25:31 -0400 Subject: reverted changes to the toolbar: - swapped back "add profile" and "install mod" icons - removed links toolbar, icons are added to the main one - locked the toolbar because there's only one now - right align everything after the last separator and executable shortcuts --- src/mainwindow.cpp | 74 +++++++++++++++++++++++++++++++++++------------------- src/mainwindow.h | 8 ++++-- src/mainwindow.ui | 43 +++++++------------------------ 3 files changed, 63 insertions(+), 62 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ebb5d7ab..60a86b95 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -202,6 +202,7 @@ MainWindow::MainWindow(QSettings &initSettings , ui(new Ui::MainWindow) , m_WasVisible(false) , m_menuBarVisible(true) + , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) , m_ModListGroupingProxy(nullptr) @@ -251,15 +252,7 @@ MainWindow::MainWindow(QSettings &initSettings updateProblemsButton(); - // Setup toolbar - - setupActionMenu(ui->actionTool); - setupActionMenu(ui->actionHelp); - setupActionMenu(ui->actionEndorseMO); - - createHelpMenu(); - createEndorseMenu(); - + setupToolbar(); toggleMO2EndorseState(); TaskProgressManager::instance().tryCreateTaskbar(); @@ -400,7 +393,7 @@ MainWindow::MainWindow(QSettings &initSettings 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->linksToolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(linksToolBar_customContextMenuRequested(QPoint))); + connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); connect(ui->menuToolbars, &QMenu::aboutToShow, [&]{ toolbarMenu_aboutToShow(); }); connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); @@ -601,6 +594,34 @@ static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex return result; } +void MainWindow::setupToolbar() +{ + setupActionMenu(ui->actionTool); + setupActionMenu(ui->actionHelp); + setupActionMenu(ui->actionEndorseMO); + + createHelpMenu(); + createEndorseMenu(); + + // find last separator, add a spacer just before it so the icons are + // right-aligned + m_linksSeparator = nullptr; + for (auto* a : ui->toolBar->actions()) { + if (a->isSeparator()) { + m_linksSeparator = a; + } + } + + if (m_linksSeparator) { + auto* spacer = new QWidget(ui->toolBar); + spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + ui->toolBar->insertWidget(m_linksSeparator, spacer); + + } else { + qWarning("no separator found on the toolbar, icons won't be right-aligned"); + } +} + void MainWindow::setupActionMenu(QAction* a) { a->setMenu(new QMenu(this)); @@ -612,9 +633,11 @@ void MainWindow::setupActionMenu(QAction* a) void MainWindow::updatePinnedExecutables() { - for (auto* a : ui->linksToolBar->actions()) { - ui->linksToolBar->removeAction(a); - a->deleteLater(); + for (auto* a : ui->toolBar->actions()) { + if (a->objectName().startsWith("custom__")) { + ui->toolBar->removeAction(a); + a->deleteLater(); + } } ui->menuRun->clear(); @@ -637,13 +660,18 @@ void MainWindow::updatePinnedExecutables() qDebug("failed to connect trigger?"); } - ui->linksToolBar->addAction(exeAction); + if (m_linksSeparator) { + ui->toolBar->insertAction(m_linksSeparator, exeAction); + } else { + // separator wasn't found, add it to the end + ui->toolBar->addAction(exeAction); + } + ui->menuRun->addAction(exeAction); } } - // don't show the toolbar or menu if there are no links - ui->linksToolBar->setVisible(hasLinks); + // don't show the menu if there are no links ui->menuRun->menuAction()->setVisible(hasLinks); } @@ -660,7 +688,6 @@ void MainWindow::toolbarMenu_aboutToShow() ui->actionMainMenuToggle->setChecked(ui->menuBar->isVisible()); ui->actionToolBarMainToggle->setChecked(ui->toolBar->isVisible()); - ui->actionToolBarLinksToggle->setChecked(ui->linksToolBar->isVisible()); ui->actionToolBarSmallIcons->setChecked(ui->toolBar->iconSize() == SmallToolbarSize); ui->actionToolBarMediumIcons->setChecked(ui->toolBar->iconSize() == MediumToolbarSize); @@ -687,11 +714,6 @@ void MainWindow::on_actionToolBarMainToggle_triggered() ui->toolBar->setVisible(!ui->toolBar->isVisible()); } -void MainWindow::on_actionToolBarLinksToggle_triggered() -{ - ui->linksToolBar->setVisible(!ui->linksToolBar->isVisible()); -} - void MainWindow::on_actionToolBarSmallIcons_triggered() { setToolbarSize(SmallToolbarSize); @@ -6207,23 +6229,23 @@ void MainWindow::removeFromToolbar() } -void MainWindow::linksToolBar_customContextMenuRequested(const QPoint &point) +void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) { - QAction *action = ui->linksToolBar->actionAt(point); + QAction *action = ui->toolBar->actionAt(point); if (action != nullptr) { if (action->objectName().startsWith("custom_")) { m_ContextAction = action; QMenu menu; menu.addAction(tr("Remove '%1' from the toolbar").arg(action->text()), this, SLOT(removeFromToolbar())); - menu.exec(ui->linksToolBar->mapToGlobal(point)); + menu.exec(ui->toolBar->mapToGlobal(point)); return; } } // did not click a link button, show the default context menu auto* m = createPopupMenu(); - m->exec(ui->linksToolBar->mapToGlobal(point)); + m->exec(ui->toolBar->mapToGlobal(point)); } void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) diff --git a/src/mainwindow.h b/src/mainwindow.h index 81b6a656..679065bd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -207,6 +207,7 @@ private: void cleanup(); + void setupToolbar(); void setupActionMenu(QAction* a); void createHelpMenu(); void createEndorseMenu(); @@ -328,6 +329,10 @@ private: // the window is closed and the menubar is hidden bool m_menuBarVisible; + // last separator on the toolbar, used to add spacer for right-alignment and + // as an insert point for executables + QAction* m_linksSeparator; + MOBase::TutorialControl m_Tutorial; int m_OldProfileIndex; @@ -602,7 +607,7 @@ private slots: */ void allowListResize(); - void linksToolBar_customContextMenuRequested(const QPoint &point); + void toolBar_customContextMenuRequested(const QPoint &point); void removeFromToolbar(); void overwriteClosed(int); @@ -640,7 +645,6 @@ private slots: // ui slots void on_actionExit_triggered(); void on_actionMainMenuToggle_triggered(); void on_actionToolBarMainToggle_triggered(); - void on_actionToolBarLinksToggle_triggered(); void on_actionToolBarSmallIcons_triggered(); void on_actionToolBarMediumIcons_triggered(); void on_actionToolBarLargeIcons_triggered(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e093fd3f..d3f9ef39 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1352,9 +1352,15 @@ p, li { white-space: pre-wrap; } + + Qt::CustomContextMenu + Main ToolBar + + false + 42 @@ -1368,11 +1374,9 @@ p, li { white-space: pre-wrap; } false - - - - + + @@ -1437,7 +1441,6 @@ p, li { white-space: pre-wrap; } - @@ -1462,26 +1465,6 @@ p, li { white-space: pre-wrap; } - - - Qt::CustomContextMenu - - - Links ToolBar - - - - 42 - 36 - - - - TopToolBarArea - - - false - - @@ -1734,15 +1717,7 @@ p, li { white-space: pre-wrap; } true - M&ain - - - - - true - - - &Links + M&ain Toolbar -- cgit v1.3.1 From 479150a619bbfdfa8d7527e0fee28daecd22724c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 18:33:23 -0400 Subject: show the main menu when releasing alt instead of pressing, less annoying to take screenshots with alt+printscreen without having the menu pop up --- src/mainwindow.cpp | 4 ++-- src/mainwindow.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 60a86b95..bf357c20 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6880,7 +6880,7 @@ void MainWindow::dropEvent(QDropEvent *event) event->accept(); } -void MainWindow::keyPressEvent(QKeyEvent *event) +void MainWindow::keyReleaseEvent(QKeyEvent *event) { // if the menubar is hidden, pressing Alt will make it visible if (event->key() == Qt::Key_Alt) { @@ -6890,7 +6890,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event) } } - QMainWindow::keyPressEvent(event); + QMainWindow::keyReleaseEvent(event); } void MainWindow::on_clickBlankButton_clicked() diff --git a/src/mainwindow.h b/src/mainwindow.h index 679065bd..35ac75a9 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -195,7 +195,7 @@ protected: virtual void resizeEvent(QResizeEvent *event); virtual void dragEnterEvent(QDragEnterEvent *event); virtual void dropEvent(QDropEvent *event); - void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; private slots: void on_actionChange_Game_triggered(); -- cgit v1.3.1 From 63d62a40c261fd17cf3001808fce3a8d4bb705e2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 22:44:46 -0400 Subject: added "Open Origin in Explorer" to the data tab renamed openOriginExplorer_clicked() to openPluginOriginExplorer_clicked() to make it clearer exec() for context menu in lists should be relative to the viewport to account for the header size --- src/mainwindow.cpp | 45 +++++++++++++++++++++++++++++++++++---------- src/mainwindow.h | 3 ++- 2 files changed, 37 insertions(+), 11 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ebb5d7ab..085f626b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3377,7 +3377,7 @@ void MainWindow::openExplorer_clicked() } } -void MainWindow::openOriginExplorer_clicked() +void MainWindow::openPluginOriginExplorer_clicked() { QItemSelectionModel *selection = ui->espList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 0) { @@ -4721,7 +4721,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) // no selection QMenu menu(this); initModListContextMenu(&menu); - menu.exec(modList->mapToGlobal(pos)); + menu.exec(modList->viewport()->mapToGlobal(pos)); } else { QMenu menu(this); @@ -4866,7 +4866,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu.setDefaultAction(infoAction); } - menu.exec(modList->mapToGlobal(pos)); + menu.exec(modList->viewport()->mapToGlobal(pos)); } } catch (const std::exception &e) { reportError(tr("Exception: ").arg(e.what())); @@ -5005,7 +5005,7 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); - menu.exec(ui->savegameList->mapToGlobal(pos)); + menu.exec(ui->savegameList->viewport()->mapToGlobal(pos)); } void MainWindow::linkToolbar() @@ -5447,6 +5447,24 @@ void MainWindow::openDataFile() m_OrganizerCore.executeFileVirtualized(this, targetInfo); } +void MainWindow::openDataOriginExplorer_clicked() +{ + if (m_ContextItem == nullptr) { + return; + } + + 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) { + return; + } + + const auto fullPath = m_ContextItem->data(0, Qt::UserRole).toString(); + + qDebug().nospace() << "opening in explorer: " << fullPath; + shell::ExploreFile(fullPath); +} void MainWindow::updateAvailable() { @@ -5490,8 +5508,15 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) 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())); + } + // offer to hide/unhide file, but not for files from archives - if (!m_ContextItem->data(0, Qt::UserRole + 1).toBool()) { + if (!isArchive) { if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { menu.addAction(tr("Un-Hide"), this, SLOT(unhideFile())); } else { @@ -5504,7 +5529,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->mapToGlobal(pos)); + menu.exec(dataTree->viewport()->mapToGlobal(pos)); } void MainWindow::on_conflictsCheckBox_toggled(bool) @@ -6086,7 +6111,7 @@ void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos) QMenu menu; menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered())); - menu.exec(ui->bsaList->mapToGlobal(pos)); + menu.exec(ui->bsaList->viewport()->mapToGlobal(pos)); } void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) @@ -6163,7 +6188,7 @@ void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); - menu.exec(ui->categoriesList->mapToGlobal(pos)); + menu.exec(ui->categoriesList->viewport()->mapToGlobal(pos)); } @@ -6271,7 +6296,7 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) unsigned int modInfoIndex = ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(idx.data().toString())); //this is to avoid showing the option on game files like skyrim.esm if (modInfoIndex != UINT_MAX) { - menu.addAction(tr("Open Origin in Explorer"), this, SLOT(openOriginExplorer_clicked())); + menu.addAction(tr("Open Origin in Explorer"), this, SLOT(openPluginOriginExplorer_clicked())); ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); std::vector flags = modInfo->getFlags(); @@ -6282,7 +6307,7 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) } try { - menu.exec(ui->espList->mapToGlobal(pos)); + menu.exec(ui->espList->viewport()->mapToGlobal(pos)); } catch (const std::exception &e) { reportError(tr("Exception: ").arg(e.what())); } catch (...) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 81b6a656..c964749a 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -445,7 +445,7 @@ private slots: void visitOnNexus_clicked(); void visitWebPage_clicked(); void openExplorer_clicked(); - void openOriginExplorer_clicked(); + void openPluginOriginExplorer_clicked(); void openOriginInformation_clicked(); void information_clicked(); void enableSelectedMods_clicked(); @@ -464,6 +464,7 @@ private slots: void previewDataFile(); void hideFile(); void unhideFile(); + void openDataOriginExplorer_clicked(); // pluginlist context menu void enableSelectedPlugins_clicked(); -- cgit v1.3.1 From 4f240ddb7f8fa2cb2211105756202daca010b008 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 09:14:35 -0400 Subject: fixes toolbar and menu icons not respecting the stylesheet --- src/mainwindow.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++++--------- src/mainwindow.h | 1 + 2 files changed, 67 insertions(+), 13 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b19e7573..62846da8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -401,8 +401,6 @@ MainWindow::MainWindow(QSettings &initSettings connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*))); - connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); - m_CheckBSATimer.setSingleShot(true); connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); @@ -465,19 +463,65 @@ MainWindow::MainWindow(QSettings &initSettings refreshExecutablesList(); updatePinnedExecutables(); - - for (QAction *action : ui->toolBar->actions()) { - // set the name of the widget to the name of the action to allow styling - QWidget *actionWidget = ui->toolBar->widgetForAction(action); - actionWidget->setObjectName(action->objectName()); - actionWidget->style()->unpolish(actionWidget); - actionWidget->style()->polish(actionWidget); - } - + resetActionIcons(); updatePluginCount(); updateModCount(); } +void MainWindow::resetActionIcons() +{ + // this is a bit of a hack + // + // the .qss files have historically set qproperty-icon by id and these ids + // correspond to the QActions created in the .ui file + // + // the problem is that QActions do not support having their icon property + // set from a .qss because they're not widgets (they don't inherit from + // QWidget), and styling only works on widget + // + // a QAction _does_ have an associated icon, it just can't be set from a .qss + // file + // + // so here, a dummy QToolButton widget is created for each QAction and is + // given the same name as the action, which makes it pick up the icon + // specified in the .qss file + // + // that icon is then given to the widget used by the QAction (if it's some + // sort of button, which typically happens on the toolbar) _and_ to the + // QAction itself, which is used in the menu bar + + // QActions created from the .ui file are children of the main window + for (QAction* action : findChildren()) { + // creating a dummy button + auto dummy = std::make_unique(); + + // reusing the action name + dummy->setObjectName(action->objectName()); + + // styling the button, this has to be done manually because the button is + // never added anywhere + style()->polish(dummy.get()); + + // the button's icon may be null if it wasn't specified in the .qss file, + // which can happen if the stylesheet just doesn't override icons, or for + // other actions like the pinned custom executables + const auto icon = dummy->icon(); + if (icon.isNull()) { + continue; + } + + // button associated with the action on the toolbar + QWidget* actionWidget = ui->toolBar->widgetForAction(action); + + if (auto* actionButton=dynamic_cast(actionWidget)) { + actionButton->setIcon(icon); + } + + // the action's icon is used by the menu bar + action->setIcon(icon); + } +} + MainWindow::~MainWindow() { @@ -565,8 +609,7 @@ void MainWindow::allowListResize() void MainWindow::updateStyle(const QString&) { - // no effect? - ensurePolished(); + resetActionIcons(); } void MainWindow::resizeEvent(QResizeEvent *event) @@ -1049,6 +1092,16 @@ void MainWindow::showEvent(QShowEvent *event) QMainWindow::showEvent(event); if (!m_WasVisible) { + // 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 + // _after_ the MainWindow is constructed, but _before_ it is shown + // + // by connecting the event here, changing the style setting will first be + // handled by MOApplication, and then in updateStyle(), at which point the + // stylesheet has already been set correctly + connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); + // only the first time the window becomes visible m_Tutorial.registerControl(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 426b0881..07a580c3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -631,6 +631,7 @@ private slots: void search_activated(); void searchClear_activated(); + void resetActionIcons(); void updateModCount(); void updatePluginCount(); -- cgit v1.3.1 From c31f8b7e314d08022d1d8e50cc77e727eb4bcb5c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 09:42:22 -0400 Subject: fixed notification icon not respecting the stylesheet on startup --- src/mainwindow.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++++------ src/mainwindow.h | 4 ++++ 2 files changed, 55 insertions(+), 6 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 62846da8..a20f49f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -490,6 +490,10 @@ void MainWindow::resetActionIcons() // sort of button, which typically happens on the toolbar) _and_ to the // QAction itself, which is used in the menu bar + // clearing the notification, will be set below if the stylesheet has set + // anything for it + m_originalNotificationIcon = {}; + // QActions created from the .ui file are children of the main window for (QAction* action : findChildren()) { // creating a dummy button @@ -519,7 +523,16 @@ void MainWindow::resetActionIcons() // the action's icon is used by the menu bar action->setIcon(icon); + + if (action == ui->actionNotifications) { + // if the stylesheet has set a notification icon, remember it here so it + // can be used in updateProblemsButton() + m_originalNotificationIcon = icon; + } } + + // update the button for the potentially new icon + updateProblemsButton(); } @@ -831,20 +844,52 @@ void MainWindow::scheduleUpdateButton() void MainWindow::updateProblemsButton() { - size_t numProblems = checkForProblems(); + // if the current stylesheet doesn't provide an icon, this is used instead + const char* DefaultIconName = ":/MO/gui/warning"; + + const std::size_t numProblems = checkForProblems(); + + // starting icon + const QIcon original = m_originalNotificationIcon.isNull() ? + QIcon(DefaultIconName) : m_originalNotificationIcon; + + // final icon + QIcon final; + if (numProblems > 0) { ui->actionNotifications->setToolTip(tr("There are notifications to read")); - QPixmap mergedIcon = QPixmap(":/MO/gui/warning").scaled(64, 64); + // will contain the original icon, plus a notification count; this also + // makes sure the pixmap is exactly 64x64 by 1) requesting the icon that's + // as close to 64x64 as possible, then scaling it up if it's too small + QPixmap merged = original.pixmap(64, 64).scaled(64, 64); + { - QPainter painter(&mergedIcon); - std::string badgeName = std::string(":/MO/gui/badge_") + (numProblems < 10 ? std::to_string(static_cast(numProblems)) : "more"); + QPainter painter(&merged); + + const std::string badgeName = + std::string(":/MO/gui/badge_") + + (numProblems < 10 ? std::to_string(static_cast(numProblems)) : "more"); + painter.drawPixmap(32, 32, 32, 32, QPixmap(badgeName.c_str())); } - ui->actionNotifications->setIcon(QIcon(mergedIcon)); + + final = QIcon(merged); } else { ui->actionNotifications->setToolTip(tr("There are no notifications")); - ui->actionNotifications->setIcon(QIcon(":/MO/gui/warning")); + + // no change + final = original; + } + + // setting the icon on the action (shown on the menu) + ui->actionNotifications->setIcon(final); + + // setting the icon on the toolbar button + if (auto* actionWidget=ui->toolBar->widgetForAction(ui->actionNotifications)) { + if (auto* button=dynamic_cast(actionWidget)) { + button->setIcon(final); + } } } diff --git a/src/mainwindow.h b/src/mainwindow.h index 07a580c3..b8d9f49f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -398,6 +398,10 @@ private: MOBase::DelayedFileWriter m_ArchiveListWriter; + // icon set by the stylesheet, used to remember its original appearance + // when painting the count + QIcon m_originalNotificationIcon; + enum class ShortcutType { Toolbar, Desktop, -- cgit v1.3.1 From 853c95b921f4fc3beb8daf71d79b44aa1ab06c92 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 10 Jun 2019 16:43:39 -0400 Subject: added new statusbar class, moved refresh progress bar to it --- src/CMakeLists.txt | 3 +++ src/mainwindow.cpp | 20 ++++---------------- src/mainwindow.h | 5 +++-- src/statusbar.cpp | 27 +++++++++++++++++++++++++++ src/statusbar.h | 20 ++++++++++++++++++++ 5 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 src/statusbar.cpp create mode 100644 src/statusbar.h (limited to 'src/mainwindow.h') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93597d62..71f87a8a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,6 +116,7 @@ SET(organizer_SRCS forcedloaddialog.cpp forcedloaddialogwidget.cpp filterwidget.cpp + statusbar.cpp shared/windows_error.cpp shared/error_report.cpp @@ -213,6 +214,7 @@ SET(organizer_HDRS forcedloaddialog.h forcedloaddialogwidget.h filterwidget.h + statusbar.h shared/windows_error.h shared/error_report.h @@ -277,6 +279,7 @@ set(application moshortcut selfupdater singleinstance + statusbar ) set(browser diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 911d0ff1..67d0f2ff 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -77,6 +77,7 @@ along with Mod Organizer. If not, see . #include "nxmaccessmanager.h" #include "appconfig.h" #include "eventfilter.h" +#include "statusbar.h" #include #include #include @@ -242,13 +243,7 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->logList, SLOT(scrollToBottom())); - m_RefreshProgress = new QProgressBar(statusBar()); - m_RefreshProgress->setTextVisible(true); - m_RefreshProgress->setRange(0, 100); - m_RefreshProgress->setValue(0); - m_RefreshProgress->setVisible(false); - statusBar()->addWidget(m_RefreshProgress, 1000); - statusBar()->clearMessage(); + m_statusBar.reset(new StatusBar(statusBar())); updateProblemsButton(); @@ -2534,15 +2529,8 @@ void MainWindow::setESPListSorting(int index) void MainWindow::refresher_progress(int percent) { - if (percent == 100) { - m_RefreshProgress->setVisible(false); - this->setEnabled(true); - } else if (!m_RefreshProgress->isVisible()) { - this->setEnabled(false); - m_RefreshProgress->setVisible(true); - m_RefreshProgress->setRange(0, 100); - m_RefreshProgress->setValue(percent); - } + setEnabled(percent == 100); + m_statusBar->setProgress(percent); } void MainWindow::directory_refreshed() diff --git a/src/mainwindow.h b/src/mainwindow.h index b8d9f49f..734ece88 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -37,6 +37,7 @@ struct Executable; class CategoryFactory; class LockedDialogBase; class OrganizerCore; +class StatusBar; #include "plugincontainer.h" //class PluginContainer; class PluginListSortProxy; namespace BSA { class Archive; } @@ -75,7 +76,6 @@ class QListWidgetItem; class QMenu; class QModelIndex; class QPoint; -class QProgressBar; class QProgressDialog; class QTranslator; class QTreeWidgetItem; @@ -329,6 +329,8 @@ private: // the window is closed and the menubar is hidden bool m_menuBarVisible; + 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; @@ -338,7 +340,6 @@ private: int m_OldProfileIndex; std::vector m_ModNameList; // the mod-list to go with the directory structure - QProgressBar *m_RefreshProgress; bool m_Refreshing; QStringList m_DefaultArchives; diff --git a/src/statusbar.cpp b/src/statusbar.cpp new file mode 100644 index 00000000..7370662a --- /dev/null +++ b/src/statusbar.cpp @@ -0,0 +1,27 @@ +#include "statusbar.h" + +StatusBar::StatusBar(QStatusBar* bar) + : m_bar(bar), m_nexusAPI(new QLabel), m_progress(new QProgressBar) +{ + m_progress->setTextVisible(true); + m_progress->setRange(0, 100); + m_progress->setValue(0); + m_progress->setVisible(false); + + m_bar->addPermanentWidget(m_nexusAPI); + m_bar->addPermanentWidget(m_progress); + + m_bar->clearMessage(); +} + +void StatusBar::setProgress(int percent) +{ + qDebug().nospace() << "progress: " << percent; + + if (percent < 0 || percent >= 100) { + m_progress->setVisible(false); + } else if (!m_progress->isVisible()) { + m_progress->setVisible(true); + m_progress->setValue(percent); + } +} diff --git a/src/statusbar.h b/src/statusbar.h new file mode 100644 index 00000000..f3ad3081 --- /dev/null +++ b/src/statusbar.h @@ -0,0 +1,20 @@ +#ifndef MO_STATUSBAR_H +#define MO_STATUSBAR_H + +#include +#include + +class StatusBar +{ +public: + StatusBar(QStatusBar* bar); + + void setProgress(int percent); + +private: + QStatusBar* m_bar; + QLabel* m_api; + QProgressBar* m_progress; +}; + +#endif // MO_STATUSBAR_H -- cgit v1.3.1 From ebbc900755b09862be95d29d2a02b8abd1792a3a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 12 Jun 2019 15:10:41 -0400 Subject: added a few helper classes for user accounts and stats moved the api label to the status bar refactored a bunch of copy/pasted code in NexusInterface to use shouldThrottle() and throttledWarning() --- src/mainwindow.cpp | 68 +++++----- src/mainwindow.h | 5 +- src/mainwindow.ui | 46 +------ src/nexusinterface.cpp | 314 ++++++++++++++++++++++++++++++----------------- src/nexusinterface.h | 150 +++++++++++++++++++--- src/nxmaccessmanager.cpp | 15 +-- src/nxmaccessmanager.h | 5 +- src/statusbar.cpp | 53 +++++++- src/statusbar.h | 6 + 9 files changed, 428 insertions(+), 234 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 67d0f2ff..6b3ee11f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -225,7 +225,7 @@ MainWindow::MainWindow(QSettings &initSettings QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory()); QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); ui->setupUi(this); - updateWindowTitle(QString(), 0, false); + updateWindowTitle({}); languageChange(m_OrganizerCore.settings().language()); @@ -340,13 +340,6 @@ MainWindow::MainWindow(QSettings &initSettings ui->bossButton->setToolTip(tr("There is no supported sort mechanism for this game. You will probably have to use a third-party tool.")); } - ui->apiRequests->setAutoFillBackground(true); - QPalette palette = ui->apiRequests->palette(); - palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkGreen); - palette.setColor(ui->apiRequests->foregroundRole(), Qt::white); - ui->apiRequests->setPalette(palette); - ui->apiRequests->setVisible(!m_OrganizerCore.settings().hideAPICounter()); - connect(&m_PluginContainer, SIGNAL(diagnosisUpdate()), this, SLOT(updateProblemsButton())); connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); @@ -379,11 +372,24 @@ MainWindow::MainWindow(QSettings &initSettings 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(), SIGNAL(credentialsReceived(const QString&, int, bool, std::tuple)), - this, SLOT(updateWindowTitle(const QString&, int, bool))); - connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, int, bool, std::tuple)), - NexusInterface::instance(&m_PluginContainer), SLOT(setRateMax(const QString&, int, bool, std::tuple))); - connect(NexusInterface::instance(&pluginContainer), SIGNAL(requestsChanged(int, std::tuple)), this, SLOT(updateAPICounter(int, std::tuple))); + + connect( + NexusInterface::instance(&pluginContainer)->getAccessManager(), + SIGNAL(credentialsReceived(const APIUserAccount&)), + this, + SLOT(updateWindowTitle(const APIUserAccount&))); + + connect( + NexusInterface::instance(&pluginContainer)->getAccessManager(), + SIGNAL(credentialsReceived(const APIUserAccount&)), + NexusInterface::instance(&m_PluginContainer), + SLOT(setUserAccount(const APIUserAccount&))); + + connect( + NexusInterface::instance(&pluginContainer), + SIGNAL(requestsChanged(const APIStats&, const APIUserAccount&)), + this, + SLOT(onRequestsChanged(const APIStats&, const APIUserAccount&))); connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); @@ -548,20 +554,27 @@ MainWindow::~MainWindow() } -void MainWindow::updateWindowTitle(const QString &accountName, int, bool premium) +void MainWindow::updateWindowTitle(const APIUserAccount& user) { QString title = QString("%1 Mod Organizer v%2").arg( m_OrganizerCore.managedGame()->gameName(), m_OrganizerCore.getVersion().displayString(3)); - if (!accountName.isEmpty()) { - title.append(QString(" (%1%2)").arg(accountName, premium ? "*" : "")); + if (!user.name().isEmpty()) { + const QString premium = (user.type() == APIUserAccountTypes::Premium ? "*" : ""); + title.append(QString(" (%1%2)").arg(user.name(), premium)); } this->setWindowTitle(title); } +void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user) +{ + m_statusBar->updateAPI(stats, user); +} + + void MainWindow::disconnectPlugins() { if (ui->actionTool->menu() != nullptr) { @@ -5267,8 +5280,7 @@ void MainWindow::on_actionSettings_triggered() activateProxy(settings.useProxy()); } - ui->apiRequests->setVisible(!settings.hideAPICounter()); - + m_statusBar->checkSettings(m_OrganizerCore.settings()); updateDownloadView(); m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType(), settings.executablesBlacklist()); @@ -6067,26 +6079,6 @@ void MainWindow::nxmRequestFailed(QString gameName, int modID, int, QVariant, in } -void MainWindow::updateAPICounter(int queueCount, std::tuple limits) -{ - ui->apiRequests->setText(QString("API: Q: %1 | D: %2 | H: %3").arg(queueCount).arg(std::get<0>(limits)).arg(std::get<2>(limits))); - int requestsRemaining = std::get<0>(limits) + std::get<2>(limits); - if (requestsRemaining > 300) { - QPalette palette = ui->apiRequests->palette(); - palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkGreen); - ui->apiRequests->setPalette(palette); - } else if (requestsRemaining < 150) { - QPalette palette = ui->apiRequests->palette(); - palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkRed); - ui->apiRequests->setPalette(palette); - } else { - QPalette palette = ui->apiRequests->palette(); - palette.setColor(ui->apiRequests->backgroundRole(), Qt::darkYellow); - ui->apiRequests->setPalette(palette); - } -} - - BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &progress) { diff --git a/src/mainwindow.h b/src/mainwindow.h index 734ece88..e2c6ce8b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -416,8 +416,7 @@ private: private slots: - void updateWindowTitle(const QString &accountName, int, bool premium); - + void updateWindowTitle(const APIUserAccount& user); void showMessage(const QString &message); void showError(const QString &message); @@ -544,7 +543,7 @@ private slots: void nxmDownloadURLs(QString, int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); - void updateAPICounter(int queueCount, std::tuple limits); + void onRequestsChanged(const APIStats& stats, const APIUserAccount& user); void editCategories(); void deselectFilters(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d3f9ef39..bbcb734c 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -412,7 +412,7 @@ p, li { white-space: pre-wrap; } - + @@ -471,37 +471,6 @@ p, li { white-space: pre-wrap; } - - - - Nexus API Queued and Remaining Requests - - - <html><head/><body><p>This tracks the number of queued Nexus API requests on the left (<span style=" font-weight:600;">Q</span>) and the remaining daily (<span style=" font-weight:600;">D</span>) and hourly (<span style=" font-weight:600;">H</span>) requests on the right. 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.</p></body></html> - - - QFrame::StyledPanel - - - QFrame::Sunken - - - 2 - - - 1 - - - API: Q: 0 | D: 0 | H: 0 - - - Qt::AlignCenter - - - 2 - - - @@ -546,19 +515,6 @@ p, li { white-space: pre-wrap; } - - - - Qt::Horizontal - - - - 40 - 20 - - - - diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index c677add0..8362143a 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -38,6 +38,82 @@ using namespace MOBase; 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()); +} + + +APIUserAccount::APIUserAccount() + : m_type(APIUserAccountTypes::None) +{ +} + +const QString& APIUserAccount::id() const +{ + return m_id; +} + +const QString& APIUserAccount::name() const +{ + return m_name; +} + +APIUserAccountTypes APIUserAccount::type() const +{ + return m_type; +} + +const APILimits& APIUserAccount::limits() const +{ + return m_limits; +} + +APIUserAccount& APIUserAccount::id(const QString& id) +{ + m_id = id; + return *this; +} + +APIUserAccount& APIUserAccount::name(const QString& name) +{ + m_name = name; + return *this; +} + +APIUserAccount& APIUserAccount::type(APIUserAccountTypes type) +{ + m_type = type; + return *this; +} + +APIUserAccount& APIUserAccount::limits(const APILimits& limits) +{ + m_limits = limits; + return *this; +} + +int APIUserAccount::remainingRequests() const +{ + return m_limits.remainingDailyRequests + m_limits.remainingHourlyRequests; +} + +bool APIUserAccount::shouldThrottle() const +{ + return (remainingRequests() < ThrottleThreshold); +} + +bool APIUserAccount::exhausted() const +{ + return (remainingRequests() <= 0); +} + + NexusBridge::NexusBridge(PluginContainer *pluginContainer, const QString &subModule) : m_Interface(NexusInterface::instance(pluginContainer)) , m_SubModule(subModule) @@ -179,15 +255,39 @@ void NexusBridge::nxmRequestFailed(QString gameName, int modID, int fileID, QVar QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); +APILimits NexusInterface::defaultAPILimits() +{ + // https://app.swaggerhub.com/apis-docs/NexusMods/nexus-mods_public_api_params_in_form_data/1.0#/ + const int MaxDaily = 2500; + const int MaxHourly = 100; + + APILimits limits; + + limits.maxDailyRequests = MaxDaily; + limits.remainingDailyRequests = MaxDaily; + limits.maxHourlyRequests = MaxHourly; + limits.remainingHourlyRequests = MaxHourly; + + return limits; +} + +APILimits NexusInterface::parseLimits(const QNetworkReply* reply) +{ + 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(); + + return limits; +} + + NexusInterface::NexusInterface(PluginContainer *pluginContainer) : m_PluginContainer(pluginContainer) - , m_RemainingDailyRequests(2500) - , m_RemainingHourlyRequests(100) - , m_MaxDailyRequests(2500) - , m_MaxHourlyRequests(100) - , m_IsPremium(false) - , m_UserID(0) { + m_User.limits(defaultAPILimits()); m_MOVersion = createVersionInfo(); m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString(3)); @@ -222,15 +322,10 @@ void NexusInterface::loginCompleted() nextRequest(); } -void NexusInterface::setRateMax(const QString&, int userId, bool isPremium, std::tuple limits) +void NexusInterface::setUserAccount(const APIUserAccount& user) { - m_RemainingDailyRequests = std::get<0>(limits); - m_MaxDailyRequests = std::get<1>(limits); - m_RemainingHourlyRequests = std::get<2>(limits); - m_MaxHourlyRequests = std::get<3>(limits); - m_IsPremium = isPremium; - m_UserID = userId; - emit requestsChanged(m_RequestQueue.size(), limits); + m_User = user; + emit requestsChanged(stats(), m_User); } void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) @@ -369,70 +464,70 @@ int NexusInterface::requestDescription(QString gameName, int modID, QObject *rec int NexusInterface::requestModInfo(QString gameName, int modID, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game) { - if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game); - m_RequestQueue.enqueue(requestInfo); + if (m_User.shouldThrottle()) { + throttledWarning(m_User); + return -1; + } - connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_MODINFO, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), + receiver, SLOT(nxmModInfoAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; - } - qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") - .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); - return -1; + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; } int NexusInterface::requestUpdateInfo(QString gameName, NexusInterface::UpdatePeriod period, QObject *receiver, QVariant userData, const QString &subModule, const MOBase::IPluginGame *game) { - if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { - NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game); - m_RequestQueue.enqueue(requestInfo); + if (m_User.shouldThrottle()) { + throttledWarning(m_User); + return -1; + } - connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), - receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection); + NXMRequestInfo requestInfo(period, NXMRequestInfo::TYPE_CHECKUPDATES, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), + receiver, SLOT(nxmUpdateInfoAvailable(QString, QVariant, QVariant, int)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; - } - qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") - .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); - return -1; + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; } int NexusInterface::requestUpdates(const int &modID, QObject *receiver, QVariant userData, QString gameName, const QString &subModule) { - if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { - IPluginGame *game = getGame(gameName); - if (game == nullptr) { - qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName)); - return -1; - } + if (m_User.shouldThrottle()) { + throttledWarning(m_User); + return -1; + } + + IPluginGame *game = getGame(gameName); + if (game == nullptr) { + qCritical("requestUpdates can't find plugin for %s", qUtf8Printable(gameName)); + return -1; + } - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game); - m_RequestQueue.enqueue(requestInfo); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), + receiver, SLOT(nxmUpdatesAvailable(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; - } - qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") - .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); - return -1; + nextRequest(); + return requestInfo.m_ID; } @@ -527,23 +622,23 @@ int NexusInterface::requestEndorsementInfo(QObject *receiver, QVariant userData, int NexusInterface::requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game) { - if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { - NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game); - requestInfo.m_Endorse = endorse; - m_RequestQueue.enqueue(requestInfo); + if (m_User.shouldThrottle()) { + throttledWarning(m_User); + return -1; + } - connect(this, SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), - receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); + NXMRequestInfo requestInfo(modID, modVersion, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game); + requestInfo.m_Endorse = endorse; + m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), + receiver, SLOT(nxmEndorsementToggled(QString, int, QVariant, QVariant, int)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; - } - qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") - .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); - return -1; + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; } int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, const QString &subModule) @@ -564,23 +659,23 @@ int NexusInterface::requestTrackingInfo(QObject *receiver, QVariant userData, co int NexusInterface::requestToggleTracking(QString gameName, int modID, bool track, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game) { - if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) >= 200) { - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game); - requestInfo.m_Track = track; - m_RequestQueue.enqueue(requestInfo); + if (m_User.shouldThrottle()) { + throttledWarning(m_User); + return -1; + } - connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)), - receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection); + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLETRACKING, userData, subModule, game); + requestInfo.m_Track = track; + m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), - receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmTrackingToggled(QString, int, QVariant, bool, int)), + receiver, SLOT(nxmTrackingToggled(QString, int, QVariant, bool, int)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; - } - qCritical() << QString("You have fewer than 200 requests remaining (%1). Only downloads and login validation are being allowed.") - .arg(std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests)); - return -1; + connect(this, SIGNAL(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), + receiver, SLOT(nxmRequestFailed(QString, int, int, QVariant, int, QNetworkReply::NetworkError, QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; } int NexusInterface::requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, @@ -645,7 +740,7 @@ void NexusInterface::nextRequest() } } - if (std::max(m_RemainingDailyRequests, m_RemainingHourlyRequests) <= 0) { + if (m_User.exhausted()) { m_RequestQueue.clear(); QTime time = QTime::currentTime(); QTime targetTime; @@ -696,9 +791,9 @@ void NexusInterface::nextRequest() } break; case NXMRequestInfo::TYPE_DOWNLOADURL: { ModRepositoryFileInfo *fileInfo = qobject_cast(qvariant_cast(info.m_UserData)); - if (m_IsPremium) { + if (m_User.type() == APIUserAccountTypes::Premium) { url = QString("%1/games/%2/mods/%3/files/%4/download_link").arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID); - } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && fileInfo->nexusDownloadUser == m_UserID) { + } else if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires && fileInfo->nexusDownloadUser == m_User.id().toInt()) { 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 { @@ -780,23 +875,16 @@ void NexusInterface::requestFinished(std::list::iterator iter) if (iter->m_AllowedErrors.contains(error) && iter->m_AllowedErrors[error].contains(statusCode)) { // These errors are allows to silently happen. They should be handled in nxmRequestFailed below. } else if (statusCode == 429) { - m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt(); - m_MaxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt(); - m_RemainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt(); - m_MaxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt(); + m_User.limits(parseLimits(reply)); - if (m_RemainingDailyRequests || m_RemainingHourlyRequests) + 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."); - else + } + else { qWarning("All API requests have been consumed and are now being denied."); + } - emit requestsChanged(m_RequestQueue.size(), std::tuple(std::make_tuple( - m_RemainingDailyRequests, - m_MaxDailyRequests, - m_RemainingHourlyRequests, - m_MaxHourlyRequests - ))); - + emit requestsChanged(stats(), m_User); qWarning("Error: %s", reply->errorString().toUtf8().constData()); } else { qWarning("request failed: %s", reply->errorString().toUtf8().constData()); @@ -871,17 +959,8 @@ void NexusInterface::requestFinished(std::list::iterator iter) } break; } - m_RemainingDailyRequests = reply->rawHeader("x-rl-daily-remaining").toInt(); - m_MaxDailyRequests = reply->rawHeader("x-rl-daily-limit").toInt(); - m_RemainingHourlyRequests = reply->rawHeader("x-rl-hourly-remaining").toInt(); - m_MaxHourlyRequests = reply->rawHeader("x-rl-hourly-limit").toInt(); - - emit requestsChanged(m_RequestQueue.size(), std::tuple(std::make_tuple( - m_RemainingDailyRequests, - m_MaxDailyRequests, - m_RemainingHourlyRequests, - m_MaxHourlyRequests - ))); + m_User.limits(parseLimits(reply)); + emit requestsChanged(stats(), m_User); } else { emit nxmRequestFailed(iter->m_GameName, iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->error(), tr("invalid response")); } @@ -938,6 +1017,15 @@ void NexusInterface::requestTimeout() } } +APIStats NexusInterface::stats() const +{ + APIStats stats; + stats.requestsQueued = m_RequestQueue.size(); + + return stats; +} + + namespace { QString get_management_url() { diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 70c1c9c9..6c4e8d11 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -39,6 +39,131 @@ namespace MOBase { class IPluginGame; } class NexusInterface; class NXMAccessManager; + +/** + * represents user account types on a mod provider website such as nexus + */ +enum class APIUserAccountTypes +{ + // not logged in + None = 0, + + // regular account + Regular, + + // premium account + Premium +}; + + +/** + * current limits imposed on the user account + **/ +struct APILimits +{ + // maximum number of requests per day + int maxDailyRequests = 0; + + // remaining number of requests today + int remainingDailyRequests = 0; + + // maximum number of requests per hour + int maxHourlyRequests = 0; + + // remaining number of requests this hour + int remainingHourlyRequests = 0; +}; + + +/** + * API statistics + */ +struct APIStats +{ + // number of API requests currently queued + int requestsQueued = 0; +}; + + +/** + * represents a user account on the mod provier website + */ +class APIUserAccount +{ +public: + // when the number of remanining requests is under this number, further + // requests will be throttled by avoiding non-critical ones + static const int ThrottleThreshold = 300; + + APIUserAccount(); + + /** + * user id + */ + const QString& id() const; + + /** + * user name + */ + const QString& name() const; + + /** + * account type + */ + APIUserAccountTypes type() const; + + /** + * current API limits + */ + const APILimits& limits() const; + + + /** + * sets the user id + */ + APIUserAccount& id(const QString& id); + + /** + * sets the user name + **/ + APIUserAccount& name(const QString& name); + + /** + * sets the acount type + */ + APIUserAccount& type(APIUserAccountTypes type); + + /** + * sets the current limits + */ + APIUserAccount& limits(const APILimits& limits); + + + /** + * returns the number of remaining requests + */ + int remainingRequests() const; + + /** + * whether the number of remaining requests is low enough that further + * requests should be throttled + */ + bool shouldThrottle() const; + + /** + * true if all the remaining requests have been used and the API will refuse + * further requests + */ + bool exhausted() const; + +private: + QString m_id, m_name; + APIUserAccountTypes m_type; + APILimits m_limits; + APIStats m_stats; +}; + + /** * @brief convenience class to make nxm requests easier * usually, all objects that started a nxm request will be signaled if one finished. @@ -53,7 +178,6 @@ class NexusBridge : public MOBase::IModRepositoryBridge Q_OBJECT public: - NexusBridge(PluginContainer *pluginContainer, const QString &subModule = ""); /** @@ -147,6 +271,8 @@ public: }; public: + static APILimits defaultAPILimits(); + static APILimits parseLimits(const QNetworkReply* reply); ~NexusInterface(); @@ -380,7 +506,7 @@ public: /** * */ - int requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, const QString &subModule, + int requestInfoFromMd5(QString gameName, QByteArray &hash, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); /** @@ -459,11 +585,11 @@ signals: void nxmTrackedModsAvailable(QVariant userData, QVariant resultData, int requestID); void nxmTrackingToggled(QString gameName, int modID, QVariant userData, bool tracked, int requestID); void nxmRequestFailed(QString gameName, int modID, int fileID, QVariant userData, int requestID, QNetworkReply::NetworkError error, const QString &errorString); - void requestsChanged(int queueCount, std::tuple requestsRemaining); + void requestsChanged(const APIStats& stats, const APIUserAccount& user); public slots: - void setRateMax(const QString&, int userId, bool isPremium, std::tuple limits); + void setUserAccount(const APIUserAccount& user); private slots: @@ -534,27 +660,15 @@ private: QString getOldModsURL(QString gameName) const; private: - QNetworkDiskCache *m_DiskCache; - NXMAccessManager *m_AccessManager; - std::list m_ActiveRequest; QQueue m_RequestQueue; - MOBase::VersionInfo m_MOVersion; - PluginContainer *m_PluginContainer; + APIUserAccount m_User; - int m_RemainingDailyRequests; - int m_RemainingHourlyRequests; - int m_MaxDailyRequests; - int m_MaxHourlyRequests; - - int m_UserID; - - bool m_IsPremium; - + APIStats stats() const; }; #endif // NEXUSINTERFACE_H diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 684a53fa..5886d3ee 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -18,8 +18,8 @@ along with Mod Organizer. If not, see . */ #include "nxmaccessmanager.h" - #include "iplugingame.h" +#include "nexusinterface.h" #include "nxmurl.h" #include "report.h" #include "utility.h" @@ -280,14 +280,11 @@ void NXMAccessManager::validateFinished() QString name = credentialsData.value("name").toString(); bool premium = credentialsData.value("is_premium").toBool(); - std::tuple limits(std::make_tuple( - m_ValidateReply->rawHeader("x-rl-daily-remaining").toInt(), - m_ValidateReply->rawHeader("x-rl-daily-limit").toInt(), - m_ValidateReply->rawHeader("x-rl-hourly-remaining").toInt(), - m_ValidateReply->rawHeader("x-rl-hourly-limit").toInt() - )); - - emit credentialsReceived(name, id, premium, limits); + emit credentialsReceived(APIUserAccount() + .id(QString("%1").arg(id)) + .name(name) + .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) + .limits(NexusInterface::parseLimits(m_ValidateReply))); m_ValidateReply->deleteLater(); m_ValidateReply = nullptr; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index e3608f8d..1d23faf9 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -20,7 +20,6 @@ along with Mod Organizer. If not, see . #ifndef NXMACCESSMANAGER_H #define NXMACCESSMANAGER_H - #include #include #include @@ -29,6 +28,8 @@ along with Mod Organizer. If not, see . namespace MOBase { class IPluginGame; } +class APIUserAccount; + /** * @brief access manager extended to handle nxm links **/ @@ -78,7 +79,7 @@ signals: void validateFailed(const QString &message); - void credentialsReceived(const QString &userName, int userId, bool premium, std::tuple limits); + void credentialsReceived(const APIUserAccount& user); private slots: diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 7370662a..6af10c56 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -1,23 +1,26 @@ #include "statusbar.h" +#include "nexusinterface.h" +#include "settings.h" StatusBar::StatusBar(QStatusBar* bar) - : m_bar(bar), m_nexusAPI(new QLabel), m_progress(new QProgressBar) + : m_bar(bar), m_api(new QLabel), m_progress(new QProgressBar) { m_progress->setTextVisible(true); m_progress->setRange(0, 100); - m_progress->setValue(0); - m_progress->setVisible(false); - m_bar->addPermanentWidget(m_nexusAPI); + m_bar->addPermanentWidget(m_api); m_bar->addPermanentWidget(m_progress); + m_api->setObjectName("apistats"); + m_api->setStyleSheet("QLabel{ padding-left: 0.1em; padding-right: 0.1em; }"); + m_bar->clearMessage(); + setProgress(-1); + updateAPI({}, {}); } void StatusBar::setProgress(int percent) { - qDebug().nospace() << "progress: " << percent; - if (percent < 0 || percent >= 100) { m_progress->setVisible(false); } else if (!m_progress->isVisible()) { @@ -25,3 +28,41 @@ void StatusBar::setProgress(int percent) m_progress->setValue(percent); } } + +void StatusBar::updateAPI(const APIStats& stats, const APIUserAccount& user) +{ + m_api->setText( + QString("API: Q: %1 | D: %2 | H: %3") + .arg(stats.requestsQueued) + .arg(user.limits().remainingDailyRequests) + .arg(user.limits().remainingHourlyRequests)); + + QColor textColor; + QColor backgroundColor; + + if (user.type() == APIUserAccountTypes::None) { + backgroundColor = Qt::transparent; + } else if (user.remainingRequests() > 300) { + textColor = "white"; + backgroundColor = Qt::darkGreen; + } else if (user.remainingRequests() < 150) { + textColor = "white"; + backgroundColor = Qt::darkRed; + } else { + textColor = "black"; + backgroundColor = Qt::darkYellow; + } + + QPalette palette = m_api->palette(); + + palette.setColor(QPalette::WindowText, textColor); + palette.setColor(QPalette::Background, backgroundColor); + + m_api->setPalette(palette); + m_api->setAutoFillBackground(true); +} + +void StatusBar::checkSettings(const Settings& settings) +{ + m_api->setVisible(!settings.hideAPICounter()); +} diff --git a/src/statusbar.h b/src/statusbar.h index f3ad3081..47a0e56d 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -4,12 +4,18 @@ #include #include +struct APIStats; +class APIUserAccount; +class Settings; + class StatusBar { public: StatusBar(QStatusBar* bar); void setProgress(int percent); + void updateAPI(const APIStats& stats, const APIUserAccount& user); + void checkSettings(const Settings& settings); private: QStatusBar* m_bar; -- cgit v1.3.1 From a7757e8ba6f5d10feea9e58611bde4dcb911079b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 13 Jun 2019 23:43:51 -0400 Subject: added option to hide the status bar centralized menu visibility into a showMenuBar() function --- src/mainwindow.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++++------ src/mainwindow.h | 8 ++++++-- src/mainwindow.ui | 9 +++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2497b05d..c255759b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -203,6 +203,7 @@ MainWindow::MainWindow(QSettings &initSettings , ui(new Ui::MainWindow) , m_WasVisible(false) , m_menuBarVisible(true) + , m_statusBarVisible(true) , m_linksSeparator(nullptr) , m_Tutorial(this, "MainWindow") , m_OldProfileIndex(-1) @@ -752,6 +753,7 @@ void MainWindow::toolbarMenu_aboutToShow() ui->actionMainMenuToggle->setChecked(ui->menuBar->isVisible()); ui->actionToolBarMainToggle->setChecked(ui->toolBar->isVisible()); + ui->actionStatusBarToggle->setChecked(ui->statusBar->isVisible()); ui->actionToolBarSmallIcons->setChecked(ui->toolBar->iconSize() == SmallToolbarSize); ui->actionToolBarMediumIcons->setChecked(ui->toolBar->iconSize() == MediumToolbarSize); @@ -769,8 +771,7 @@ QMenu* MainWindow::createPopupMenu() void MainWindow::on_actionMainMenuToggle_triggered() { - ui->menuBar->setVisible(!ui->menuBar->isVisible()); - m_menuBarVisible = ui->menuBar->isVisible(); + showMenuBar(!ui->menuBar->isVisible()); } void MainWindow::on_actionToolBarMainToggle_triggered() @@ -778,6 +779,11 @@ void MainWindow::on_actionToolBarMainToggle_triggered() ui->toolBar->setVisible(!ui->toolBar->isVisible()); } +void MainWindow::on_actionStatusBarToggle_triggered() +{ + showStatusBar(!ui->statusBar->isVisible()); +} + void MainWindow::on_actionToolBarSmallIcons_triggered() { setToolbarSize(SmallToolbarSize); @@ -822,6 +828,36 @@ 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 @@ -2096,8 +2132,11 @@ void MainWindow::readSettings() } if (settings.contains("menubar_visible")) { - m_menuBarVisible = settings.value("menubar_visible").toBool(); - ui->menuBar->setVisible(m_menuBarVisible); + showMenuBar(settings.value("menubar_visible").toBool()); + } + + if (settings.contains("statusbar_visible")) { + showStatusBar(settings.value("statusbar_visible").toBool()); } if (settings.contains("window_split")) { @@ -2193,6 +2232,7 @@ void MainWindow::storeSettings(QSettings &settings) { 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()); settings.setValue("window_monitor", QApplication::desktop()->screenNumber(this)); settings.setValue("log_split", ui->topLevelSplitter->saveState()); @@ -6989,8 +7029,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()) { - ui->menuBar->setVisible(true); - m_menuBarVisible = true; + showMenuBar(true); } } diff --git a/src/mainwindow.h b/src/mainwindow.h index e2c6ce8b..88389738 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -326,8 +326,8 @@ private: bool m_WasVisible; // this has to be remembered because by the time storeSettings() is called, - // the window is closed and the menubar is hidden - bool m_menuBarVisible; + // the window is closed and the all bars are hidden + bool m_menuBarVisible, m_statusBarVisible; std::unique_ptr m_statusBar; @@ -651,6 +651,7 @@ private slots: // ui slots void on_actionExit_triggered(); void on_actionMainMenuToggle_triggered(); void on_actionToolBarMainToggle_triggered(); + void on_actionStatusBarToggle_triggered(); void on_actionToolBarSmallIcons_triggered(); void on_actionToolBarMediumIcons_triggered(); void on_actionToolBarLargeIcons_triggered(); @@ -694,6 +695,9 @@ private slots: // ui slots void on_categoriesAndBtn_toggled(bool checked); void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); + + void showMenuBar(bool b); + void showStatusBar(bool b); }; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index bbcb734c..98743b7e 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1397,6 +1397,7 @@ p, li { white-space: pre-wrap; } + @@ -1732,6 +1733,14 @@ p, li { white-space: pre-wrap; } &Menu + + + true + + + St&atus bar + + -- cgit v1.3.1 From 5fb26b2dcbfae9d6a1aaac9d61f017bacf572f09 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 17:15:44 -0400 Subject: made Executable members private, added member function to get and set them --- src/editexecutablesdialog.cpp | 32 ++++----- src/executableslist.cpp | 149 ++++++++++++++++++++++++++++++++---------- src/executableslist.h | 50 ++++++++++---- src/mainwindow.cpp | 50 +++++++------- src/mainwindow.h | 2 +- src/organizercore.cpp | 42 ++++++------ 6 files changed, 215 insertions(+), 110 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 177661ff..67fbff31 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -71,7 +71,7 @@ void EditExecutablesDialog::refreshExecutablesWidget() m_ExecutablesList.getExecutables(current, end); for(; current != end; ++current) { - QListWidgetItem *newItem = new QListWidgetItem(current->m_Title); + QListWidgetItem *newItem = new QListWidgetItem(current->title()); newItem->setTextColor(current->isCustom() ? QColor(Qt::black) : QColor(Qt::darkGray)); ui->executablesListBox->addItem(newItem); } @@ -284,10 +284,10 @@ bool EditExecutablesDialog::executableChanged() if (m_CurrentItem != nullptr) { Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); - QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.title()).toString(); bool forcedLibrariesDirty = false; - auto forcedLibaries = m_Profile->determineForcedLibraries(selectedExecutable.m_Title); + auto forcedLibaries = m_Profile->determineForcedLibraries(selectedExecutable.title()); forcedLibrariesDirty |= !std::equal(forcedLibaries.begin(), forcedLibaries.end(), m_ForcedLibraries.begin(), m_ForcedLibraries.end(), [](const ExecutableForcedLoadSetting &lhs, const ExecutableForcedLoadSetting &rhs) @@ -300,13 +300,13 @@ bool EditExecutablesDialog::executableChanged() forcedLibrariesDirty |= m_Profile->setting("forced_libraries", ui->titleEdit->text() + "/enabled", false).toBool() != ui->forceLoadCheckBox->isChecked(); - return selectedExecutable.m_Title != ui->titleEdit->text() - || selectedExecutable.m_Arguments != ui->argumentsEdit->text() - || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() + return selectedExecutable.title() != ui->titleEdit->text() + || selectedExecutable.arguments() != ui->argumentsEdit->text() + || selectedExecutable.steamAppID() != ui->appIDOverwriteEdit->text() || !storedCustomOverwrite.isEmpty() != ui->newFilesModCheckBox->isChecked() || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->newFilesModBox->currentText()) - || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) - || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) + || selectedExecutable.workingDirectory() != QDir::fromNativeSeparators(ui->workingDirEdit->text()) + || selectedExecutable.binaryInfo().absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) || selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked() || forcedLibrariesDirty ; @@ -376,14 +376,14 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); - ui->titleEdit->setText(selectedExecutable.m_Title); - ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); - ui->argumentsEdit->setText(selectedExecutable.m_Arguments); - ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); + ui->titleEdit->setText(selectedExecutable.title()); + ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath())); + ui->argumentsEdit->setText(selectedExecutable.arguments()); + ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.workingDirectory())); ui->removeButton->setEnabled(selectedExecutable.isCustom()); - ui->overwriteAppIDBox->setChecked(!selectedExecutable.m_SteamAppID.isEmpty()); - if (!selectedExecutable.m_SteamAppID.isEmpty()) { - ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); + ui->overwriteAppIDBox->setChecked(!selectedExecutable.steamAppID().isEmpty()); + if (!selectedExecutable.steamAppID().isEmpty()) { + ui->appIDOverwriteEdit->setText(selectedExecutable.steamAppID()); } else { ui->appIDOverwriteEdit->clear(); } @@ -391,7 +391,7 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur int index = -1; - QString customOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + QString customOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.title()).toString(); if (!customOverwrite.isEmpty()) { index = ui->newFilesModBox->findText(customOverwrite); qDebug("find %s -> %d", qUtf8Printable(customOverwrite), index); diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 4b2e380b..79b17f5b 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -84,7 +84,7 @@ void ExecutablesList::getExecutables(std::vector::const_iterator &be const Executable &ExecutablesList::find(const QString &title) const { for (Executable const &exe : m_Executables) { - if (exe.m_Title == title) { + if (exe.title() == title) { return exe; } } @@ -95,7 +95,7 @@ const Executable &ExecutablesList::find(const QString &title) const Executable &ExecutablesList::find(const QString &title) { for (Executable &exe : m_Executables) { - if (exe.m_Title == title) { + if (exe.title() == title) { return exe; } } @@ -106,7 +106,7 @@ Executable &ExecutablesList::find(const QString &title) Executable &ExecutablesList::findByBinary(const QFileInfo &info) { for (Executable &exe : m_Executables) { - if (info == exe.m_BinaryInfo) { + if (exe.binaryInfo() == info) { return exe; } } @@ -117,7 +117,7 @@ Executable &ExecutablesList::findByBinary(const QFileInfo &info) std::vector::iterator ExecutablesList::findExe(const QString &title) { for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Title == title) { + if (iter->title() == title) { return iter; } } @@ -127,14 +127,14 @@ std::vector::iterator ExecutablesList::findExe(const QString &title) bool ExecutablesList::titleExists(const QString &title) const { - auto test = [&] (const Executable &exe) { return exe.m_Title == title; }; + auto test = [&] (const Executable &exe) { return exe.title() == title; }; return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); } void ExecutablesList::addExecutable(const Executable &executable) { - auto existingExe = findExe(executable.m_Title); + auto existingExe = findExe(executable.title()); if (existingExe != m_Executables.end()) { *existingExe = executable; } else { @@ -157,31 +157,32 @@ void ExecutablesList::updateExecutable(const QString &title, flags &= mask; if (existingExe != m_Executables.end()) { - existingExe->m_Title = title; - existingExe->m_Flags &= ~mask; - existingExe->m_Flags |= flags; + existingExe->setTitle(title); + + auto newFlags = existingExe->flags(); + newFlags &= ~mask; + newFlags |= flags; + + existingExe->setFlags(newFlags); + // for pre-configured executables don't overwrite settings we didn't store if (flags & Executable::CustomExecutable) { if (file.exists()) { // don't overwrite a valid binary with an invalid one - existingExe->m_BinaryInfo = file; + existingExe->setBinaryInfo(file); } + if (dir.exists()) { // don't overwrite a valid working directory with an invalid one - existingExe->m_WorkingDirectory = workingDirectory; + existingExe->setWorkingDirectory(workingDirectory); } - existingExe->m_Arguments = arguments; - existingExe->m_SteamAppID = steamAppID; + existingExe->setArguments(arguments); + existingExe->setSteamAppID(steamAppID); } } else { - Executable newExe; - newExe.m_Title = title; - newExe.m_BinaryInfo = file; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Flags = Executable::CustomExecutable | flags; - m_Executables.push_back(newExe); + m_Executables.push_back({ + title, file, arguments, workingDirectory, steamAppID, + Executable::CustomExecutable | flags}); } } @@ -189,7 +190,7 @@ void ExecutablesList::updateExecutable(const QString &title, void ExecutablesList::remove(const QString &title) { for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->isCustom() && (iter->m_Title == title)) { + if (iter->isCustom() && (iter->title() == title)) { m_Executables.erase(iter); break; } @@ -203,23 +204,105 @@ void ExecutablesList::addExecutableInternal(const QString &title, const QString { QFileInfo file(executableName); if (file.exists()) { - Executable newExe; - newExe.m_BinaryInfo = file; - newExe.m_Title = title; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Flags = Executable::UseApplicationIcon; - m_Executables.push_back(newExe); + m_Executables.push_back({ + title, file, arguments, steamAppID, + workingDirectory, Executable::UseApplicationIcon}); } } -void Executable::showOnToolbar(bool state) +Executable::Executable( + QString title, QFileInfo binaryInfo, QString arguments, + QString steamAppID, QString workingDirectory, Flags flags) : + m_title(std::move(title)), + m_binaryInfo(std::move(binaryInfo)), + m_arguments(std::move(arguments)), + m_steamAppID(std::move(steamAppID)), + m_workingDirectory(std::move(workingDirectory)), + m_flags(flags) +{ +} + +const QString& Executable::title() const +{ + return m_title; +} + +void Executable::setTitle(const QString& s) +{ + m_title = s; +} + +const QFileInfo& Executable::binaryInfo() const +{ + return m_binaryInfo; +} + +void Executable::setBinaryInfo(const QFileInfo& fi) +{ + m_binaryInfo = fi; +} + +const QString& Executable::arguments() const +{ + return m_arguments; +} + +void Executable::setArguments(const QString& s) +{ + m_arguments = s; +} + +const QString& Executable::steamAppID() const +{ + return m_steamAppID; +} + +void Executable::setSteamAppID(const QString& s) +{ + m_steamAppID = s; +} + +const QString& Executable::workingDirectory() const +{ + return m_workingDirectory; +} + +void Executable::setWorkingDirectory(const QString& s) +{ + m_workingDirectory = s; +} + +Executable::Flags Executable::flags() const +{ + return m_flags; +} + +void Executable::setFlags(Flags f) +{ + m_flags = f; +} + +bool Executable::isCustom() const +{ + return m_flags.testFlag(CustomExecutable); +} + +bool Executable::isShownOnToolbar() const +{ + return m_flags.testFlag(ShowInToolbar); +} + +void Executable::setShownOnToolbar(bool state) { if (state) { - m_Flags |= ShowInToolbar; + m_flags |= ShowInToolbar; } else { - m_Flags &= ~ShowInToolbar; + m_flags &= ~ShowInToolbar; } } + +bool Executable::usesOwnIcon() const +{ + return m_flags.testFlag(UseApplicationIcon); +} diff --git a/src/executableslist.h b/src/executableslist.h index 0534c09e..0e43b337 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -32,14 +32,11 @@ namespace MOBase { class IPluginGame; } /*! * @brief Information about an executable **/ -struct Executable { - QString m_Title; - QFileInfo m_BinaryInfo; - QString m_Arguments; - QString m_SteamAppID; - QString m_WorkingDirectory; - - enum Flag { +class Executable +{ +public: + enum Flag + { CustomExecutable = 0x01, ShowInToolbar = 0x02, UseApplicationIcon = 0x04, @@ -47,17 +44,42 @@ struct Executable { AllFlags = 0xff //I know, I know }; - Q_DECLARE_FLAGS(Flags, Flag) + Q_DECLARE_FLAGS(Flags, Flag); + + Executable( + QString title, QFileInfo binaryInfo, QString arguments, + QString steamAppID, QString workingDirectory, Flags flags); + + const QString& title() const; + void setTitle(const QString& s); - Flags m_Flags; + const QFileInfo& binaryInfo() const; + void setBinaryInfo(const QFileInfo& fi); - bool isCustom() const { return m_Flags.testFlag(CustomExecutable); } + const QString& arguments() const; + void setArguments(const QString& s); - bool isShownOnToolbar() const { return m_Flags.testFlag(ShowInToolbar); } + const QString& steamAppID() const; + void setSteamAppID(const QString& s); - void showOnToolbar(bool state); + const QString& workingDirectory() const; + void setWorkingDirectory(const QString& s); - bool usesOwnIcon() const { return m_Flags.testFlag(UseApplicationIcon); } + Flags flags() const; + void setFlags(Flags f); + + bool isCustom() const; + bool isShownOnToolbar() const; + void setShownOnToolbar(bool state); + bool usesOwnIcon() const; + +private: + QString m_title; + QFileInfo m_binaryInfo; + QString m_arguments; + QString m_steamAppID; + QString m_workingDirectory; + Flags m_flags; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 205ab7cc..38b2ed3f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -741,7 +741,7 @@ void MainWindow::updatePinnedExecutables() hasLinks = true; QAction *exeAction = new QAction( - iconForExecutable(iter->m_BinaryInfo.filePath()), iter->m_Title); + iconForExecutable(iter->binaryInfo().filePath()), iter->title()); exeAction->setObjectName(QString("custom__") + iter->m_Title); exeAction->setStatusTip(iter->m_BinaryInfo.filePath()); @@ -1506,17 +1506,17 @@ void MainWindow::startExeAction() QAction *action = qobject_cast(sender()); if (action != nullptr) { const Executable &selectedExecutable(m_OrganizerCore.executablesList()->find(action->text())); - QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); - auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.m_Title); - if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.m_Title)) { + QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.title()).toString(); + auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.title()); + if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.title())) { forcedLibraries.clear(); } m_OrganizerCore.spawnBinary( - selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 - ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_SteamAppID, + selectedExecutable.binaryInfo(), selectedExecutable.arguments(), + selectedExecutable.workingDirectory().length() != 0 + ? selectedExecutable.workingDirectory() + : selectedExecutable.binaryInfo().absolutePath(), + selectedExecutable.steamAppID(), customOverwrite, forcedLibraries); } else { @@ -1792,8 +1792,8 @@ void MainWindow::refreshExecutablesList() std::vector::const_iterator current, end; m_OrganizerCore.executablesList()->getExecutables(current, end); for(int i = 0; current != end; ++current, ++i) { - QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath()); - executablesList->addItem(icon, current->m_Title); + QIcon icon = iconForExecutable(current->binaryInfo().filePath()); + executablesList->addItem(icon, current->title()); model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole); } @@ -2359,17 +2359,17 @@ void MainWindow::on_startButton_clicked() { ui->startButton->setEnabled(false); try { const Executable &selectedExecutable(getSelectedExecutable()); - QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); - auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.m_Title); - if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.m_Title)) { + QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.title()).toString(); + auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.title()); + if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.title())) { forcedLibraries.clear(); } m_OrganizerCore.spawnBinary( - selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 - ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_SteamAppID, + selectedExecutable.binaryInfo(), selectedExecutable.arguments(), + selectedExecutable.workingDirectory().length() != 0 + ? selectedExecutable.workingDirectory() + : selectedExecutable.binaryInfo().absolutePath(), + selectedExecutable.steamAppID(), customOverwrite, forcedLibraries); } catch (...) { @@ -5204,7 +5204,7 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) void MainWindow::linkToolbar() { Executable &exe(getSelectedExecutable()); - exe.showOnToolbar(!exe.isShownOnToolbar()); + exe.setShownOnToolbar(!exe.isShownOnToolbar()); ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); updatePinnedExecutables(); } @@ -5212,7 +5212,7 @@ void MainWindow::linkToolbar() namespace { QString getLinkfile(const QString &dir, const Executable &exec) { - return QDir::fromNativeSeparators(dir) + "/" + exec.m_Title + ".lnk"; + return QDir::fromNativeSeparators(dir) + "/" + exec.title() + ".lnk"; } QString getDesktopLinkfile(const Executable &exec) @@ -5241,12 +5241,12 @@ void MainWindow::addWindowsLink(const ShortcutType mapping) } else { QFileInfo const exeInfo(qApp->applicationFilePath()); // create link - QString executable = QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath()); + QString executable = QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath()); std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); std::wstring parameter = ToWString( - QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.m_Title)); - std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.m_Title)); + QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.title())); + std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.title())); std::wstring iconFile = ToWString(executable); std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath())); @@ -6391,7 +6391,7 @@ void MainWindow::removeFromToolbar() { try { Executable &exe = m_OrganizerCore.executablesList()->find(m_ContextAction->text()); - exe.showOnToolbar(false); + exe.setShownOnToolbar(false); } catch (const std::runtime_error&) { qDebug("executable doesn't exist any more"); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 88389738..b6283a26 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -33,7 +33,7 @@ along with Mod Organizer. If not, see . //Note the commented headers here can be replaced with forward references, //when I get round to cleaning up main.cpp -struct Executable; +class Executable; class CategoryFactory; class LockedDialogBase; class OrganizerCore; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 892162f6..cfbcad39 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -381,15 +381,15 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName) for (; current != end; ++current) { const Executable &item = *current; settings.setArrayIndex(count++); - settings.setValue("title", item.m_Title); + settings.setValue("title", item.title()); settings.setValue("custom", item.isCustom()); settings.setValue("toolbar", item.isShownOnToolbar()); settings.setValue("ownicon", item.usesOwnIcon()); if (item.isCustom()) { - settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); - settings.setValue("arguments", item.m_Arguments); - settings.setValue("workingDirectory", item.m_WorkingDirectory); - settings.setValue("steamAppID", item.m_SteamAppID); + settings.setValue("binary", item.binaryInfo().absoluteFilePath()); + settings.setValue("arguments", item.arguments()); + settings.setValue("workingDirectory", item.workingDirectory()); + settings.setValue("steamAppID", item.steamAppID()); } } settings.endArray(); @@ -1742,12 +1742,12 @@ HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) } return spawnBinaryDirect( - exe.m_BinaryInfo, exe.m_Arguments, + exe.binaryInfo(), exe.arguments(), m_CurrentProfile->name(), - exe.m_WorkingDirectory.length() != 0 - ? exe.m_WorkingDirectory - : exe.m_BinaryInfo.absolutePath(), - exe.m_SteamAppID, + exe.workingDirectory().length() != 0 + ? exe.workingDirectory() + : exe.binaryInfo().absolutePath(), + exe.steamAppID(), "", forcedLibaries); } @@ -1787,12 +1787,12 @@ HANDLE OrganizerCore::startApplication(const QString &executable, } try { const Executable &exe = m_ExecutablesList.findByBinary(binary); - steamAppID = exe.m_SteamAppID; + steamAppID = exe.steamAppID(); customOverwrite - = m_CurrentProfile->setting("custom_overwrites", exe.m_Title) + = m_CurrentProfile->setting("custom_overwrites", exe.title()) .toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.m_Title)) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.m_Title); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); } } catch (const std::runtime_error &) { // nop @@ -1801,19 +1801,19 @@ HANDLE OrganizerCore::startApplication(const QString &executable, // only a file name, search executables list try { const Executable &exe = m_ExecutablesList.find(executable); - steamAppID = exe.m_SteamAppID; + steamAppID = exe.steamAppID(); customOverwrite - = m_CurrentProfile->setting("custom_overwrites", exe.m_Title) + = m_CurrentProfile->setting("custom_overwrites", exe.title()) .toString(); - if (m_CurrentProfile->forcedLibrariesEnabled(exe.m_Title)) { - forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.m_Title); + if (m_CurrentProfile->forcedLibrariesEnabled(exe.title())) { + forcedLibraries = m_CurrentProfile->determineForcedLibraries(exe.title()); } if (arguments == "") { - arguments = exe.m_Arguments; + arguments = exe.arguments(); } - binary = exe.m_BinaryInfo; + binary = exe.binaryInfo(); if (cwd.length() == 0) { - currentDirectory = exe.m_WorkingDirectory; + currentDirectory = exe.workingDirectory(); } } catch (const std::runtime_error &) { qWarning("\"%s\" not set up as executable", -- cgit v1.3.1 From eb8140afadc5aa4e6d1d2611f69dc6e38f469978 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 07:28:02 -0400 Subject: grey out tab names when they have no data remove tabs if they're can't handle the selected mod next/previous now load mods in place without reopening the dialog tab reordering is broken --- src/mainwindow.cpp | 104 +++++++----- src/mainwindow.h | 5 +- src/modinfodialog.cpp | 358 ++++++++++++++++++++++++++-------------- src/modinfodialog.h | 60 ++++--- src/modinfodialogcategories.cpp | 2 + src/modinfodialogconflicts.cpp | 7 +- src/modinfodialogconflicts.h | 3 +- src/modinfodialogesps.cpp | 2 + src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogimages.cpp | 12 +- src/modinfodialogimages.h | 2 +- src/modinfodialognexus.cpp | 2 + src/modinfodialogtab.cpp | 22 ++- src/modinfodialogtab.h | 3 + src/modinfodialogtextfiles.cpp | 2 + 15 files changed, 375 insertions(+), 213 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f9bfaafb..67dc8418 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3223,18 +3223,14 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); - ModInfoDialog dialog( - modInfo, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), - &m_OrganizerCore, &m_PluginContainer, this); - - connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenNext(int)), this, SLOT(modOpenNext(int)), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenPrev(int)), this, SLOT(modOpenPrev(int)), Qt::QueuedConnection); + ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); + dialog.setMod(modInfo); + //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { - dialog.openTab(tab); + dialog.setTab(tab); } dialog.restoreState(m_OrganizerCore.settings()); @@ -3244,16 +3240,6 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, dialog.restoreGeometry(settings.value(key).toByteArray()); } - //If no tab was specified use the first tab from the left based on the user order. - if (tab == -1) { - for (int i = 0; i < dialog.findChild("tabWidget")->count(); ++i) { - if (dialog.findChild("tabWidget")->isTabEnabled(i)) { - dialog.findChild("tabWidget")->setCurrentIndex(i); - break; - } - } - } - dialog.exec(); dialog.saveState(m_OrganizerCore.settings()); settings.setValue(key, dialog.saveGeometry()); @@ -3296,43 +3282,71 @@ void MainWindow::setWindowEnabled(bool enabled) } -void MainWindow::modOpenNext(int tab) +ModInfo::Ptr MainWindow::nextModInList() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); + const QModelIndex start = m_ModListSortProxy->mapFromSource( + m_OrganizerCore.modList()->index(m_ContextRow, 0)); + + auto index = start; + + for (;;) { + index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } + + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())) { // skip overwrite and backups and separators - modOpenNext(tab); - } else { - displayModInformation(m_ContextRow,tab); + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return mod; } + + return {}; } -void MainWindow::modOpenPrev(int tab) +ModInfo::Ptr MainWindow::previousModInList() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - int row = index.row() - 1; - if (row == -1) { - row = m_ModListSortProxy->rowCount() - 1; - } + const QModelIndex start = m_ModListSortProxy->mapFromSource( + m_OrganizerCore.modList()->index(m_ContextRow, 0)); + + auto index = start; + + for (;;) { + int row = index.row() - 1; + if (row == -1) { + row = m_ModListSortProxy->rowCount() - 1; + } + + index = m_ModListSortProxy->index(row, 0); + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } - m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())) { // skip overwrite and backups and separators - modOpenPrev(tab); - } else { - displayModInformation(m_ContextRow,tab); + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); + + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return mod; } + + return {}; } void MainWindow::displayModInformation(const QString &modName, int tab) diff --git a/src/mainwindow.h b/src/mainwindow.h index b6283a26..f204211e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -160,6 +160,9 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } + ModInfo::Ptr nextModInList(); + ModInfo::Ptr previousModInList(); + public slots: void displayColumnSelection(const QPoint &pos); @@ -549,8 +552,6 @@ private slots: void deselectFilters(); void displayModInformation(const QString &modName, int tab); - void modOpenNext(int tab=-1); - void modOpenPrev(int tab=-1); void modRenamed(const QString &oldName, const QString &newName); void modRemoved(const QString &fileName); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index be7d4aa4..ad704ce8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "ui_modinfodialog.h" #include "plugincontainer.h" #include "organizercore.h" +#include "mainwindow.h" #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" @@ -34,23 +35,6 @@ using namespace MOShared; const int max_scan_for_context_menu = 50; - -class ModFileListWidget : public QListWidgetItem { - friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); -public: - ModFileListWidget(const QString &text, int sortValue, QListWidget *parent = 0) - : QListWidgetItem(text, parent, QListWidgetItem::UserType + 1), m_SortValue(sortValue) {} -private: - int m_SortValue; -}; - - -static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS) -{ - return LHS.m_SortValue < RHS.m_SortValue; -} - - bool canPreviewFile( PluginContainer& pluginContainer, bool isArchive, const QString& filename) { @@ -111,74 +95,54 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } +ModInfoDialog::TabInfo::TabInfo(std::unique_ptr tab) + : tab(std::move(tab)), realPos(-1), widget(nullptr) +{ +} + ModInfoDialog::ModInfoDialog( - ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, - PluginContainer *pluginContainer, QWidget *parent) : - TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), - m_ModInfo(modInfo), m_RootPath(modInfo->absolutePath()), - m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), - m_Origin(nullptr) + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : + TutorableDialog("ModInfoDialog", mw), + ui(new Ui::ModInfoDialog), m_mainWindow(mw), + m_core(core), m_plugin(plugin), m_initialTab(-1) { ui->setupUi(this); - auto* ds = m_OrganizerCore->directoryStructure(); - if (ds->originExists(ToWString(m_ModInfo->name()))) { - m_Origin = &ds->getOriginByName(ToWString(m_ModInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = nullptr; - } - } - - this->setWindowTitle(m_ModInfo->name()); - this->setWindowModality(Qt::WindowModal); - auto* sc = new QShortcut(QKeySequence::Delete, this); connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); m_tabs = createTabs(); - bool tabSelected = false; - for (std::size_t i=0; itabWidget->count(); ++i) { + if (static_cast(i) >= m_tabs.size()) { + qCritical() << "mod info dialog has more tabs than expected"; + break; + } + + auto& tabInfo = m_tabs[static_cast(i)]; + tabInfo.widget = ui->tabWidget->widget(i); + tabInfo.caption = ui->tabWidget->tabText(i); + tabInfo.icon = ui->tabWidget->tabIcon(i); + tabInfo.realPos = i; + connect( - m_tabs[i].get(), &ModInfoDialogTab::originModified, + tabInfo.tab.get(), &ModInfoDialogTab::originModified, [&](int originID){ emit originModified(originID); }); connect( - m_tabs[i].get(), &ModInfoDialogTab::modOpen, + tabInfo.tab.get(), &ModInfoDialogTab::modOpen, [&](const QString& name){ - close(); - emit modOpen(name, static_cast(i)); + setMod(name); + update(); }); - - bool enabled = true; - - if (unmanaged) { - enabled = m_tabs[i]->canHandleUnmanaged(); - } else if (m_ModInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { - enabled = m_tabs[i]->canHandleSeparators(); - } - - ui->tabWidget->setTabEnabled(static_cast(i), enabled); - - if (!tabSelected && enabled) { - ui->tabWidget->setCurrentIndex(static_cast(i)); - tabSelected = true; - } - } - - for (auto& tab : m_tabs) { - tab->setMod(m_ModInfo, m_Origin); } } -ModInfoDialog::~ModInfoDialog() -{ - delete ui; -} +ModInfoDialog::~ModInfoDialog() = default; -std::vector> ModInfoDialog::createTabs() +std::vector ModInfoDialog::createTabs() { - std::vector> v; + std::vector v; v.push_back(createTab(TAB_TEXTFILES)); v.push_back(createTab(TAB_INIFILES)); @@ -195,31 +159,206 @@ std::vector> ModInfoDialog::createTabs() int ModInfoDialog::exec() { - refreshLists(); + update(); return TutorableDialog::exec(); } +void ModInfoDialog::setMod(ModInfo::Ptr mod) +{ + m_mod = mod; +} + +void ModInfoDialog::setMod(const QString& name) +{ + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + qCritical() << "failed to resolve mod name " << name; + return; + } + + auto mod = ModInfo::getByIndex(index); + if (!mod) { + qCritical() << "mod by index " << index << " is null"; + return; + } + + setMod(mod); +} + +void ModInfoDialog::setTab(int index) +{ + if (!isVisible()) { + m_initialTab = index; + return; + } + + switchToTab(index); +} + +void ModInfoDialog::update() +{ + setWindowTitle(m_mod->name()); + setTabsVisibility(); + updateTabs(); + feedFiles(); + setTabsColors(); + + if (m_initialTab >= 0) { + switchToTab(m_initialTab); + m_initialTab = -1; + } +} + +void ModInfoDialog::setTabsVisibility() +{ + std::vector visibility(m_tabs.size()); + bool changed = false; + + for (std::size_t i=0; ihasFlag(ModInfo::FLAG_FOREIGN)) { + visible = tabInfo.tab->canHandleUnmanaged(); + } else if (m_mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + visible = tabInfo.tab->canHandleSeparators(); + } + + const auto currentlyVisible = (ui->tabWidget->indexOf(tabInfo.widget) != -1); + + if (visible != currentlyVisible) { + changed = true; + } + + visibility[i] = visible; + } + + if (!changed) { + return; + } + + // remember selection + const int sel = ui->tabWidget->currentIndex(); + + // remove all tabs + ui->tabWidget->clear(); + + // add visible tabs + for (std::size_t i=0; itabWidget->addTab(m_tabs[i].widget, m_tabs[i].icon, m_tabs[i].caption); + + if (static_cast(i) == sel) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + } + } + } +} + +void ModInfoDialog::updateTabs() +{ + auto* origin = getOrigin(); + + for (auto& tabInfo : m_tabs) { + tabInfo.tab->setMod(m_mod, origin); + tabInfo.tab->clear(); + tabInfo.tab->update(); + } +} + +void ModInfoDialog::feedFiles() +{ + const auto rootPath = m_mod->absolutePath(); + + if (rootPath.length() > 0) { + QDirIterator dirIterator(rootPath, QDir::Files, QDirIterator::Subdirectories); + while (dirIterator.hasNext()) { + QString fileName = dirIterator.next(); + + for (auto& tabInfo : m_tabs) { + if (tabInfo.tab->feedFile(rootPath, fileName)) { + break; + } + } + } + } +} + +void ModInfoDialog::setTabsColors() +{ + for (const auto& tabInfo : m_tabs) { + const auto c = tabInfo.tab->hasData() ? + QColor::Invalid : + ui->tabWidget->palette().color(QPalette::Disabled, QPalette::WindowText); + + ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, c); + } +} + +void ModInfoDialog::switchToTab(std::size_t index) +{ + if (index >= m_tabs.size()) { + qCritical() << "tab index " << index << "out of range"; + return; + } + + if (ui->tabWidget->indexOf(m_tabs[index].widget) == -1) { + qCritical() << "can't switch to tab " << index << ", not available"; + return; + } + + ui->tabWidget->setCurrentIndex(m_tabs[index].realPos); +} + +MOShared::FilesOrigin* ModInfoDialog::getOrigin() +{ + MOShared::FilesOrigin* origin = nullptr; + + auto* ds = m_core->directoryStructure(); + if (ds->originExists(ToWString(m_mod->name()))) { + auto* origin = &ds->getOriginByName(ToWString(m_mod->name())); + if (!origin->isDisabled()) { + return origin; + } + } + + return nullptr; +} + void ModInfoDialog::saveState(Settings& s) const { - s.directInterface().setValue("mod_info_tabs", saveTabState()); + //s.directInterface().setValue("mod_info_tabs", saveTabState()); - for (const auto& tab : m_tabs) { - tab->saveState(s); + for (const auto& tabInfo : m_tabs) { + tabInfo.tab->saveState(s); } } void ModInfoDialog::restoreState(const Settings& s) { - restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); + //restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - for (const auto& tab : m_tabs) { - tab->restoreState(s); + for (const auto& tabInfo : m_tabs) { + tabInfo.tab->restoreState(s); } } +QByteArray ModInfoDialog::saveTabState() const +{ + QByteArray result; + /*QDataStream stream(&result, QIODevice::WriteOnly); + stream << ui->tabWidget->count(); + for (int i = 0; i < ui->tabWidget->count(); ++i) { + stream << ui->tabWidget->widget(i)->objectName(); + }*/ + + return result; +} + void ModInfoDialog::restoreTabState(const QByteArray &state) { - QDataStream stream(state); + /*QDataStream stream(state); int count = 0; stream >> count; @@ -232,9 +371,9 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) tabIds.append(tabId); int oldPos = tabIndex(tabId); if (oldPos != -1) { - m_RealTabPos[newPos] = oldPos; + m_realTabPos[newPos] = oldPos; } else { - m_RealTabPos[newPos] = newPos; + m_realTabPos[newPos] = newPos; } } @@ -246,19 +385,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) int oldPos = tabIndex(tabId); tabBar->moveTab(oldPos, newPos); } - ui->tabWidget->blockSignals(false); -} - -QByteArray ModInfoDialog::saveTabState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - stream << ui->tabWidget->count(); - for (int i = 0; i < ui->tabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName(); - } - - return result; + ui->tabWidget->blockSignals(false);*/ } int ModInfoDialog::tabIndex(const QString& tabId) @@ -273,37 +400,17 @@ int ModInfoDialog::tabIndex(const QString& tabId) void ModInfoDialog::onDeleteShortcut() { - for (auto& t : m_tabs) { - if (t->deleteRequested()) { + for (auto& tabInfo : m_tabs) { + if (tabInfo.tab->deleteRequested()) { break; } } } -void ModInfoDialog::refreshLists() -{ - for (auto& tab : m_tabs) { - tab->update(); - } - - if (m_RootPath.length() > 0) { - QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); - - for (auto& tab : m_tabs) { - if (tab->feedFile(m_RootPath, fileName)) { - break; - } - } - } - } -} - void ModInfoDialog::on_closeButton_clicked() { - for (auto& tab : m_tabs) { - if (!tab->canClose()) { + for (auto& tabInfo : m_tabs) { + if (!tabInfo.tab->canClose()) { return; } } @@ -311,31 +418,28 @@ void ModInfoDialog::on_closeButton_clicked() close(); } -void ModInfoDialog::openTab(int tab) -{ - if (ui->tabWidget->isTabEnabled(tab)) { - ui->tabWidget->setCurrentIndex(tab); - } -} - void ModInfoDialog::on_tabWidget_currentChanged(int index) { } void ModInfoDialog::on_nextButton_clicked() { - int currentTab = ui->tabWidget->currentIndex(); - int tab = m_RealTabPos[currentTab]; + auto mod = m_mainWindow->nextModInList(); + if (mod == m_mod) { + return; + } - emit modOpenNext(tab); - this->accept(); + setMod(mod); + update(); } void ModInfoDialog::on_prevButton_clicked() { - int currentTab = ui->tabWidget->currentIndex(); - int tab = m_RealTabPos[currentTab]; + auto mod = m_mainWindow->previousModInList(); + if (mod == m_mod) { + return; + } - emit modOpenPrev(tab); - this->accept(); + setMod(mod); + update(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 020e7958..1cefc71a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -32,7 +32,7 @@ class PluginContainer; class OrganizerCore; class Settings; class ModInfoDialogTab; - +class MainWindow; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); @@ -71,10 +71,7 @@ public: * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog( - ModInfo::Ptr modInfo, - bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, - QWidget *parent = 0); + ModInfoDialog(MainWindow* mw, OrganizerCore* core, PluginContainer* plugin); ~ModInfoDialog(); @@ -92,12 +89,9 @@ public: **/ const int getModID() const; - /** - * @brief open the specified tab in the dialog if it's enabled - * - * @param tab the tab to activate - **/ - void openTab(int tab); + void setMod(ModInfo::Ptr mod); + void setMod(const QString& name); + void setTab(int index); int exec() override; @@ -105,9 +99,6 @@ public: void restoreState(const Settings& s); signals: - void modOpen(const QString &modName, int tab); - void modOpenNext(int tab=-1); - void modOpenPrev(int tab=-1); void originModified(int originID); private slots: @@ -117,27 +108,42 @@ private slots: void on_prevButton_clicked(); private: - Ui::ModInfoDialog *ui; - ModInfo::Ptr m_ModInfo; - std::vector> m_tabs; - QString m_RootPath; - OrganizerCore *m_OrganizerCore; - PluginContainer *m_PluginContainer; - MOShared::FilesOrigin *m_Origin; - std::map m_RealTabPos; - - std::vector> createTabs(); - void refreshLists(); + struct TabInfo + { + std::unique_ptr tab; + int realPos; + QWidget* widget; + QString caption; + QIcon icon; + + TabInfo(std::unique_ptr tab); + }; + + std::unique_ptr ui; + MainWindow* m_mainWindow; + ModInfo::Ptr m_mod; + OrganizerCore* m_core; + PluginContainer* m_plugin; + std::vector m_tabs; + int m_initialTab; + + std::vector createTabs(); void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; + void update(); void onDeleteShortcut(); int tabIndex(const QString &tabId); + MOShared::FilesOrigin* getOrigin(); + void setTabsVisibility(); + void updateTabs(); + void feedFiles(); + void setTabsColors(); + void switchToTab(std::size_t index); template std::unique_ptr createTab(int index) { - return std::make_unique( - *m_OrganizerCore, *m_PluginContainer, this, ui, index); + return std::make_unique(*m_core, *m_plugin, this, ui.get(), index); } }; diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 321c22b8..bce1162b 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -22,6 +22,7 @@ void CategoriesTab::clear() { ui->categories->clear(); ui->primaryCategories->clear(); + setHasData(false); } void CategoriesTab::update() @@ -33,6 +34,7 @@ void CategoriesTab::update() ui->categories->invisibleRootItem(), 0); updatePrimary(); + setHasData(ui->primaryCategories->count() > 0); } bool CategoriesTab::canHandleSeparators() const diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 15bb7ed4..dde00354 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -148,7 +148,7 @@ ConflictsTab::ConflictsTab( void ConflictsTab::update() { - m_general.update(); + setHasData(m_general.update()); m_advanced.update(); } @@ -156,6 +156,7 @@ void ConflictsTab::clear() { m_general.clear(); m_advanced.clear(); + setHasData(false); } void ConflictsTab::saveState(Settings& s) @@ -572,7 +573,7 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::update() +bool GeneralConflictsTab::update() { clear(); @@ -616,6 +617,8 @@ void GeneralConflictsTab::update() ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); ui->noConflictCount->display(numNonConflicting); + + return (numOverwrite > 0 || numOverwritten > 0); } QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index a05682ba..38fa6a74 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -22,7 +22,7 @@ public: void saveState(Settings& s); void restoreState(const Settings& s); - void update(); + bool update(); signals: void modOpen(QString name); @@ -100,7 +100,6 @@ public: QWidget* parent, Ui::ModInfoDialog* ui, int index); void update() override; - void clear() override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index dd4fff0b..d0dcaf2b 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -138,6 +138,7 @@ void ESPsTab::clear() { ui->inactiveESPList->clear(); ui->activeESPList->clear(); + setHasData(false); } bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -158,6 +159,7 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) ui->inactiveESPList->addItem(item); } + setHasData(true); return true; } } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 3e233ccc..dae37f25 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -49,7 +49,9 @@ FileTreeTab::FileTreeTab( void FileTreeTab::clear() { m_fs->setRootPath({}); - //ui->filetree-> + + // always has data; even if the mod is empty, it still has a meta.ini + setHasData(true); } void FileTreeTab::update() diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 332a0984..9a60fc8e 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -105,6 +105,7 @@ void ImagesTab::clear() } static_cast(ui->imagesThumbnails->layout())->addStretch(1); + setHasData(false); } bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -115,7 +116,10 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - add(fullPath); + if (add(fullPath)) { + setHasData(true); + } + return true; } } @@ -123,13 +127,13 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) return false; } -void ImagesTab::add(const QString& fullPath) +bool ImagesTab::add(const QString& fullPath) { QImage image = QImage(fullPath); if (image.isNull()) { qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; - return; + return false; } auto* thumbnail = new ScalableImage(std::move(image)); @@ -140,6 +144,8 @@ void ImagesTab::add(const QString& fullPath) static_cast(ui->imagesThumbnails->layout())->insertWidget( ui->imagesThumbnails->layout()->count() - 1, thumbnail); + + return true; } void ImagesTab::onClicked(const QImage& original) diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 689b8e93..60271da0 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -44,7 +44,7 @@ public: private: ScalableImage* m_image; - void add(const QString& fullPath); + bool add(const QString& fullPath); void onClicked(const QImage& image); }; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 9d51871c..61b868d1 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -48,6 +48,7 @@ void NexusTab::clear() ui->version->clear(); ui->browser->setPage(new NexusTabWebpage(ui->browser)); ui->url->clear(); + setHasData(false); } void NexusTab::update() @@ -88,6 +89,7 @@ void NexusTab::update() (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); updateWebpage(); + setHasData(mod()->getNexusID() >= 0); } void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 1b7fadbb..e50aec29 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -7,7 +7,7 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int index) : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabIndex(index) + m_origin(nullptr), m_tabIndex(index), m_hasData(false) { } @@ -74,6 +74,11 @@ int ModInfoDialogTab::tabIndex() const return m_tabIndex; } +bool ModInfoDialogTab::hasData() const +{ + return m_hasData; +} + OrganizerCore& ModInfoDialogTab::core() { return m_core; @@ -101,6 +106,11 @@ void ModInfoDialogTab::emitModOpen(QString name) emit modOpen(name); } +void ModInfoDialogTab::setHasData(bool b) +{ + m_hasData = b; +} + NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, @@ -115,12 +125,18 @@ void NotesTab::clear() { ui->commentsEdit->clear(); ui->notesEdit->clear(); + setHasData(false); } void NotesTab::update() { - ui->commentsEdit->setText(mod()->comments()); - ui->notesEdit->setText(mod()->notes()); + const auto comments = mod()->comments(); + const auto notes = mod()->notes(); + + ui->commentsEdit->setText(comments); + ui->notesEdit->setText(notes); + + setHasData(!comments.isEmpty() || !notes.isEmpty()); } bool NotesTab::canHandleSeparators() const diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 1f99344f..8fe7d2d4 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -38,6 +38,7 @@ public: MOShared::FilesOrigin* origin() const; int tabIndex() const; + bool hasData() const; signals: void originModified(int originID); @@ -57,6 +58,7 @@ protected: void emitOriginModified(); void emitModOpen(QString name); + void setHasData(bool b); private: OrganizerCore& m_core; @@ -65,6 +67,7 @@ private: ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; int m_tabIndex; + bool m_hasData; }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index fddfafba..bd175c24 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -42,6 +42,7 @@ void GenericFilesTab::clear() { m_list->clear(); select(nullptr); + setHasData(false); } bool GenericFilesTab::canClose() @@ -76,6 +77,7 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (wantsFile(rootPath, fullPath)) { m_list->addItem(new FileListItem(rootPath, fullPath)); + setHasData(true); return true; } } -- cgit v1.3.1 From 4aa59cdc7dd779c7e864a1c4e96c6b52c61879ff Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 12:35:33 -0400 Subject: added modinfodialogfwd.h, mostly for the enum that's used in various places renamed ETabs to ModInfoTabIDs and changed all ints to use the enum instead added ModInfoPtr to avoid having to include modinfo.h just to get ModInfo::Ptr --- src/CMakeLists.txt | 2 ++ src/iuserinterface.h | 5 ++-- src/mainwindow.cpp | 37 +++++++++++++++------------- src/mainwindow.h | 7 +++--- src/modinfodialog.cpp | 60 ++++++++++++++++++++-------------------------- src/modinfodialog.h | 50 ++++++-------------------------------- src/modinfodialogfwd.h | 50 ++++++++++++++++++++++++++++++++++++++ src/modinfodialognexus.cpp | 2 +- src/modinfodialognexus.h | 2 +- src/modinfodialogtab.cpp | 7 +++--- src/modinfodialogtab.h | 20 ++++++++-------- src/organizercore.cpp | 8 +++---- 12 files changed, 132 insertions(+), 118 deletions(-) create mode 100644 src/modinfodialogfwd.h (limited to 'src/mainwindow.h') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1d27f444..929ee296 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -166,6 +166,7 @@ SET(organizer_HDRS modinfodialogconflicts.h modinfodialogesps.h modinfodialogfiletree.h + modinfodialogfwd.h modinfodialogimages.h modinfodialognexus.h modinfodialogtab.h @@ -379,6 +380,7 @@ set(modinfo\\dialog modinfodialogconflicts modinfodialogesps modinfodialogfiletree + modinfodialogfwd modinfodialogimages modinfodialognexus modinfodialogtab diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 034fa029..bba8de2b 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -2,7 +2,7 @@ #define IUSERINTERFACE_H -#include "modinfo.h" +#include "modinfodialogfwd.h" #include "ilockedwaitingforprocess.h" #include #include @@ -29,7 +29,8 @@ public: virtual bool closeWindow() = 0; virtual void setWindowEnabled(bool enabled) = 0; - virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0; + virtual void displayModInformation( + ModInfoPtr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) = 0; virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7ab555fa..a596c542 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3192,7 +3192,8 @@ void MainWindow::overwriteClosed(int) } -void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) +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"); @@ -3227,8 +3228,8 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); //Open the tab first if we want to use the standard indexes of the tabs. - if (tab != -1) { - dialog.setTab(ModInfoDialog::ETabs(tab)); + if (tabID != ModInfoTabIDs::None) { + dialog.setTab(tabID); } dialog.restoreState(m_OrganizerCore.settings()); @@ -3247,7 +3248,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, m_OrganizerCore.modList()->modInfoChanged(modInfo); } - if (m_OrganizerCore.currentProfile()->modEnabled(index) + if (m_OrganizerCore.currentProfile()->modEnabled(modIndex) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); @@ -3258,7 +3259,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, m_OrganizerCore.directoryRefresher()->addModToStructure(m_OrganizerCore.directoryStructure() , modInfo->name() - , m_OrganizerCore.currentProfile()->getModPriority(index) + , m_OrganizerCore.currentProfile()->getModPriority(modIndex) , modInfo->absolutePath() , modInfo->stealFiles() , modInfo->archives()); @@ -3347,7 +3348,7 @@ ModInfo::Ptr MainWindow::previousModInList() return {}; } -void MainWindow::displayModInformation(const QString &modName, int tab) +void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tabID) { unsigned int index = ModInfo::getIndex(modName); if (index == UINT_MAX) { @@ -3356,14 +3357,14 @@ void MainWindow::displayModInformation(const QString &modName, int tab) } ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - displayModInformation(modInfo, index, tab); + displayModInformation(modInfo, index, tabID); } -void MainWindow::displayModInformation(int row, int tab) +void MainWindow::displayModInformation(int row, ModInfoTabIDs tabID) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - displayModInformation(modInfo, row, tab); + displayModInformation(modInfo, row, tabID); } @@ -4048,16 +4049,18 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) try { m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); sourceIdx.column(); - int tab = -1; + + auto tab = ModInfoTabIDs::None; + switch (sourceIdx.column()) { - case ModList::COL_NOTES: tab = ModInfoDialog::TAB_NOTES; break; - case ModList::COL_VERSION: tab = ModInfoDialog::TAB_NEXUS; break; - case ModList::COL_MODID: tab = ModInfoDialog::TAB_NEXUS; break; - case ModList::COL_GAME: tab = ModInfoDialog::TAB_NEXUS; break; - case ModList::COL_CATEGORY: tab = ModInfoDialog::TAB_CATEGORIES; break; - case ModList::COL_FLAGS: tab = ModInfoDialog::TAB_CONFLICTS; break; - default: tab = -1; + case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; + case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; + 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; } + displayModInformation(sourceIdx.row(), tab); // workaround to cancel the editor that might have opened because of // selection-click diff --git a/src/mainwindow.h b/src/mainwindow.h index f204211e..00f15a2b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -151,7 +151,8 @@ public: virtual void disconnectPlugins(); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); + void displayModInformation( + ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; bool confirmExit(); @@ -235,7 +236,7 @@ private: QList findFileInfos(const QString &path, const std::function &filter) const; bool modifyExecutablesDialog(); - void displayModInformation(int row, int tab = -1); + void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None); void testExtractBSA(int modIndex); void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); @@ -551,7 +552,7 @@ private slots: void editCategories(); void deselectFilters(); - void displayModInformation(const QString &modName, int tab); + void displayModInformation(const QString &modName, ModInfoTabIDs tabID); void modRenamed(const QString &oldName, const QString &newName); void modRemoved(const QString &fileName); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4ef010e4..c8ffa35b 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -131,7 +131,7 @@ ModInfoDialog::ModInfoDialog( ModInfo::Ptr mod) : TutorableDialog("ModInfoDialog", mw), ui(new Ui::ModInfoDialog), m_mainWindow(mw), - m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)), + m_core(core), m_plugin(plugin), m_initialTab(ModInfoTabIDs::None), m_arrangingTabs(false) { ui->setupUi(this); @@ -155,16 +155,11 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, - [this, i](int originID) { - onOriginModified(originID); - }); + [this](int originID){ onOriginModified(originID); }); connect( tabInfo.tab.get(), &ModInfoDialogTab::modOpen, - [&](const QString& name){ - setMod(name); - update(); - }); + [&](const QString& name){ setMod(name); update(); }); connect( tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, @@ -172,12 +167,7 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, - [&, i=static_cast(i)] - { - if (i < m_tabs.size()) { - switchToTab(ETabs(m_tabs[i].tab->tabID())); - } - }); + [&, id=tabInfo.tab->tabID()]{ switchToTab(id); }); } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); @@ -187,32 +177,32 @@ ModInfoDialog::ModInfoDialog( ModInfoDialog::~ModInfoDialog() = default; template -std::unique_ptr createTab(ModInfoDialog& d, int index) +std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) { return std::make_unique(ModInfoDialogTabContext( - *d.m_core, *d.m_plugin, &d, d.ui.get(), index, d.m_mod, d.getOrigin())); + *d.m_core, *d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); } std::vector ModInfoDialog::createTabs() { std::vector v; - v.push_back(createTab(*this, TAB_TEXTFILES)); - v.push_back(createTab(*this, TAB_INIFILES)); - v.push_back(createTab(*this, TAB_IMAGES)); - v.push_back(createTab(*this, TAB_ESPS)); - v.push_back(createTab(*this, TAB_CONFLICTS)); - v.push_back(createTab(*this, TAB_CATEGORIES)); - v.push_back(createTab(*this, TAB_NEXUS)); - v.push_back(createTab(*this, TAB_NOTES)); - v.push_back(createTab(*this, TAB_FILETREE)); + v.push_back(createTab(*this, ModInfoTabIDs::TextFiles)); + v.push_back(createTab(*this, ModInfoTabIDs::IniFiles)); + v.push_back(createTab(*this, ModInfoTabIDs::Images)); + v.push_back(createTab(*this, ModInfoTabIDs::Esps)); + v.push_back(createTab(*this, ModInfoTabIDs::Conflicts)); + v.push_back(createTab(*this, ModInfoTabIDs::Categories)); + v.push_back(createTab(*this, ModInfoTabIDs::Nexus)); + v.push_back(createTab(*this, ModInfoTabIDs::Notes)); + v.push_back(createTab(*this, ModInfoTabIDs::Filetree)); return v; } int ModInfoDialog::exec() { - const auto selectFirst = (m_initialTab == -1); + const auto selectFirst = (m_initialTab == ModInfoTabIDs::None); update(true); @@ -252,7 +242,7 @@ void ModInfoDialog::setMod(const QString& name) setMod(mod); } -void ModInfoDialog::setTab(ETabs id) +void ModInfoDialog::setTab(ModInfoTabIDs id) { if (!isVisible()) { m_initialTab = id; @@ -288,9 +278,9 @@ void ModInfoDialog::update(bool firstTime) updateTabs(); - if (m_initialTab >= 0) { + if (m_initialTab != ModInfoTabIDs::None) { switchToTab(m_initialTab); - m_initialTab = ETabs(-1); + m_initialTab = ModInfoTabIDs::None; } if (ui->tabWidget->currentIndex() == oldTab) { @@ -335,11 +325,11 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) // remember selection const int selIndex = ui->tabWidget->currentIndex(); - ETabs sel = ETabs(-1); + auto sel = ModInfoTabIDs::None; for (const auto& tabInfo : m_tabs) { if (tabInfo.realPos == selIndex) { - sel = ETabs(tabInfo.tab->tabID()); + sel = tabInfo.tab->tabID(); break; } } @@ -420,7 +410,7 @@ void ModInfoDialog::setTabsColors() } } -void ModInfoDialog::switchToTab(ETabs id) +void ModInfoDialog::switchToTab(ModInfoTabIDs id) { for (const auto& tabInfo : m_tabs) { if (tabInfo.tab->tabID() == id) { @@ -429,7 +419,8 @@ void ModInfoDialog::switchToTab(ETabs id) } } - qDebug() << "can't switch to tab " << id << ", not available"; + qDebug() + << "can't switch to tab ID " << static_cast(id) << ", not available"; } MOShared::FilesOrigin* ModInfoDialog::getOrigin() @@ -525,7 +516,8 @@ std::vector ModInfoDialog::getOrderedTabNames() const return v; } -void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) +void ModInfoDialog::reAddTabs( + const std::vector& visibility, ModInfoTabIDs sel) { Q_ASSERT(visibility.size() == m_tabs.size()); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 9eb00a3b..effb5d98 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "tutorabledialog.h" #include "filerenamer.h" +#include "modinfodialogfwd.h" namespace Ui { class ModInfoDialog; } namespace MOShared { class FilesOrigin; } @@ -34,32 +35,6 @@ class Settings; class ModInfoDialogTab; class MainWindow; -bool canPreviewFile(PluginContainer& pluginContainer, 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); -bool canUnhideFile(bool isArchive, const QString& filename); - -FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); -FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); - -int naturalCompare(const QString& a, const QString& b); - - -class ElideLeftDelegate : public QStyledItemDelegate -{ -public: - using QStyledItemDelegate::QStyledItemDelegate; - -protected: - void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const - { - QStyledItemDelegate::initStyleOption(o, i); - o->textElideMode = Qt::ElideLeft; - } -}; - - /** * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system @@ -70,21 +45,9 @@ class ModInfoDialog : public MOBase::TutorableDialog template friend std::unique_ptr createTab( - ModInfoDialog& d, int index); + ModInfoDialog& d, ModInfoTabIDs index); public: - enum ETabs { - TAB_TEXTFILES, - TAB_INIFILES, - TAB_IMAGES, - TAB_ESPS, - TAB_CONFLICTS, - TAB_CATEGORIES, - TAB_NEXUS, - TAB_NOTES, - TAB_FILETREE - }; - ModInfoDialog( MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, ModInfo::Ptr mod); @@ -93,7 +56,8 @@ public: void setMod(ModInfo::Ptr mod); void setMod(const QString& name); - void setTab(ETabs id); + + void setTab(ModInfoTabIDs id); int exec() override; @@ -130,7 +94,7 @@ private: OrganizerCore* m_core; PluginContainer* m_plugin; std::vector m_tabs; - ETabs m_initialTab; + ModInfoTabIDs m_initialTab; bool m_arrangingTabs; std::vector createTabs(); @@ -144,8 +108,8 @@ private: void updateTabs(bool becauseOriginChanged=false); void feedFiles(bool becauseOriginChanged); void setTabsColors(); - void switchToTab(ETabs id); - void reAddTabs(const std::vector& visibility, ETabs sel); + void switchToTab(ModInfoTabIDs id); + void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); std::vector getOrderedTabNames() const; bool tryClose(); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h new file mode 100644 index 00000000..9ede766f --- /dev/null +++ b/src/modinfodialogfwd.h @@ -0,0 +1,50 @@ +#ifndef MODINFODIALOGFWD_H +#define MODINFODIALOGFWD_H + +#include "filerenamer.h" + +class ModInfo; +using ModInfoPtr = QSharedPointer; + +enum class ModInfoTabIDs +{ + None = -1, + TextFiles = 0, + IniFiles, + Images, + Esps, + Conflicts, + Categories, + Nexus, + Notes, + Filetree +}; + +class PluginContainer; + +bool canPreviewFile(PluginContainer& pluginContainer, 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); +bool canUnhideFile(bool isArchive, const QString& filename); + +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); + +int naturalCompare(const QString& a, const QString& b); + + +class ElideLeftDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + +protected: + void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const + { + QStyledItemDelegate::initStyleOption(o, i); + o->textElideMode = Qt::ElideLeft; + } +}; + +#endif // MODINFODIALOGFWD_H diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 81546f58..04683c89 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -108,7 +108,7 @@ void NexusTab::firstActivation() updateWebpage(); } -void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +void NexusTab::setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) { cleanup(); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 6478375b..7f894dbf 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -40,7 +40,7 @@ public: void clear() override; void update() override; void firstActivation() override; - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + void setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) override; bool usesOriginFiles() const override; private: diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 554df6df..9748d059 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -2,6 +2,7 @@ #include "ui_modinfodialog.h" #include "texteditor.h" #include "directoryentry.h" +#include "modinfo.h" ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) : ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), @@ -74,7 +75,7 @@ bool ModInfoDialogTab::usesOriginFiles() const return true; } -void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +void ModInfoDialogTab::setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) { m_mod = mod; m_origin = origin; @@ -86,7 +87,7 @@ ModInfo& ModInfoDialogTab::mod() const return *m_mod; } -ModInfo::Ptr ModInfoDialogTab::modPtr() const +ModInfoPtr ModInfoDialogTab::modPtr() const { Q_ASSERT(m_mod); return m_mod; @@ -97,7 +98,7 @@ MOShared::FilesOrigin* ModInfoDialogTab::origin() const return m_origin; } -int ModInfoDialogTab::tabID() const +ModInfoTabIDs ModInfoDialogTab::tabID() const { return m_tabID; } diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index eb574de0..283d9e73 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGTAB_H #define MODINFODIALOGTAB_H -#include "modinfo.h" +#include "modinfodialogfwd.h" #include namespace MOShared { class FilesOrigin; } @@ -18,8 +18,8 @@ struct ModInfoDialogTabContext PluginContainer& plugin; QWidget* parent; Ui::ModInfoDialog* ui; - int id; - ModInfo::Ptr mod; + ModInfoTabIDs id; + ModInfoPtr mod; MOShared::FilesOrigin* origin; ModInfoDialogTabContext( @@ -27,8 +27,8 @@ struct ModInfoDialogTabContext PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, - int id, - ModInfo::Ptr mod, + ModInfoTabIDs id, + ModInfoPtr mod, MOShared::FilesOrigin* origin) : core(core), plugin(plugin), parent(parent), ui(ui), id(id), mod(mod), origin(origin) @@ -96,7 +96,7 @@ public: // derived classes can override this to connect to events on the mod for // examples (see NexusTab), but must call the base class implementation // - virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + virtual void setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin); // this tab should clear its user interface; clear() will always be called // before feedFile() and update() @@ -195,7 +195,7 @@ public: // returns the currently selected mod, can never be empty // - ModInfo::Ptr modPtr() const; + ModInfoPtr modPtr() const; // returns the origin of the selected mod; this can be null for mods that // don't have an origin, like deactivated mods @@ -203,7 +203,7 @@ public: MOShared::FilesOrigin* origin() const; - int tabID() const; + ModInfoTabIDs tabID() const; bool hasData() const; signals: @@ -249,9 +249,9 @@ private: OrganizerCore& m_core; PluginContainer& m_plugin; QWidget* m_parent; - ModInfo::Ptr m_mod; + ModInfoPtr m_mod; MOShared::FilesOrigin* m_origin; - int m_tabID; + ModInfoTabIDs m_tabID; bool m_hasData; bool m_firstActivation; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index e073924b..99ddda1d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -966,8 +966,8 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation(modInfo, modIndex, - ModInfoDialog::TAB_INIFILES); + m_UserInterface->displayModInformation( + modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); m_DownloadManager.markInstalled(fileName); @@ -1033,8 +1033,8 @@ void OrganizerCore::installDownload(int index) "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation(modInfo, modIndex, - ModInfoDialog::TAB_INIFILES); + m_UserInterface->displayModInformation( + modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); -- cgit v1.3.1 From 9166bd9c5bf02484bd7486374e508ca304111f5a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 18:50:28 -0400 Subject: added new Shortcut class, moved stuff that was in MainWindow into it added more error handling and logging, some was missing removed some redundancy of setting the menu icons for add/remove, they're all set when clicking the button anyway --- src/mainwindow.cpp | 177 ++++------------------------- src/mainwindow.h | 12 +- src/shared/util.cpp | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/shared/util.h | 96 ++++++++++++++++ 4 files changed, 442 insertions(+), 163 deletions(-) (limited to 'src/mainwindow.h') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7681b482..f9e4138e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -220,6 +220,9 @@ MainWindow::MainWindow(QSettings &initSettings , m_PluginContainer(pluginContainer) , m_DidUpdateMasterList(false) , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) + , m_LinkToolbar(nullptr) + , m_LinkDesktop(nullptr) + , m_LinkStartMenu(nullptr) { QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); @@ -335,9 +338,9 @@ MainWindow::MainWindow(QSettings &initSettings resizeLists(modListAdjusted, pluginListAdjusted); QMenu *linkMenu = new QMenu(this); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); + m_LinkToolbar = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar and Menu"), this, SLOT(linkToolbar())); + m_LinkDesktop = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); + m_LinkStartMenu = linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); ui->linkButton->setMenu(linkMenu); QMenu *listOptionsMenu = new QMenu(ui->listOptionsBtn); @@ -2397,88 +2400,6 @@ void MainWindow::on_startButton_clicked() { ui->startButton->setEnabled(true); } -static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, - LPCSTR linkFileName, LPCWSTR description, - LPCTSTR iconFileName, int iconNumber, - LPCWSTR currentDirectory) -{ - HRESULT result = E_INVALIDARG; - if ((targetFileName != nullptr) && (wcslen(targetFileName) > 0) && - (arguments != nullptr) && - (linkFileName != nullptr) && (strlen(linkFileName) > 0) && - (description != nullptr) && - (currentDirectory != nullptr)) { - - IShellLink* shellLink; - result = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, - IID_IShellLink, (LPVOID*)&shellLink); - - if (!SUCCEEDED(result)) { - qCritical("failed to create IShellLink instance"); - return result; - } - - result = shellLink->SetPath(targetFileName); - if (!SUCCEEDED(result)) { - qCritical("failed to set target path %ls", targetFileName); - shellLink->Release(); - return result; - } - - result = shellLink->SetArguments(arguments); - if (!SUCCEEDED(result)) { - qCritical("failed to set arguments: %ls", arguments); - shellLink->Release(); - return result; - } - - if (wcslen(description) > 0) { - result = shellLink->SetDescription(description); - if (!SUCCEEDED(result)) { - qCritical("failed to set description: %ls", description); - shellLink->Release(); - return result; - } - } - - if (wcslen(currentDirectory) > 0) { - result = shellLink->SetWorkingDirectory(currentDirectory); - if (!SUCCEEDED(result)) { - qCritical("failed to set working directory: %ls", currentDirectory); - shellLink->Release(); - return result; - } - } - - if (iconFileName != nullptr) { - result = shellLink->SetIconLocation(iconFileName, iconNumber); - if (!SUCCEEDED(result)) { - qCritical("failed to load program icon: %ls %d", iconFileName, iconNumber); - shellLink->Release(); - return result; - } - } - - IPersistFile *persistFile; - result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(result)) { - wchar_t linkFileNameW[MAX_PATH]; - if (MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH) > 0) { - result = persistFile->Save(linkFileNameW, TRUE); - } else { - qCritical("failed to create link: %s", linkFileName); - } - persistFile->Release(); - } else { - qCritical("failed to create IPersistFile instance"); - } - - shellLink->Release(); - } - return result; -} - - bool MainWindow::modifyExecutablesDialog() { bool result = false; @@ -5144,74 +5065,39 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) void MainWindow::linkToolbar() { - Executable &exe(getSelectedExecutable()); + Executable& exe = getSelectedExecutable(); + exe.setShownOnToolbar(!exe.isShownOnToolbar()); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); updatePinnedExecutables(); } -namespace { -QString getLinkfile(const QString &dir, const Executable &exec) +void MainWindow::linkDesktop() { - return QDir::fromNativeSeparators(dir) + "/" + exec.title() + ".lnk"; + env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::Desktop); } -QString getDesktopLinkfile(const Executable &exec) +void MainWindow::linkMenu() { - return getLinkfile(getDesktopDirectory(), exec); + env::Shortcut(getSelectedExecutable()).toggle(env::Shortcut::StartMenu); } -QString getStartMenuLinkfile(const Executable &exec) +void MainWindow::on_linkButton_pressed() { - return getLinkfile(getStartMenuDirectory(), exec); -} -} + const Executable& exe = getSelectedExecutable(); -void MainWindow::addWindowsLink(const ShortcutType mapping) -{ - const Executable &selectedExecutable(getSelectedExecutable()); - QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(), - selectedExecutable); + const QIcon addIcon(":/MO/gui/link"); + const QIcon removeIcon(":/MO/gui/remove"); - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo const exeInfo(qApp->applicationFilePath()); - // create link - QString executable = QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath()); - - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString( - QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.title())); - std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.title())); - std::wstring iconFile = ToWString(executable); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath())); - - if (CreateShortcut(targetFile.c_str() - , parameter.c_str() - , QDir::toNativeSeparators(linkName).toUtf8().constData() - , description.c_str() - , (selectedExecutable.usesOwnIcon() ? iconFile.c_str() : nullptr), 0 - , currentDirectory.c_str()) == 0) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } -} + env::Shortcut shortcut(exe); -void MainWindow::linkDesktop() -{ - addWindowsLink(ShortcutType::Desktop); -} + m_LinkToolbar->setIcon( + exe.isShownOnToolbar() ? removeIcon : addIcon); -void MainWindow::linkMenu() -{ - addWindowsLink(ShortcutType::StartMenu); + m_LinkDesktop->setIcon( + shortcut.exists(env::Shortcut::Desktop) ? removeIcon : addIcon); + + m_LinkStartMenu->setIcon( + shortcut.exists(env::Shortcut::StartMenu) ? removeIcon : addIcon); } void MainWindow::on_actionSettings_triggered() @@ -6476,21 +6362,6 @@ Executable &MainWindow::getSelectedExecutable() return m_OrganizerCore.executablesList()->get(name); } -void MainWindow::on_linkButton_pressed() -{ - const Executable &selectedExecutable(getSelectedExecutable()); - - const QIcon addIcon(":/MO/gui/link"); - const QIcon removeIcon(":/MO/gui/remove"); - - const QFileInfo linkDesktopFile(getDesktopLinkfile(selectedExecutable)); - const QFileInfo linkMenuFile(getStartMenuLinkfile(selectedExecutable)); - - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(selectedExecutable.isShownOnToolbar() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Desktop))->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(static_cast(ShortcutType::StartMenu))->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); -} - void MainWindow::on_showHiddenBox_toggled(bool checked) { m_OrganizerCore.downloadManager()->setShowHidden(checked); diff --git a/src/mainwindow.h b/src/mainwindow.h index 00f15a2b..eee269cf 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -403,18 +403,14 @@ private: MOBase::DelayedFileWriter m_ArchiveListWriter; + QAction* m_LinkToolbar; + QAction* m_LinkDesktop; + QAction* m_LinkStartMenu; + // icon set by the stylesheet, used to remember its original appearance // when painting the count QIcon m_originalNotificationIcon; - enum class ShortcutType { - Toolbar, - Desktop, - StartMenu - }; - - void addWindowsLink(ShortcutType const); - Executable const &getSelectedExecutable() const; Executable &getSelectedExecutable(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 072cee2d..2bf1dba6 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 "error_report.h" +#include "executableslist.h" +#include "instancemanager.h" #include #include @@ -307,10 +309,324 @@ struct COMReleaser } }; + template using COMPtr = std::unique_ptr; +class ShellLinkException {}; + +// 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()) { + critical() << "path cannot be empty"; + throw ShellLinkException(); + } + + 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; + + QDebug critical() + { + return qCritical().noquote().nospace() << "system shortcut: "; + } + + void throwOnFail(HRESULT r, const QString& s) + { + if (FAILED(r)) { + critical() << s << ", " << formatSystemMessageQ(r); + throw ShellLinkException(); + } + } + + 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) { + critical() << "creating IShellLink worked, but pointer is null"; + throw ShellLinkException(); + } + + 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) { + critical() << "querying IPersistFile worked, but pointer is null"; + throw ShellLinkException(); + } + + 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) +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + if (m_target.isEmpty()) { + qCritical() << "system shortcut: target is empty"; + return false; + } + + 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&) + { + } + + return false; +} + +bool Shortcut::remove(Locations loc) +{ + const auto path = shortcutPath(loc); + if (path.isEmpty()) { + return false; + } + + if (!QFile::exists(path)) { + qCritical().nospace().noquote() + << "system shortcut: can't remove '" << path << "', file not found"; + + return false; + } + + if (!QFile::remove(path)) { + qCritical().nospace().noquote() + << "system shortcut: failed to remove '" << path << "'"; + + 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: + qCritical() << "system shortcut: bad location " << loc; + return {}; + } + } + catch(std::exception&) + { + return {}; + } + + return QDir::toNativeSeparators(dir); +} + +QString Shortcut::shortcutFilename() const +{ + if (m_name.isEmpty()) { + qCritical() << "system shortcut: name is empty"; + return {}; + } + + return m_name + ".lnk"; +} + + + class WMI { public: @@ -674,7 +990,7 @@ std::optional Environment::getWindowsFirewall() const if (FAILED(hr) || !rawPolicy) { qCritical() << "CoCreateInstance for NetFwPolicy2 failed, " - << formatSystemMessage(hr); + << formatSystemMessageQ(hr); return {}; } @@ -690,7 +1006,7 @@ std::optional Environment::getWindowsFirewall() const { qCritical() << "get_FirewallEnabled failed, " - << formatSystemMessage(hr); + << formatSystemMessageQ(hr); return {}; } diff --git a/src/shared/util.h b/src/shared/util.h index 4df1d13c..a40fa201 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -29,6 +29,8 @@ along with Mod Organizer. If not, see . #include +class Executable; + namespace MOShared { /// Test if a file (or directory) by the specified name exists @@ -52,6 +54,100 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); 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 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; +}; + + // represents one module // class Module -- cgit v1.3.1