From c7ca9653e9763f397cdb5d60eae5e1788786b8e8 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 8 May 2019 23:02:51 -0500 Subject: Modify LZ4 paths to support building from source --- src/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8b368257..40fe2630 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -301,12 +301,12 @@ INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/game_gamebryo/src/creation ${project_path}/game_features/src ${project_path}/githubpp/src - ${LZ4_ROOT}/include) + ${LZ4_ROOT}/lib) INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS}) LINK_DIRECTORIES(${lib_path} ${ZLIB_ROOT}/lib - ${LZ4_ROOT}/dll) + ${LZ4_ROOT}/bin) EXECUTE_PROCESS( COMMAND git log -1 --format=%h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -- cgit v1.3.1 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/downloadmanager.cpp | 1 + src/mainwindow.cpp | 12 ++--- src/mainwindow.h | 2 +- src/nexusinterface.cpp | 24 +++++++-- src/nexusinterface.h | 6 ++- src/nxmaccessmanager.cpp | 3 +- src/nxmaccessmanager.h | 2 +- src/organizer_en.ts | 124 +++++++++++++++++++++++------------------------ 8 files changed, 97 insertions(+), 77 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 25542ef3..78162f11 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -628,6 +628,7 @@ void DownloadManager::addNXMDownload(const QString &url) info->nexusKey = nxmInfo.key(); info->nexusExpires = nxmInfo.expires(); + info->nexusDownloadUser = nxmInfo.userId(); QObject *test = info; m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.game(), nxmInfo.modId(), nxmInfo.fileId(), this, qVariantFromValue(test), "")); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 389e65ca..02c65266 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -217,7 +217,7 @@ MainWindow::MainWindow(QSettings &initSettings QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory()); QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); ui->setupUi(this); - updateWindowTitle(QString(), false); + updateWindowTitle(QString(), 0, false); languageChange(m_OrganizerCore.settings().language()); @@ -403,10 +403,10 @@ 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&, bool, std::tuple)), - this, SLOT(updateWindowTitle(const QString&, bool))); - connect(NexusInterface::instance(&pluginContainer)->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool, std::tuple)), - NexusInterface::instance(&m_PluginContainer), SLOT(setRateMax(const QString&, bool, std::tuple))); + 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(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); @@ -514,7 +514,7 @@ MainWindow::~MainWindow() } -void MainWindow::updateWindowTitle(const QString &accountName, bool premium) +void MainWindow::updateWindowTitle(const QString &accountName, int, bool premium) { QString title = QString("%1 Mod Organizer v%2").arg( m_OrganizerCore.managedGame()->gameName(), 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); diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 9db0d54e..a4c08f53 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -180,7 +180,13 @@ QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); NexusInterface::NexusInterface(PluginContainer *pluginContainer) - : m_PluginContainer(pluginContainer), m_RemainingDailyRequests(2500), m_RemainingHourlyRequests(100), m_MaxDailyRequests(2500), m_MaxHourlyRequests(100) + : m_PluginContainer(pluginContainer) + , m_RemainingDailyRequests(2500) + , m_RemainingHourlyRequests(100) + , m_MaxDailyRequests(2500) + , m_MaxHourlyRequests(100) + , m_IsPremium(false) + , m_UserID(0) { m_MOVersion = createVersionInfo(); @@ -216,12 +222,14 @@ void NexusInterface::loginCompleted() nextRequest(); } -void NexusInterface::setRateMax(const QString&, bool, std::tuple limits) +void NexusInterface::setRateMax(const QString&, int userId, bool isPremium, std::tuple limits) { 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); } @@ -688,11 +696,17 @@ void NexusInterface::nextRequest() } break; case NXMRequestInfo::TYPE_DOWNLOADURL: { ModRepositoryFileInfo *fileInfo = qobject_cast(qvariant_cast(info.m_UserData)); - if (!fileInfo->nexusKey.isEmpty() && fileInfo->nexusExpires) + if (m_IsPremium) { + 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) { 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 - 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 { + QString warning("Aborting download: Either you clicked on a premium-only link and your account is not premium, " + "or the download link was generated by a different account than the one stored in Mod Organizer."); + qWarning() << warning; + return; + } } break; case NXMRequestInfo::TYPE_ENDORSEMENTS: { url = QString("%1/user/endorsements").arg(info.m_URL); diff --git a/src/nexusinterface.h b/src/nexusinterface.h index ac7f61c5..70c1c9c9 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -463,7 +463,7 @@ signals: public slots: - void setRateMax(const QString&, bool, std::tuple limits); + void setRateMax(const QString&, int userId, bool isPremium, std::tuple limits); private slots: @@ -551,6 +551,10 @@ private: int m_MaxDailyRequests; int m_MaxHourlyRequests; + int m_UserID; + + bool m_IsPremium; + }; #endif // NEXUSINTERFACE_H diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index b1e1ad89..00bb2a91 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -272,6 +272,7 @@ void NXMAccessManager::validateFinished() if (!jdoc.isNull()) { QJsonObject credentialsData = jdoc.object(); if (credentialsData.contains("user_id")) { + int id = credentialsData.value("user_id").toInt(); QString name = credentialsData.value("name").toString(); bool premium = credentialsData.value("is_premium").toBool(); @@ -282,7 +283,7 @@ void NXMAccessManager::validateFinished() m_ValidateReply->rawHeader("x-rl-hourly-limit").toInt() )); - emit credentialsReceived(name, premium, limits); + emit credentialsReceived(name, id, premium, limits); m_ValidateReply->deleteLater(); m_ValidateReply = nullptr; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index da7736cc..54ae14fb 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -78,7 +78,7 @@ signals: void validateFailed(const QString &message); - void credentialsReceived(const QString &userName, bool premium, std::tuple limits); + void credentialsReceived(const QString &userName, int userId, bool premium, std::tuple limits); private slots: diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 28ddc09c..b8f4914f 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -624,261 +624,261 @@ File %3: %4 - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - - + + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -4459,17 +4459,17 @@ p, li { white-space: pre-wrap; } - + Validation failed, please reauthenticate in the Settings -> Nexus tab: %1 - + Could not parse response. Invalid JSON. - + Unknown error. @@ -4477,7 +4477,7 @@ p, li { white-space: pre-wrap; } NXMUrl - + invalid nxm-link: %1 @@ -4485,17 +4485,17 @@ p, li { white-space: pre-wrap; } NexusInterface - + Failed to guess mod id for "%1", please pick the correct one - + empty response - + invalid response -- cgit v1.3.1 From 50fc72ca8d4d904c0260583bd2c31beabcf14f35 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 9 May 2019 12:23:33 -0500 Subject: Fix missing translations --- src/nexusinterface.cpp | 7 +++---- src/organizer_en.ts | 42 ++++++++++++++++++++++-------------------- 2 files changed, 25 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index a4c08f53..c677add0 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -641,7 +641,7 @@ void NexusInterface::nextRequest() } else if (getAccessManager()->validateWaiting()) { return; } else { - qCritical("You must authorize MO2 in Settings -> Nexus to use the Nexus API."); + qCritical() << tr("You must authorize MO2 in Settings -> Nexus to use the Nexus API."); } } @@ -650,7 +650,7 @@ void NexusInterface::nextRequest() QTime time = QTime::currentTime(); QTime targetTime; targetTime.setHMS((time.hour() + 1) % 23, 5, 0); - QString warning("You've exceeded the Nexus API rate limit and requests are now being throttled. " + QString warning = tr("You've exceeded the Nexus API rate limit and requests are now being throttled. " "Your next batch of requests will be available in approximately %1 minutes and %2 seconds."); qWarning() << warning.arg(time.secsTo(targetTime) / 60).arg(time.secsTo(targetTime) % 60); return; @@ -702,9 +702,8 @@ void NexusInterface::nextRequest() url = QString("%1/games/%2/mods/%3/files/%4/download_link?key=%5&expires=%6") .arg(info.m_URL).arg(info.m_GameName).arg(info.m_ModID).arg(info.m_FileID).arg(fileInfo->nexusKey).arg(fileInfo->nexusExpires); } else { - QString warning("Aborting download: Either you clicked on a premium-only link and your account is not premium, " + qWarning() << tr("Aborting download: Either you clicked on a premium-only link and your account is not premium, " "or the download link was generated by a different account than the one stored in Mod Organizer."); - qWarning() << warning; return; } } break; diff --git a/src/organizer_en.ts b/src/organizer_en.ts index b8f4914f..93997047 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -4490,12 +4490,27 @@ p, li { white-space: pre-wrap; } - + + You must authorize MO2 in Settings -> Nexus to use the Nexus API. + + + + + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. + + + + + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. + + + + empty response - + invalid response @@ -5097,10 +5112,6 @@ p, li { white-space: pre-wrap; } Missing files: - Some of your profile-specific game INI files were missing. They will now be copied from the vanilla game folder. You might want double-check your settings. - -Missing files: - @@ -6086,6 +6097,11 @@ Select Show Details option to see the full change-log. Failed to retrieve a Nexus API key! Please try again. A browser window should open asking you to authorize. + + + Restart Mod Organizer? + + In order to finish configuration changes, MO must be restarted. @@ -6097,11 +6113,6 @@ Restart it now? Failed to create "%1", you may not have the necessary permission. path remains unchanged. - - - Restart Mod Organizer? - - SettingsDialog @@ -6362,11 +6373,6 @@ If you use pre-releases, never contact me directly by e-mail or via private mess p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things. Clicking &quot;Connect to Nexus&quot; will open a Nexus webpage to authorise Mod Organizer. You will need to be logged into your Nexus account. The authorisation is stored in the Windows Credential Manager. Your Nexus username and password are not required or stored by Mod Organizer.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> @@ -7286,15 +7292,12 @@ Please go along with the tutorial because some things can't be demonstrated The highlighted button provides hints on solving potential problems MO recognized automatically. - The highlighted button provides hints on solving problems MO recognized automatically. There IS a notification now but you may want to hold off on clearing it until after completing the tutorial. - -There IS a problem now but you may want to hold off on fixing it until after completing the tutorial. @@ -7491,7 +7494,6 @@ Please open the "Nexus"-tab Notifications about the current setup. - Reports potential Problems about the current setup. -- cgit v1.3.1 From 47cca19109d47ccdb70c49f2bdc2e8170cd95ce3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 16 May 2019 23:47:02 -0400 Subject: when removing mods, limit the number of items in the list so the dialog stays at a sane height --- src/mainwindow.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 02c65266..9108464f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2628,18 +2628,34 @@ void MainWindow::modListSectionResized(int logicalIndex, int oldSize, int newSiz void MainWindow::removeMod_clicked() { + const int max_items = 20; + try { QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { QString mods; QStringList modNames; + + int i = 0; for (QModelIndex idx : selection->selectedRows()) { QString name = idx.data().toString(); if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) { continue; } - mods += "
  • " + name + "
  • "; + + // adds an item for the mod name until `i` reaches `max_items`, which + // adds one "..." item; subsequent mods are not shown on the list but + // are still added to `modNames` below so they can be removed correctly + + if (i < max_items) { + mods += "
  • " + name + "
  • "; + } + else if (i == max_items) { + mods += "
  • ...
  • "; + } + modNames.append(ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->name()); + ++i; } if (QMessageBox::question(this, tr("Confirm"), tr("Remove the following mods?
      %1
    ").arg(mods), -- cgit v1.3.1 From 6c470aff829bed12853321c25262b84934242098 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 May 2019 03:53:20 -0400 Subject: changed the top conflict treeview to support multiple selection this required making some changes to both trees because they were sharing m_ConflictsContextItem that was set on right clicks; both trees are now independent hide/unhide is always shown on multiple selection to avoid scanning all the selected items --- src/modinfodialog.cpp | 239 +++++++++++++++++++++++++++++++++++++++----------- src/modinfodialog.h | 18 ++-- src/modinfodialog.ui | 3 + 3 files changed, 201 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 0e41b10e..b0035c02 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1233,18 +1233,38 @@ bool ModInfoDialog::unhideFile(const QString &oldName) } -void ModInfoDialog::hideConflictFile() +void ModInfoDialog::hideConflictFiles() { - if (hideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + bool changed = false; + + for (const auto* item : ui->overwriteTree->selectedItems()) { + if (canHide(item)) { + if (hideFile(item->data(0, Qt::UserRole).toString())) { + changed = true; + } + } + } + + if (changed) { emit originModified(m_Origin->getID()); refreshLists(); } } -void ModInfoDialog::unhideConflictFile() +void ModInfoDialog::unhideConflictFiles() { - if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + bool changed = false; + + for (const auto* item : ui->overwriteTree->selectedItems()) { + if (canUnhide(item)) { + if (unhideFile(item->data(0, Qt::UserRole).toString())) { + changed = true; + } + } + } + + if (changed) { emit originModified(m_Origin->getID()); refreshLists(); } @@ -1309,33 +1329,75 @@ int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo & } } -void ModInfoDialog::openDataFile() -{ - if (m_ConflictsContextItem != nullptr) { - QFileInfo targetInfo(m_ConflictsContextItem->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; - } +void ModInfoDialog::previewOverwriteDataFile() +{ + // the menu item is only shown for a single selection, but check just in case + const auto selection = ui->overwriteTree->selectedItems(); + if (!selection.empty()) { + previewDataFile(selection[0]); + } +} + +void ModInfoDialog::openOverwriteDataFile() +{ + // the menu item is only shown for a single selection, but check just in case + const auto selection = ui->overwriteTree->selectedItems(); + if (!selection.empty()) { + openDataFile(selection[0]); + } +} + +void ModInfoDialog::previewOverwrittenDataFile() +{ + // the overwritten tree only supports single selection, but check just in case + const auto selection = ui->overwrittenTree->selectedItems(); + if (!selection.empty()) { + previewDataFile(selection[0]); + } +} + +void ModInfoDialog::openOverwrittenDataFile() +{ + // the overwritten tree only supports single selection, but check just in case + const auto selection = ui->overwrittenTree->selectedItems(); + if (!selection.empty()) { + openDataFile(selection[0]); + } +} + +void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) +{ + if (!item) { + return; + } + + 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; } } -void ModInfoDialog::previewDataFile() +void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) { - QString fileName = QDir::fromNativeSeparators(m_ConflictsContextItem->data(0, Qt::UserRole).toString()); + if (!item) { + return; + } + + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory @@ -1395,54 +1457,125 @@ void ModInfoDialog::previewDataFile() } } +bool ModInfoDialog::canHide(const QTreeWidgetItem* item) const +{ + if (item->data(1, Qt::UserRole + 2).toBool()) { + // can't hide files from archives + return false; + } -void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) + if (item->text(0).endsWith(ModInfo::s_HiddenExt)) { + // already hidden + return false; + } + + return true; +} + +bool ModInfoDialog::canUnhide(const QTreeWidgetItem* item) const { - m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y()); + if (item->data(1, Qt::UserRole + 2).toBool()) { + // can't unhide files from archives + return false; + } - if (m_ConflictsContextItem != nullptr) { - // offer to hide/unhide file, but not for files from archives - if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - if (m_ConflictsContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFile())); - } else { - menu.addAction(tr("Hide"), this, SLOT(hideConflictFile())); - } + if (!item->text(0).endsWith(ModInfo::s_HiddenExt)) { + // already visible + return false; + } - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + return true; +} - QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString(); - if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); - } +bool ModInfoDialog::canPreview(const QTreeWidgetItem* item) const +{ + const QString fileName = item->data(0, Qt::UserRole).toString(); + if (!m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { + return false; + } + + return true; +} + +void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) +{ + const auto selection = ui->overwriteTree->selectedItems(); + if (selection.empty()) { + return; + } - menu.exec(ui->overwriteTree->mapToGlobal(pos)); + // for a single selection, hide/unhide is not shown for files from + // archives and whether the action is hide or unhide depends on the current + // state + // + // for multiple selection, both actions are shown unconditionally and + // handled in hideConflictFiles() and unhideConflictFiles() + bool enableHide = true; + bool enableUnhide = true; + bool enableOpen = true; + bool enablePreview = true; + + if (selection.size() == 1) { + // this is a single selection + const auto* item = selection[0]; + if (!item) { + return; } + + enableHide = canHide(item); + enableUnhide = canUnhide(item); + enablePreview = canPreview(item); + // open is always enabled + } + else { + // this is a multiple selection, don't show open/preview so users don't open + // a thousand files, but always enable hide/unhide to avoid potentially + // scanning hundreds of selected files to check their state + enableOpen = false; + enablePreview = false; + } + + + QMenu menu; + + if (enableHide) { + menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); + } + + if (enableUnhide) { + menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); + } + + if (enableOpen) { + menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); + } + + if (enablePreview) { + menu.addAction(tr("Preview"), this, SLOT(previewOverwriteDataFile())); } + + menu.exec(ui->overwriteTree->viewport()->mapToGlobal(pos)); } void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) { - m_ConflictsContextItem = ui->overwrittenTree->itemAt(pos.x(), pos.y()); + auto* item = ui->overwrittenTree->itemAt(pos.x(), pos.y()); - if (m_ConflictsContextItem != nullptr) { - if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { + if (item != nullptr) { + if (!item->data(1, Qt::UserRole + 2).toBool()) { QMenu menu; - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + menu.addAction(tr("Open/Execute"), this, SLOT(openOverwrittenDataFile())); - QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString(); - if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + if (canPreview(item)) { + menu.addAction(tr("Preview"), this, SLOT(previewOverwrittenDataFile())); } - menu.exec(ui->overwrittenTree->mapToGlobal(pos)); + menu.exec(ui->overwrittenTree->viewport()->mapToGlobal(pos)); } } } - void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 4c731c00..45745471 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -156,12 +156,14 @@ private: private slots: - void hideConflictFile(); - void unhideConflictFile(); + void hideConflictFiles(); + void unhideConflictFiles(); + void previewOverwriteDataFile(); + void openOverwriteDataFile(); int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); - void previewDataFile(); - void openDataFile(); + void previewOverwrittenDataFile(); + void openOverwrittenDataFile(); void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); @@ -241,13 +243,17 @@ private: QAction *m_HideAction; QAction *m_UnhideAction; - QTreeWidgetItem *m_ConflictsContextItem; - const MOShared::DirectoryEntry *m_Directory; MOShared::FilesOrigin *m_Origin; std::map m_RealTabPos; + bool canHide(const QTreeWidgetItem* item) const; + bool canUnhide(const QTreeWidgetItem* item) const; + bool canPreview(const QTreeWidgetItem* item) const; + + void previewDataFile(const QTreeWidgetItem* item); + void openDataFile(const QTreeWidgetItem* item); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 0211e704..39173a14 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -430,6 +430,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft -- cgit v1.3.1 From 0232435e0a2fed46c864cf241fb2c42d9682bfed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 May 2019 06:26:13 -0400 Subject: disabled unhide menu item in the conflict tree because hidden items are never shown --- src/modinfodialog.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b0035c02..9f35fc59 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1542,9 +1542,11 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); } - if (enableUnhide) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); - } + // disabling this for now, because hidden files are never shows in this list + // at all; if this ever changes, this should be added back + //if (enableUnhide) { + // menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); + //} if (enableOpen) { menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); -- cgit v1.3.1 From b0c5cf146c2d8eeb1e6dc945b069ed16b05154f8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 07:21:41 -0400 Subject: added new FileRenamer class to handle user confirmations when hiding/unhiding files conflict tree hooked up filetree shows the new dialogs, but doesn't handle buttons correctly yet --- src/modinfodialog.cpp | 318 ++++++++++++++++++++++++++++++++++++++++---------- src/modinfodialog.h | 140 +++++++++++++++++++++- 2 files changed, 396 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 9f35fc59..cbfa02fd 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -74,6 +74,193 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS } +FileRenamer::FileRenamer(QWidget* parent, QFlags flags) + : m_parent(parent), m_flags(flags) +{ + // sanity check for flags + if ((m_flags & (HIDE|UNHIDE)) == 0) { + qCritical("renameFile() missing hide flag"); + // doesn't really matter, it's just for text + m_flags = HIDE; + } +} + +FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) +{ + qDebug().nospace() << "renaming " << oldName << " to " << newName; + + if (QFileInfo(newName).exists()) { + qDebug().nospace() << newName << " already exists"; + + // target file already exists, confirm replacement + auto answer = confirmReplace(); + + switch (answer) { + case DECISION_SKIP: { + // user wants to skip this file + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + case DECISION_REPLACE: { + qDebug().nospace() << "removing " << newName; + // user wants to replace the file, so remove it + if (!QFile(newName).remove()) { + qWarning().nospace() << "failed to remove " << newName; + // removal failed, warn the user and allow canceling + if (!removeFailed(newName)) { + qDebug().nospace() << "canceling " << oldName; + // user wants to cancel + return RESULT_CANCEL; + } + + // ignore this file and continue on + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + break; + } + + case DECISION_CANCEL: // fall-through + default: { + // user wants to stop + qDebug().nospace() << "canceling"; + return RESULT_CANCEL; + } + } + } + + // target either didn't exist or was removed correctly + + if (!QFile::rename(oldName, newName)) { + qWarning().nospace() << "failed to rename " << oldName << " to " << newName; + + // renaming failed, warn the user and allow canceling + if (!renameFailed(oldName, newName)) { + // user wants to cancel + qDebug().nospace() << "canceling"; + return RESULT_CANCEL; + } + + // ignore this file and continue on + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + // everything worked + qDebug().nospace() << "successfully renamed " << oldName << " to " << newName; + return RESULT_OK; +} + +FileRenamer::RenameDecision FileRenamer::confirmReplace() +{ + if (m_flags & REPLACE_ALL) { + // user wants to silently replace all + qDebug().nospace() << "user has selected replace all"; + return DECISION_REPLACE; + } + else if (m_flags & REPLACE_NONE) { + // user wants to silently skip all + qDebug().nospace() << "user has selected replace none"; + return DECISION_SKIP; + } + + QString text; + + if (m_flags & HIDE) { + text = QObject::tr("There already is a hidden version of this file. Replace it?"); + } + else if (m_flags & UNHIDE) { + text = QObject::tr("There already is a visible version of this file. Replace it?"); + } + + auto buttons = QMessageBox::Yes | QMessageBox::No; + if (m_flags & MULTIPLE) { + // only show these buttons when there are multiple files to replace + buttons |= QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel; + } + + const auto answer = QMessageBox::question( + m_parent, QObject::tr("Replace file?"), text, buttons); + + switch (answer) { + case QMessageBox::Yes: + qDebug().nospace() << "user wants to replace"; + return DECISION_REPLACE; + + case QMessageBox::No: + qDebug().nospace() << "user wants to skip"; + return DECISION_SKIP; + + case QMessageBox::YesToAll: + qDebug().nospace() << "user wants to replace all"; + // remember the answer + m_flags |= REPLACE_ALL; + return DECISION_REPLACE; + + case QMessageBox::NoToAll: + qDebug().nospace() << "user wants to replace none"; + // remember the answer + m_flags |= REPLACE_NONE; + return DECISION_SKIP; + + case QMessageBox::Cancel: // fall-through + default: + qDebug().nospace() << "user wants to cancel"; + return DECISION_CANCEL; + } +} + +bool FileRenamer::removeFailed(const QString& name) +{ + QMessageBox::StandardButtons buttons = QMessageBox::Ok; + if (m_flags & MULTIPLE) { + // only show cancel for multiple files + buttons |= QMessageBox::Cancel; + } + + const auto answer = QMessageBox::critical( + m_parent, QObject::tr("File operation failed"), + QObject::tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(name), + buttons); + + if (answer == QMessageBox::Cancel) { + // user wants to stop + qDebug().nospace() << "user wants to cancel"; + return false; + } + + // skip this one and continue + qDebug().nospace() << "user wants to skip"; + return true; +} + +bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) +{ + QMessageBox::StandardButtons buttons = QMessageBox::Ok; + if (m_flags & MULTIPLE) { + // only show cancel for multiple files + buttons |= QMessageBox::Cancel; + } + + const auto answer = QMessageBox::critical( + m_parent, QObject::tr("File operation failed"), + QObject::tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName)), + buttons); + + if (answer == QMessageBox::Cancel) { + // user wants to stop + qDebug().nospace() << "user wants to cancel"; + return false; + } + + // skip this one and continue + qDebug().nospace() << "user wants to skip"; + return true; +} + + ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), @@ -1026,11 +1213,14 @@ void ModInfoDialog::renameTriggered() void ModInfoDialog::hideTriggered() { + QFlags flags = FileRenamer::HIDE; + FileRenamer renamer(this, flags); + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); iter != m_FileSelection.constEnd(); ++iter) { QString path = m_FileSystemModel->filePath(*iter); if (!path.endsWith(ModInfo::s_HiddenExt)) { - hideFile(path); + hideFile(renamer, path); } } } @@ -1038,11 +1228,14 @@ void ModInfoDialog::hideTriggered() void ModInfoDialog::unhideTriggered() { + QFlags flags = FileRenamer::UNHIDE; + FileRenamer renamer(this, flags); + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); iter != m_FileSelection.constEnd(); ++iter) { QString path = m_FileSystemModel->filePath(*iter); if (path.endsWith(ModInfo::s_HiddenExt)) { - unhideFile(path); + unhideFile(renamer, path); } } } @@ -1184,90 +1377,95 @@ void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, in emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); } +FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) +{ + const QString newName = oldName + ModInfo::s_HiddenExt; + return renamer.rename(oldName, newName); +} -bool ModInfoDialog::hideFile(const QString &oldName) +FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const QString &oldName) { - QString newName = oldName + ModInfo::s_HiddenExt; + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + return renamer.rename(oldName, newName); +} - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; - } - } else { - return false; - } - } +void ModInfoDialog::changeConflictFiles(bool hide) +{ + bool changed = false; + bool stop = false; - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName))); - return false; + const auto items = ui->overwriteTree->selectedItems(); + + qDebug().nospace() + << (hide ? "hiding" : "unhiding") << " " + << items.size() << " conflict files"; + + QFlags flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE); + if (items.size() > 1) { + flags |= FileRenamer::MULTIPLE; } -} + FileRenamer renamer(this, flags); -bool ModInfoDialog::unhideFile(const QString &oldName) -{ - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; + for (const auto* item : items) { + if (stop) { + break; + } + + auto result = FileRenamer::RESULT_CANCEL; + + if (hide) { + if (!canHide(item)) { + qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; + continue; } + result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); + } else { - return false; + if (!canUnhide(item)) { + qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; + continue; + } + result = unhideFile(renamer, item->data(0, Qt::UserRole).toString()); } - } - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); - return false; - } -} + switch (result) { + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } -void ModInfoDialog::hideConflictFiles() -{ - bool changed = false; + case FileRenamer::RESULT_SKIP: { + // nop + break; + } - for (const auto* item : ui->overwriteTree->selectedItems()) { - if (canHide(item)) { - if (hideFile(item->data(0, Qt::UserRole).toString())) { - changed = true; + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; } } } + qDebug().nospace() << (hide ? "hiding" : "unhiding") << " conflict files done"; + if (changed) { + qDebug().nospace() << "triggering refresh"; emit originModified(m_Origin->getID()); refreshLists(); } } +void ModInfoDialog::hideConflictFiles() +{ + changeConflictFiles(true); +} void ModInfoDialog::unhideConflictFiles() { - bool changed = false; - - for (const auto* item : ui->overwriteTree->selectedItems()) { - if (canUnhide(item)) { - if (unhideFile(item->data(0, Qt::UserRole).toString())) { - changed = true; - } - } - } - - if (changed) { - emit originModified(m_Origin->getID()); - refreshLists(); - } + changeConflictFiles(false); } int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 45745471..6af96c33 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -48,6 +48,141 @@ class QFileSystemModel; class QTreeView; class CategoryFactory; +/** +* Renames individual files and handles dialog boxes to confirm replacements and +* failures with the user +**/ +class FileRenamer +{ +public: + /** + * controls appearance and replacement behaviour; if RENAME_REPLACE_ALL and + * RENAME_REPLACE_NONE are not provided, the user will have the option to + * choose on the first replacement + **/ + enum RenameFlags + { + /** + * this renamer will be used on multiple files, so display additional + * buttons to replace all and for canceling + **/ + MULTIPLE = 0x01, + + /** + * customizes some of the text shown on dialog to mention that files are + * being hidden + **/ + HIDE = 0x02, + + /** + * customizes some of the text shown on dialog to mention that files are + * being unhidden + **/ + UNHIDE = 0x04, + + /** + * silently replaces all existing files + **/ + REPLACE_ALL = 0x08, + + /** + * silently skips all existing files + **/ + REPLACE_NONE = 0x10, + }; + + + /** result of a single rename + * + **/ + enum RenameResults + { + /** + * the user skipped this file + */ + RESULT_SKIP, + + /** + * the file was successfully renamed + */ + RESULT_OK, + + /** + * the user wants to cancel + */ + RESULT_CANCEL + }; + + + /** + * @param parent Parent widget for dialog boxes + **/ + FileRenamer(QWidget* parent, QFlags flags); + + /** + * renames the given file + * @param oldName current filename + * @param newName new filename + * @return whether the file was renamed, skipped or the user wants to cancel + **/ + RenameResults rename(const QString& oldName, const QString& newName); + +private: + /** + *user's decision when replacing + **/ + enum RenameDecision + { + /** + * replace the file + **/ + DECISION_REPLACE, + + /** + * skip the file + **/ + DECISION_SKIP, + + /** + * cancel the whole thing + **/ + DECISION_CANCEL + }; + + /** + * parent widget for dialog boxes + **/ + QWidget* m_parent; + + /** + * flags + **/ + QFlags m_flags; + + /** + * asks the user to replace an existing file, may return early if the user + * has already selected to replace all/none + * @return whether to replace, skip or cancel + **/ + RenameDecision confirmReplace(); + + /** + * removal of a file failed, ask the user to continue or cancel + * @param name The name of the file that failed to be removed + * @return true to continue, false to stop + **/ + bool removeFailed(const QString& name); + + /** + * renaming a file failed, ask the user to continue or cancel + * @param oldName current filename + * @param newName new filename + * @return true to continue, false to stop + **/ + bool renameFailed(const QString& oldName, const QString& newName); +}; + + /** * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system @@ -147,8 +282,8 @@ private: void openIniFile(const QString &fileName); bool allowNavigateFromTXT(); bool allowNavigateFromINI(); - bool hideFile(const QString &oldName); - bool unhideFile(const QString &oldName); + FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); + FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); void addCheckedCategories(QTreeWidgetItem *tree); void refreshPrimaryCategoriesBox(); @@ -254,6 +389,7 @@ private: void previewDataFile(const QTreeWidgetItem* item); void openDataFile(const QTreeWidgetItem* item); + void changeConflictFiles(bool hide); }; #endif // MODINFODIALOG_H -- cgit v1.3.1 From 2920a707c7018549616e7d0719385c915ed4d89a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 08:19:57 -0400 Subject: added filename to file replace confirmation dialog text hooked up filetree to FileRenamer fixed filetree context menu positioning being offset vertically --- src/modinfodialog.cpp | 136 ++++++++++++++++++++++++++++++++++++-------------- src/modinfodialog.h | 14 ++++-- 2 files changed, 107 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index cbfa02fd..572c018b 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -93,7 +93,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt qDebug().nospace() << newName << " already exists"; // target file already exists, confirm replacement - auto answer = confirmReplace(); + auto answer = confirmReplace(newName); switch (answer) { case DECISION_SKIP: { @@ -153,7 +153,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt return RESULT_OK; } -FileRenamer::RenameDecision FileRenamer::confirmReplace() +FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) { if (m_flags & REPLACE_ALL) { // user wants to silently replace all @@ -169,10 +169,10 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace() QString text; if (m_flags & HIDE) { - text = QObject::tr("There already is a hidden version of this file. Replace it?"); + text = QObject::tr("The hidden file \"%1\" already exists. Replace it?").arg(newName); } else if (m_flags & UNHIDE) { - text = QObject::tr("There already is a visible version of this file. Replace it?"); + text = QObject::tr("The visible file \"%1\" already exists. Replace it?").arg(newName); } auto buttons = QMessageBox::Yes | QMessageBox::No; @@ -1213,31 +1213,81 @@ void ModInfoDialog::renameTriggered() void ModInfoDialog::hideTriggered() { - QFlags flags = FileRenamer::HIDE; - FileRenamer renamer(this, flags); - - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); - iter != m_FileSelection.constEnd(); ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (!path.endsWith(ModInfo::s_HiddenExt)) { - hideFile(renamer, path); - } - } + changeFiletreeVisibility(true); } void ModInfoDialog::unhideTriggered() { - QFlags flags = FileRenamer::UNHIDE; + changeFiletreeVisibility(false); +} + +void ModInfoDialog::changeFiletreeVisibility(bool hide) +{ + bool changed = false; + bool stop = false; + + qDebug().nospace() + << (hide ? "hiding" : "unhiding") << " " + << m_FileSelection.size() << " filetree files"; + + QFlags flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE); + if (m_FileSelection.size() > 1) { + flags |= FileRenamer::MULTIPLE; + } + FileRenamer renamer(this, flags); - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); - iter != m_FileSelection.constEnd(); ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (path.endsWith(ModInfo::s_HiddenExt)) { - unhideFile(renamer, path); + for (const auto& index : m_FileSelection) { + if (stop) { + break; + } + + const QString path = m_FileSystemModel->filePath(index); + auto result = FileRenamer::RESULT_CANCEL; + + if (hide) { + if (!canHideFile(false, path)) { + qDebug().nospace() << "cannot hide " << path << ", skipping"; + continue; + } + result = hideFile(renamer, path); + + } else { + if (!canUnhideFile(false, path)) { + qDebug().nospace() << "cannot unhide " << path << ", skipping"; + continue; + } + result = unhideFile(renamer, path); + } + + switch (result) { + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } + + case FileRenamer::RESULT_SKIP: { + // nop + break; + } + + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; + } } } + + qDebug().nospace() << (hide ? "hiding" : "unhiding") << " filetree files done"; + + if (changed) { + qDebug().nospace() << "triggering refresh"; + emit originModified(m_Origin->getID()); + refreshLists(); + } } @@ -1322,7 +1372,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) m_FileSelection.clear(); m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); } - menu.exec(ui->fileTree->mapToGlobal(pos)); + menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); } @@ -1389,7 +1439,7 @@ FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const return renamer.rename(oldName, newName); } -void ModInfoDialog::changeConflictFiles(bool hide) +void ModInfoDialog::changeConflictFilesVisibility(bool hide) { bool changed = false; bool stop = false; @@ -1415,14 +1465,14 @@ void ModInfoDialog::changeConflictFiles(bool hide) auto result = FileRenamer::RESULT_CANCEL; if (hide) { - if (!canHide(item)) { + if (!canHideConflictItem(item)) { qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; continue; } result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); } else { - if (!canUnhide(item)) { + if (!canUnhideConflictItem(item)) { qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; continue; } @@ -1460,12 +1510,12 @@ void ModInfoDialog::changeConflictFiles(bool hide) void ModInfoDialog::hideConflictFiles() { - changeConflictFiles(true); + changeConflictFilesVisibility(true); } void ModInfoDialog::unhideConflictFiles() { - changeConflictFiles(false); + changeConflictFilesVisibility(false); } int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) @@ -1597,7 +1647,7 @@ void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - // what we have is an absolute path to the file in its actual location (for the primary origin) + // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory // we need to look in the virtual directory for the file to make sure the info is up to date. @@ -1655,14 +1705,14 @@ void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) } } -bool ModInfoDialog::canHide(const QTreeWidgetItem* item) const +bool ModInfoDialog::canHideFile(bool isArchive, const QString& filename) const { - if (item->data(1, Qt::UserRole + 2).toBool()) { + if (isArchive) { // can't hide files from archives return false; } - if (item->text(0).endsWith(ModInfo::s_HiddenExt)) { + if (filename.endsWith(ModInfo::s_HiddenExt)) { // already hidden return false; } @@ -1670,14 +1720,14 @@ bool ModInfoDialog::canHide(const QTreeWidgetItem* item) const return true; } -bool ModInfoDialog::canUnhide(const QTreeWidgetItem* item) const +bool ModInfoDialog::canUnhideFile(bool isArchive, const QString& filename) const { - if (item->data(1, Qt::UserRole + 2).toBool()) { + if (isArchive) { // can't unhide files from archives return false; } - if (!item->text(0).endsWith(ModInfo::s_HiddenExt)) { + if (!filename.endsWith(ModInfo::s_HiddenExt)) { // already visible return false; } @@ -1685,7 +1735,17 @@ bool ModInfoDialog::canUnhide(const QTreeWidgetItem* item) const return true; } -bool ModInfoDialog::canPreview(const QTreeWidgetItem* item) const +bool ModInfoDialog::canHideConflictItem(const QTreeWidgetItem* item) const +{ + return canHideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); +} + +bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const +{ + return canUnhideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); +} + +bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const { const QString fileName = item->data(0, Qt::UserRole).toString(); if (!m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { @@ -1720,9 +1780,9 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po return; } - enableHide = canHide(item); - enableUnhide = canUnhide(item); - enablePreview = canPreview(item); + enableHide = canHideConflictItem(item); + enableUnhide = canUnhideConflictItem(item); + enablePreview = canPreviewConflictItem(item); // open is always enabled } else { @@ -1767,7 +1827,7 @@ void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint & menu.addAction(tr("Open/Execute"), this, SLOT(openOverwrittenDataFile())); - if (canPreview(item)) { + if (canPreviewConflictItem(item)) { menu.addAction(tr("Preview"), this, SLOT(previewOverwrittenDataFile())); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6af96c33..66c50be5 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -164,7 +164,7 @@ private: * has already selected to replace all/none * @return whether to replace, skip or cancel **/ - RenameDecision confirmReplace(); + RenameDecision confirmReplace(const QString& newName); /** * removal of a file failed, ask the user to continue or cancel @@ -383,13 +383,17 @@ private: std::map m_RealTabPos; - bool canHide(const QTreeWidgetItem* item) const; - bool canUnhide(const QTreeWidgetItem* item) const; - bool canPreview(const QTreeWidgetItem* item) const; + bool canHideConflictItem(const QTreeWidgetItem* item) const; + bool canUnhideConflictItem(const QTreeWidgetItem* item) const; + bool canPreviewConflictItem(const QTreeWidgetItem* item) const; void previewDataFile(const QTreeWidgetItem* item); void openDataFile(const QTreeWidgetItem* item); - void changeConflictFiles(bool hide); + void changeConflictFilesVisibility(bool hide); + void changeFiletreeVisibility(bool hide); + + bool canHideFile(bool isArchive, const QString& filename) const; + bool canUnhideFile(bool isArchive, const QString& filename) const; }; #endif // MODINFODIALOG_H -- cgit v1.3.1 From 2c30d2d0e92e7e7ad5a1209a0efd81d39b353a26 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 08:38:05 -0400 Subject: fixed filetree context menu for multiple selection --- src/modinfodialog.cpp | 68 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 572c018b..eb91f73d 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1343,35 +1343,75 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); m_FileSelection = selectionModel->selectedRows(0); -// m_FileSelection = ui->fileTree->indexAt(pos); QMenu menu(ui->fileTree); menu.addAction(m_NewFolderAction); - bool hasFiles = false; + if (selectionModel->hasSelection()) { + bool enableOpen = true; + bool enableRename = true; + bool enableDelete = true; + bool enableHide = true; + bool enableUnhide = true; - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; + if (m_FileSelection.size() == 1) { + // single selection + + // only enable open action if a file is selected + bool hasFiles = false; + + foreach(QModelIndex idx, m_FileSelection) { + if (m_FileSystemModel->fileInfo(idx).isFile()) { + hasFiles = true; + break; + } + } + + if (!hasFiles) { + enableOpen = false; + } + + const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + + if (!canHideFile(false, fileName)) { + enableHide = false; + } + + if (!canUnhideFile(false, fileName)) { + enableUnhide = false; + } + } else { + // this is a multiple selection, don't show open action so users don't open + // a thousand files, but always enable hide/unhide to avoid potentially + // scanning hundreds of selected files to check their state + enableOpen = false; + enableRename = false; } - } - if (selectionModel->hasSelection()) { - if (hasFiles) { + if (enableOpen) { menu.addAction(m_OpenAction); } - menu.addAction(m_RenameAction); - menu.addAction(m_DeleteAction); - if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(m_UnhideAction); - } else { + + if (enableRename) { + menu.addAction(m_RenameAction); + } + + if (enableDelete) { + menu.addAction(m_DeleteAction); + } + + if (enableHide) { menu.addAction(m_HideAction); } + + if (enableUnhide) { + menu.addAction(m_UnhideAction); + } } else { m_FileSelection.clear(); m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); } + menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); } -- cgit v1.3.1 From a1f1656c362118b54a3ab4d40af42fa30a1d10ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 08:53:59 -0400 Subject: re-enabled unhide menu item in the conflict tree since hidden items can actually appear --- src/modinfodialog.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index eb91f73d..f7910b3c 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1840,11 +1840,11 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); } - // disabling this for now, because hidden files are never shows in this list - // at all; if this ever changes, this should be added back - //if (enableUnhide) { - // menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); - //} + // 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())); + } if (enableOpen) { menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); -- cgit v1.3.1 From eb203e4c2a7ef8d7e9efe43b9e5df0c8b2253ea7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 09:06:09 -0400 Subject: if the number of selected items is low, check them to accurately show the hide/unhide items --- src/modinfodialog.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f7910b3c..555b62db 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -73,6 +73,10 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS return LHS.m_SortValue < RHS.m_SortValue; } +// if there are more than 50 selected items in the conflict tree or filetree, +// don't bother checking whether they're visible, just show both menu items +const int max_scan_for_visibility = 50; + FileRenamer::FileRenamer(QWidget* parent, QFlags flags) : m_parent(parent), m_flags(flags) @@ -1382,10 +1386,33 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) } } else { // this is a multiple selection, don't show open action so users don't open - // a thousand files, but always enable hide/unhide to avoid potentially - // scanning hundreds of selected files to check their state + // a thousand files enableOpen = false; enableRename = false; + + if (m_FileSelection.size() < max_scan_for_visibility) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; + + for (const auto& index : m_FileSelection) { + const QString fileName = m_FileSystemModel->fileName(index); + + if (canHideFile(false, fileName)) { + enableHide = true; + } + + if (canUnhideFile(false, fileName)) { + enableUnhide = true; + } + + if (enableHide && enableUnhide) { + // found both, no need to check more + break; + } + } + } } if (enableOpen) { @@ -1827,10 +1854,31 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po } else { // this is a multiple selection, don't show open/preview so users don't open - // a thousand files, but always enable hide/unhide to avoid potentially - // scanning hundreds of selected files to check their state + // a thousand files enableOpen = false; enablePreview = false; + + if (selection.size() < max_scan_for_visibility) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; + + for (const auto* item : selection) { + if (canHideConflictItem(item)) { + enableHide = true; + } + + if (canUnhideConflictItem(item)) { + enableUnhide = true; + } + + if (enableHide && enableUnhide) { + // found both, no need to check more + break; + } + } + } } -- cgit v1.3.1 From e10fa6e1f3e291144ffc0a953f81fff0fde17fac Mon Sep 17 00:00:00 2001 From: LostDragonist Date: Sun, 19 May 2019 11:02:35 -0500 Subject: Fix horizontal scrollbar width for dracula theme --- src/stylesheets/dracula.qss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/stylesheets/dracula.qss b/src/stylesheets/dracula.qss index 385fca16..ac0119b0 100644 --- a/src/stylesheets/dracula.qss +++ b/src/stylesheets/dracula.qss @@ -280,12 +280,18 @@ QMenu::separator { /* * Scroll bar */ -QScrollBar { +QScrollBar:vertical { background-color: transparent; margin: 0; height: 1px; width: 12px; } +QScrollBar:horizontal { + background-color: transparent; + margin: 0; + height: 12px; + width: 1px; +} QScrollBar::handle { border: 1px solid #555555; border-radius: 4px; -- cgit v1.3.1 From a2b1f6a3604d8a2e4bc47c2b91fb58e6d5b45af6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 16:11:10 -0400 Subject: added third tree with non-conflicting files --- src/modinfodialog.cpp | 11 ++++++++++- src/modinfodialog.ui | 50 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 555b62db..3c68f4e8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -501,6 +501,7 @@ void ModInfoDialog::refreshLists() ui->overwriteTree->clear(); ui->overwrittenTree->clear(); + ui->noconflictTree->clear(); if (m_Origin != nullptr) { std::vector files = m_Origin->getFiles(); @@ -535,7 +536,15 @@ void ModInfoDialog::refreshLists() } ui->overwriteTree->addTopLevelItem(item); ++numOverwrite; - } else {// otherwise don't display the file + } else {// otherwise, put the file in the nonconflict tree + QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); + item->setData(0, Qt::UserRole, fileName); + if (archive) { + QFont font = item->font(0); + font.setItalic(true); + item->setFont(0, font); + } + ui->noconflictTree->addTopLevelItem(item); ++numNonConflicting; } } else { diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 39173a14..25fce40c 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -526,22 +526,50 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + - - - Non-Conflicted files - - + + + + + <html><head/><body><p>The following files have no conflicts</p></body></html> + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + - - - QFrame::Sunken + + + Qt::CustomContextMenu - - QLCDNumber::Flat + + Qt::ElideLeft + + + true + + + true + + + 1 + + + File + + -- cgit v1.3.1 From 00f8cbff05c3f548baba59b0cb525205cc1d80e3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 17:38:17 -0400 Subject: expandable sections in the conflict tab --- src/modinfodialog.cpp | 65 ++++++++++++++- src/modinfodialog.h | 21 +++++ src/modinfodialog.ui | 218 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 237 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 3c68f4e8..3f8b58a9 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -265,6 +265,61 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) } +ExpanderWidget::ExpanderWidget() + : m_button(nullptr), m_content(nullptr) +{ +} + +ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content) + : ExpanderWidget() +{ + set(button, content); +} + +void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) +{ + m_button = button; + m_content = content; + + m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + m_button->setCheckable(true); + + if (o) + open(); + else + close(); + + QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); +} + +void ExpanderWidget::open() +{ + m_button->setArrowType(Qt::DownArrow); + m_button->setChecked(false); + m_content->show(); +} + +void ExpanderWidget::close() +{ + m_button->setArrowType(Qt::RightArrow); + m_button->setChecked(false); + m_content->hide(); +} + +void ExpanderWidget::toggle() +{ + if (opened()) + close(); + else + open(); +} + +bool ExpanderWidget::opened() const +{ + return m_content->isVisible(); +} + + ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), @@ -378,6 +433,10 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo if (ui->tabWidget->currentIndex() == TAB_NEXUS) { activateNexusTab(); } + + m_overwriteExpander.set(ui->overwriteExpander, ui->overwriteTree, true); + m_overwrittenExpander.set(ui->overwrittenExpander, ui->overwrittenTree, true); + m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); } @@ -501,7 +560,7 @@ void ModInfoDialog::refreshLists() ui->overwriteTree->clear(); ui->overwrittenTree->clear(); - ui->noconflictTree->clear(); + ui->noConflictTree->clear(); if (m_Origin != nullptr) { std::vector files = m_Origin->getFiles(); @@ -520,7 +579,7 @@ void ModInfoDialog::refreshLists() } altString << m_Directory->getOriginByID(altIter->first).getName(); } - QStringList fields(relativeName.prepend("...")); + QStringList fields(relativeName); fields.append(ToQString(altString.str())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); @@ -544,7 +603,7 @@ void ModInfoDialog::refreshLists() font.setItalic(true); item->setFont(0, font); } - ui->noconflictTree->addTopLevelItem(item); + ui->noConflictTree->addTopLevelItem(item); ++numNonConflicting; } } else { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 66c50be5..731611d2 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -183,6 +183,25 @@ private: }; +class ExpanderWidget +{ +public: + ExpanderWidget(); + ExpanderWidget(QToolButton* button, QWidget* content); + + void set(QToolButton* button, QWidget* content, bool opened=false); + + void open(); + void close(); + void toggle(); + bool opened() const; + +private: + QToolButton* m_button; + QWidget* m_content; +}; + + /** * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system @@ -383,6 +402,8 @@ private: std::map m_RealTabPos; + ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; + bool canHideConflictItem(const QTreeWidgetItem* item) const; bool canUnhideConflictItem(const QTreeWidgetItem* item) const; bool canPreviewConflictItem(const QTreeWidgetItem* item) const; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 25fce40c..0d96f621 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -400,28 +400,57 @@ Most mods do not have optional esps, so chances are good you are looking at an e Conflicts - + + + 0 + - + - - - The following conflicted files are provided by this mod - - - - - - - QFrame::Sunken - - - 1 - - - QLCDNumber::Flat - - + + + + + + 0 + 0 + + + + border: none + + + The following conflicted files are provided by this mod + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QFrame::Sunken + + + 1 + + + QLCDNumber::Flat + + + + @@ -470,23 +499,49 @@ Most mods do not have optional esps, so chances are good you are looking at an e - - - - - The following conflicted files are provided by other mods - - - + - - - QFrame::Sunken - - - QLCDNumber::Flat - - + + + + + + 0 + 0 + + + + border: none + + + The following conflicted files are provided by other mods + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + @@ -526,16 +581,38 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + - + - + + + + 0 + 0 + + + + border: none; + - <html><head/><body><p>The following files have no conflicts</p></body></html> + The following files have no conflicts + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -548,32 +625,45 @@ Most mods do not have optional esps, so chances are good you are looking at an e - - - - Qt::CustomContextMenu - - - Qt::ElideLeft - - - true - - - true - - - 1 - - - - File - - - - + + + + Qt::CustomContextMenu + + + Qt::ElideLeft + + + true + + + true + + + 1 + + + + File + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + -- cgit v1.3.1 From c5f61733850d6737814041f645a009e1cc60a7ed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 17:58:00 -0400 Subject: made expander use the full width context menu for noconflict tree --- src/modinfodialog.cpp | 37 +++++++++++++++++++++++++++++++++-- src/modinfodialog.h | 4 ++++ src/modinfodialog.ui | 54 +++++++++------------------------------------------ 3 files changed, 48 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 3f8b58a9..d32b112f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1732,7 +1732,6 @@ void ModInfoDialog::openOverwriteDataFile() void ModInfoDialog::previewOverwrittenDataFile() { - // the overwritten tree only supports single selection, but check just in case const auto selection = ui->overwrittenTree->selectedItems(); if (!selection.empty()) { previewDataFile(selection[0]); @@ -1741,13 +1740,28 @@ void ModInfoDialog::previewOverwrittenDataFile() void ModInfoDialog::openOverwrittenDataFile() { - // the overwritten tree only supports single selection, but check just in case const auto selection = ui->overwrittenTree->selectedItems(); if (!selection.empty()) { openDataFile(selection[0]); } } +void ModInfoDialog::previewNoConflictDataFile() +{ + const auto selection = ui->noConflictTree->selectedItems(); + if (!selection.empty()) { + previewDataFile(selection[0]); + } +} + +void ModInfoDialog::openNoConflictDataFile() +{ + const auto selection = ui->noConflictTree->selectedItems(); + if (!selection.empty()) { + openDataFile(selection[0]); + } +} + void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) { if (!item) { @@ -1992,6 +2006,25 @@ void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint & } } +void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) +{ + auto* item = ui->noConflictTree->itemAt(pos.x(), pos.y()); + + if (item != nullptr) { + if (!item->data(1, Qt::UserRole + 2).toBool()) { + QMenu menu; + + menu.addAction(tr("Open/Execute"), this, SLOT(openNoConflictDataFile())); + + if (canPreviewConflictItem(item)) { + menu.addAction(tr("Preview"), this, SLOT(previewNoConflictDataFile())); + } + + menu.exec(ui->noConflictTree->viewport()->mapToGlobal(pos)); + } + } +} + void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 731611d2..32efa210 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -319,6 +319,9 @@ private slots: void previewOverwrittenDataFile(); void openOverwrittenDataFile(); + void previewNoConflictDataFile(); + void openNoConflictDataFile(); + void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); void linkClicked(QString url); @@ -355,6 +358,7 @@ private slots: void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwriteTree_customContextMenuRequested(const QPoint &pos); void on_overwrittenTree_customContextMenuRequested(const QPoint &pos); + void on_noConflictTree_customContextMenuRequested(const QPoint &pos); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 0d96f621..1187de87 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -407,7 +407,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + @@ -417,26 +417,14 @@ Most mods do not have optional esps, so chances are good you are looking at an e - border: none + border: none; +text-align: left; The following conflicted files are provided by this mod - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -501,7 +489,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + @@ -511,26 +499,14 @@ Most mods do not have optional esps, so chances are good you are looking at an e - border: none + border: none; +text-align: left; The following conflicted files are provided by other mods - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -583,7 +559,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + @@ -593,26 +569,14 @@ Most mods do not have optional esps, so chances are good you are looking at an e - border: none; + border: none; +text-align: left; The following files have no conflicts - - - - Qt::Horizontal - - - - 40 - 20 - - - - -- cgit v1.3.1 From 513eaaa3556bc08c7ac9d03944ee35deff9d7ceb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 18:33:50 -0400 Subject: added const version of Settings::directInterface() for restoring states added top-level saveState() and restoreState() to ModInfoDialog that are called from MainWindow, because it now also has to handle expander states in the conflict tab added internal opened state to expander widget instead of using widget visibility so it can be saved even after the dialog is closed --- src/mainwindow.cpp | 4 +-- src/modinfodialog.cpp | 86 ++++++++++++++++++++++++++++++++++++++------------- src/modinfodialog.h | 16 +++++++--- src/settings.h | 1 + 4 files changed, 78 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9108464f..89cb9f56 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2983,7 +2983,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, dialog.openTab(tab); } - dialog.restoreTabState(m_OrganizerCore.settings().directInterface().value("mod_info_tabs").toByteArray()); + dialog.restoreState(m_OrganizerCore.settings()); QSettings &settings = m_OrganizerCore.settings().directInterface(); QString key = QString("geometry/%1").arg(dialog.objectName()); if (settings.contains(key)) { @@ -3001,7 +3001,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } dialog.exec(); - m_OrganizerCore.settings().directInterface().setValue("mod_info_tabs", dialog.saveTabState()); + dialog.saveState(m_OrganizerCore.settings()); settings.setValue(key, dialog.saveGeometry()); modInfo->saveMeta(); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d32b112f..ca09aaa0 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -266,7 +266,7 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) ExpanderWidget::ExpanderWidget() - : m_button(nullptr), m_content(nullptr) + : m_button(nullptr), m_content(nullptr), opened_(false) { } @@ -284,39 +284,41 @@ void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_button->setCheckable(true); - if (o) - open(); - else - close(); - + toggle(o); QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); } -void ExpanderWidget::open() +void ExpanderWidget::toggle() { - m_button->setArrowType(Qt::DownArrow); - m_button->setChecked(false); - m_content->show(); + if (opened()) { + toggle(false); + } + else { + toggle(true); + } } -void ExpanderWidget::close() +void ExpanderWidget::toggle(bool b) { - m_button->setArrowType(Qt::RightArrow); - m_button->setChecked(false); - m_content->hide(); -} + if (b) { + m_button->setArrowType(Qt::DownArrow); + m_button->setChecked(false); + m_content->show(); + } else { + m_button->setArrowType(Qt::RightArrow); + m_button->setChecked(false); + m_content->hide(); + } -void ExpanderWidget::toggle() -{ - if (opened()) - close(); - else - open(); + // the state has to be remembered instead of using m_content's visibility + // because saving the state in saveConflictExpandersState() happens after the + // dialog is closed, which marks all the widgets hidden + opened_ = b; } bool ExpanderWidget::opened() const { - return m_content->isVisible(); + return opened_; } @@ -507,6 +509,18 @@ int ModInfoDialog::tabIndex(const QString &tabId) } +void ModInfoDialog::saveState(Settings& s) const +{ + s.directInterface().setValue("mod_info_tabs", saveTabState()); + s.directInterface().setValue("mod_info_conflict_expanders", saveConflictExpandersState()); +} + +void ModInfoDialog::restoreState(const Settings& s) +{ + restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); + restoreConflictExpandersState(s.directInterface().value("mod_info_conflict_expanders").toByteArray()); +} + void ModInfoDialog::restoreTabState(const QByteArray &state) { QDataStream stream(state); @@ -538,6 +552,22 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) ui->tabWidget->blockSignals(false); } +void ModInfoDialog::restoreConflictExpandersState(const QByteArray &state) +{ + QDataStream stream(state); + + bool overwriteExpanded = false; + bool overwrittenExpanded = false; + bool noConflictExpanded = false; + + stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded; + + if (stream.status() == QDataStream::Ok) { + m_overwriteExpander.toggle(overwriteExpanded); + m_overwrittenExpander.toggle(overwrittenExpanded); + m_nonconflictExpander.toggle(noConflictExpanded); + } +} QByteArray ModInfoDialog::saveTabState() const { @@ -551,6 +581,18 @@ QByteArray ModInfoDialog::saveTabState() const return result; } +QByteArray ModInfoDialog::saveConflictExpandersState() const +{ + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + + stream + << m_overwriteExpander.opened() + << m_overwrittenExpander.opened() + << m_nonconflictExpander.opened(); + + return result; +} void ModInfoDialog::refreshLists() { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 32efa210..3ce76740 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -191,14 +191,14 @@ public: void set(QToolButton* button, QWidget* content, bool opened=false); - void open(); - void close(); void toggle(); + void toggle(bool b); bool opened() const; private: QToolButton* m_button; QWidget* m_content; + bool opened_; }; @@ -257,9 +257,8 @@ public: **/ void openTab(int tab); - void restoreTabState(const QByteArray &state); - - QByteArray saveTabState() const; + void saveState(Settings& s) const; + void restoreState(const Settings& s); signals: @@ -408,6 +407,13 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; + + void restoreTabState(const QByteArray &state); + void restoreConflictExpandersState(const QByteArray &state); + + QByteArray saveTabState() const; + QByteArray saveConflictExpandersState() const; + bool canHideConflictItem(const QTreeWidgetItem* item) const; bool canUnhideConflictItem(const QTreeWidgetItem* item) const; bool canPreviewConflictItem(const QTreeWidgetItem* item) const; diff --git a/src/settings.h b/src/settings.h index ed49a1bc..04a0646e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -306,6 +306,7 @@ public: * @return the wrapped QSettings object */ QSettings &directInterface() { return m_Settings; } + const QSettings &directInterface() const { return m_Settings; } /** * @brief retrieve a setting for one of the installed plugins -- cgit v1.3.1 From 0088ee963b25495e6cce790005dcf1356a73e7b8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 18:44:41 -0400 Subject: documented ExpanderWidget, removed checkable stuff because the state is kept internally --- src/modinfodialog.cpp | 5 +---- src/modinfodialog.h | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ca09aaa0..cff1387c 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -282,10 +282,9 @@ void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) m_content = content; m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - m_button->setCheckable(true); + QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); toggle(o); - QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); } void ExpanderWidget::toggle() @@ -302,11 +301,9 @@ void ExpanderWidget::toggle(bool b) { if (b) { m_button->setArrowType(Qt::DownArrow); - m_button->setChecked(false); m_content->show(); } else { m_button->setArrowType(Qt::RightArrow); - m_button->setChecked(false); m_content->hide(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 3ce76740..dc04deb3 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -183,16 +183,38 @@ private: }; +/* Takes a QToolButton and a widget and creates an expandable widget. + **/ class ExpanderWidget { public: + /** empty expander, use set() + **/ ExpanderWidget(); + + /** see set() + **/ ExpanderWidget(QToolButton* button, QWidget* content); + /** @brief sets the button and content widgets to use + * the button will be given an arrow icon, clicking it will toggle the + * visibility of the given widget + * @param button the button that toggles the content + * @param content the widget that will be shown or hidden + * @param opened initial state, defaults to closed + **/ void set(QToolButton* button, QWidget* content, bool opened=false); + /** either opens or closes the expander depending on the current state + **/ void toggle(); + + /** sets the current state of the expander + **/ void toggle(bool b); + + /** returns whether the expander is currently opened + **/ bool opened() const; private: -- cgit v1.3.1 From c2ed511ff0822e2003b0ec3fe574a2a9e6b9d592 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 21 May 2019 01:41:12 -0500 Subject: With old mods, only queue the old ones in case of API overflow --- src/modinfo.cpp | 12 +- src/organizer_en.ts | 733 ++++++++++++++++++++++++++-------------------------- 2 files changed, 378 insertions(+), 367 deletions(-) (limited to 'src') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index c1974152..bc2979ef 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -313,19 +313,23 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei if (latest < QDateTime::currentDateTimeUtc().addDays(-30)) { std::set> organizedGames; for (auto mod : s_Collection) { - if (mod->canBeUpdated()) { + if (mod->canBeUpdated() && mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addDays(-30)) { organizedGames.insert(std::make_pair(mod->getGameName().toLower(), mod->getNexusID())); } } if (organizedGames.empty()) { - qWarning("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); + qWarning() << tr("All of your mods have been checked recently. We restrict update checks to help preserve your available API requests."); updatesAvailable = false; + } else { + qInfo() << tr( + "You have mods that haven't been checked within 30 days using the new API. These mods must be checked before we can use the bulk update API. " + "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods." + ); } - for (auto game : organizedGames) { + for (auto game : organizedGames) NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); - } } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-30)) { for (auto gameName : games) NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(true), QString()); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 93997047..a3761957 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1568,7 +1568,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1759,8 +1759,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -1955,7 +1955,7 @@ p, li { white-space: pre-wrap; }
    - + Update @@ -1993,7 +1993,7 @@ p, li { white-space: pre-wrap; }
    - + Endorse Mod Organizer @@ -2061,8 +2061,8 @@ Error: %1
    - - + + Endorse @@ -2293,622 +2293,622 @@ Error: %1 - - + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + You need to be logged in with Nexus to resume a download - - - - + + + + You need to be logged in with Nexus to endorse - - + + Endorsing multiple mods will take a while. Please wait... - - + + Unendorsing multiple mods will take a while. Please wait... - - - + + + You need to be logged in with Nexus to track - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this Mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + Web page for this mod is unknown - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - + Send to - + Top - + Bottom - + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2916,12 +2916,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2929,22 +2929,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -2952,373 +2952,373 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Select binary - + Binary - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3414,6 +3414,16 @@ You will have to visit the mod page on the %1 Nexus site to change your mind.remove: invalid mod index %1 + + + All of your mods have been checked recently. We restrict update checks to help preserve your available API requests. + + + + + You have mods that haven't been checked within 30 days using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. + +
    ModInfoBackup @@ -3576,58 +3586,58 @@ Most mods do not have optional esps, so chances are good you are looking at an e - - + + File - + Overwritten Mods - + The following conflicted files are provided by other mods - + Providing Mod - + Non-Conflicted files - + Categories - + Primary Category - + Nexus Info - + Mod ID - + Mod ID for this mod on Nexus. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3636,22 +3646,22 @@ p, li { white-space: pre-wrap; } - + Source Game - + Source game for this mod. - + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3660,76 +3670,76 @@ p, li { white-space: pre-wrap; } - + Version - + Refresh - + Refresh all information from Nexus. - + Description - + about:blank - + Endorse - + Web page URL (only used if invalid NexusID) : - + Notes - + Enter comments about the mod here. These are displayed in the notes column of the mod list. - - + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - + Filetree - + Open Mod in Explorer - + A directory view of this mod - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3739,292 +3749,258 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close - + &Delete - + &Rename - + &Hide - + &Unhide - + &Open - + &New Folder - - + + Save changes? - - + + Save changes to "%1"? - + File Exists - + A file with that name exists, please enter a new one - + failed to move file - + failed to create directory "optional" - + Info requested, please wait - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + Current Version: %1 - + No update available - + <div style="text-align: center;"><h1>Uh oh!</h1><p>Sorry, there is no description available for this mod. :(</p></div> - + <a href="%1">Visit on Nexus</a> - + Failed to delete %1 - - - - + + + + Confirm - - + + Are sure you want to delete "%1"? - - + + Are sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" - - - Replace file? - - - - - There already is a hidden version of this file. Replace it? - - - - - - File operation failed - - - - - - Failed to remove "%1". Maybe you lack the required file permissions? - - - - - - failed to rename %1 to %2 - - - - - There already is a visible version of this file. Replace it? - - - - + Select binary - + Binary - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Un-Hide - + Hide - - + + Open/Execute - - + + Preview - + Name - + Please enter a name - - + + Error - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -5804,7 +5780,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> @@ -5896,6 +5872,37 @@ You will be asked if you want to allow helper.exe to make changes to the system. failed to spawn "%1": %2 + + + The hidden file "%1" already exists. Replace it? + + + + + The visible file "%1" already exists. Replace it? + + + + + Replace file? + + + + + + File operation failed + + + + + Failed to remove "%1". Maybe you lack the required file permissions? + + + + + failed to rename %1 to %2 + + QueryOverwriteDialog -- cgit v1.3.1 From 056ecb46778627e6867208244f0fbd3432efda47 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 21 May 2019 16:09:41 -0400 Subject: fix crash when changing file visibility from the filetree of an installed but inactive mode --- src/modinfodialog.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index cff1387c..1304b27a 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1396,7 +1396,9 @@ void ModInfoDialog::changeFiletreeVisibility(bool hide) if (changed) { qDebug().nospace() << "triggering refresh"; - emit originModified(m_Origin->getID()); + if (m_Origin) { + emit originModified(m_Origin->getID()); + } refreshLists(); } } @@ -1677,7 +1679,9 @@ void ModInfoDialog::changeConflictFilesVisibility(bool hide) if (changed) { qDebug().nospace() << "triggering refresh"; - emit originModified(m_Origin->getID()); + if (m_Origin) { + emit originModified(m_Origin->getID()); + } refreshLists(); } } -- cgit v1.3.1 From 1d09e85c089ef71445286faae248f25cd7d239a6 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 21 May 2019 23:46:12 -0500 Subject: Add logs to build artifacts on CI fail, commit translation changes --- src/organizer_en.ts | 70 ++++++++++++++++++++++++++--------------------------- src/version.rc | 4 +-- 2 files changed, 37 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index a3761957..f38044de 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -3952,13 +3952,13 @@ p, li { white-space: pre-wrap; } - - Un-Hide + + Hide - - Hide + + Un-Hide @@ -5789,6 +5789,37 @@ If the folder was still in use, restart MO and try again. failed to parse profile %1: %2 + + + The hidden file "%1" already exists. Replace it? + + + + + The visible file "%1" already exists. Replace it? + + + + + Replace file? + + + + + + File operation failed + + + + + Failed to remove "%1". Maybe you lack the required file permissions? + + + + + failed to rename %1 to %2 + + Failed to start "%1" @@ -5872,37 +5903,6 @@ You will be asked if you want to allow helper.exe to make changes to the system. failed to spawn "%1": %2 - - - The hidden file "%1" already exists. Replace it? - - - - - The visible file "%1" already exists. Replace it? - - - - - Replace file? - - - - - - File operation failed - - - - - Failed to remove "%1". Maybe you lack the required file permissions? - - - - - failed to rename %1 to %2 - - QueryOverwriteDialog diff --git a/src/version.rc b/src/version.rc index 241cd0ce..af6fd603 100644 --- a/src/version.rc +++ b/src/version.rc @@ -3,8 +3,8 @@ // If VS_FF_PRERELEASE is not set, MO labels the build as a release and uses VER_FILEVERSION to determine version number. // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha -#define VER_FILEVERSION 2,2,0 -#define VER_FILEVERSION_STR "2.2.0\0" +#define VER_FILEVERSION 2,2,1 +#define VER_FILEVERSION_STR "2.2.1alpha1\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From 77678b3765365e1ee5f440ffcd6f163d0cc3ae48 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 May 2019 22:43:23 -0400 Subject: download list: added columns for mod name, version and nexus id --- src/downloadlist.cpp | 29 ++++++++++++++++++++++++++++- src/downloadlist.h | 8 +++++++- src/downloadlistsortproxy.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ src/downloadlistwidget.cpp | 11 ++++++++++- src/mainwindow.cpp | 6 ++++++ 5 files changed, 93 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index dd72abbd..e7036234 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -47,7 +47,7 @@ int DownloadList::rowCount(const QModelIndex&) const int DownloadList::columnCount(const QModelIndex&) const { - return 4; + return COL_COUNT; } @@ -69,6 +69,9 @@ QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int (orientation == Qt::Horizontal)) { switch (section) { case COL_NAME: return tr("Name"); + case COL_MODNAME: return tr("Mod name"); + case COL_VERSION: return tr("Version"); + case COL_ID: return tr("Nexus ID"); case COL_SIZE: return tr("Size"); case COL_STATUS: return tr("Status"); case COL_FILETIME: return tr("Filetime"); @@ -93,6 +96,30 @@ QVariant DownloadList::data(const QModelIndex &index, int role) const } else { switch (index.column()) { case COL_NAME: return m_MetaDisplay ? m_Manager->getDisplayName(index.row()) : m_Manager->getFileName(index.row()); + case COL_MODNAME: { + if (m_Manager->isInfoIncomplete(index.row())) { + return {}; + } else { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row()); + return info->modName; + } + } + case COL_VERSION: { + if (m_Manager->isInfoIncomplete(index.row())) { + return {}; + } else { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row()); + return info->version.canonicalString(); + } + } + case COL_ID: { + if (m_Manager->isInfoIncomplete(index.row())) { + return {}; + } else { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row()); + return QString("%1").arg(m_Manager->getModID(index.row())); + } + } case COL_SIZE: return sizeFormat(m_Manager->getFileSize(index.row())); case COL_FILETIME: return m_Manager->getFileTime(index.row()); case COL_STATUS: diff --git a/src/downloadlist.h b/src/downloadlist.h index bf0cdc37..6f63f0c8 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -39,7 +39,13 @@ public: COL_NAME = 0, COL_STATUS, COL_SIZE, - COL_FILETIME + COL_FILETIME, + COL_MODNAME, + COL_VERSION, + COL_ID, + + // number of columns + COL_COUNT }; public: diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index dc97dc3e..7bda139b 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -43,6 +43,48 @@ bool DownloadListSortProxy::lessThan(const QModelIndex &left, && (rightIndex < m_Manager->numTotalDownloads())) { if (left.column() == DownloadList::COL_NAME) { return m_Manager->getFileName(left.row()).compare(m_Manager->getFileName(right.row()), Qt::CaseInsensitive) < 0; + } else if (left.column() == DownloadList::COL_MODNAME) { + QString leftName, rightName; + + if (!m_Manager->isInfoIncomplete(left.row())) { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row()); + leftName = info->modName; + } + + if (!m_Manager->isInfoIncomplete(right.row())) { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row()); + rightName = info->modName; + } + + return leftName.compare(rightName, Qt::CaseInsensitive) < 0; + } else if (left.column() == DownloadList::COL_VERSION) { + MOBase::VersionInfo versionLeft, versionRight; + + if (!m_Manager->isInfoIncomplete(left.row())) { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row()); + versionLeft = info->version; + } + + if (!m_Manager->isInfoIncomplete(right.row())) { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row()); + versionRight = info->version; + } + + return versionLeft < versionRight; + } else if (left.column() == DownloadList::COL_ID) { + int leftID=0, rightID=0; + + if (!m_Manager->isInfoIncomplete(left.row())) { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(left.row()); + leftID = info->modID; + } + + if (!m_Manager->isInfoIncomplete(right.row())) { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(right.row()); + rightID = info->modID; + } + + return leftID < rightID; } else if (left.column() == DownloadList::COL_STATUS) { DownloadManager::DownloadState leftState = m_Manager->getState(left.row()); DownloadManager::DownloadState rightState = m_Manager->getState(right.row()); diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index f0c4da1a..e6029270 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -123,6 +123,15 @@ DownloadListWidget::~DownloadListWidget() void DownloadListWidget::setManager(DownloadManager *manager) { m_Manager = manager; + + // hide these columns by default + // + // note that this is overridden by the ini if MO has been started at least + // once before, which is handled in MainWindow::processUpdates() for older + // versions + header()->hideSection(DownloadList::COL_MODNAME); + header()->hideSection(DownloadList::COL_VERSION); + header()->hideSection(DownloadList::COL_ID); } void DownloadListWidget::setSourceModel(DownloadList *sourceModel) @@ -199,7 +208,7 @@ void DownloadListWidget::onCustomContextMenu(const QPoint &point) if (state >= DownloadManager::STATE_READY) { menu.addAction(tr("Install"), this, SLOT(issueInstall())); - if (m_Manager->isInfoIncomplete(m_ContextRow)) + if (m_Manager->isInfoIncomplete(m_ContextRow)) menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfoMd5())); else menu.addAction(tr("Visit on Nexus"), this, SLOT(issueVisitOnNexus())); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 89cb9f56..1b95ea0b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1897,6 +1897,12 @@ void MainWindow::processUpdates() { instance.remove(""); instance.endGroup(); } + if (lastVersion < QVersionNumber(2, 2, 1)) { + // hide new columns by default + for (int i=DownloadList::COL_MODNAME; idownloadView->header()->hideSection(i); + } + } } if (currentVersion > lastVersion) { -- cgit v1.3.1 From 3ef7c401d547d0ca1ace90a1132ad36e3e133eba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 22 May 2019 23:08:07 -0400 Subject: download list: made all column left-aligned except for size --- src/downloadlist.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index e7036234..5e698e0e 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -170,10 +170,10 @@ QVariant DownloadList::data(const QModelIndex &index, int role) const && m_Manager->isInfoIncomplete(index.row())) return QIcon(":/MO/gui/warning_16"); } else if (role == Qt::TextAlignmentRole) { - if (index.column() == COL_NAME) - return QVariant(Qt::AlignVCenter | Qt::AlignLeft); - else + if (index.column() == COL_SIZE) return QVariant(Qt::AlignVCenter | Qt::AlignRight); + else + return QVariant(Qt::AlignVCenter | Qt::AlignLeft); } return QVariant(); } -- cgit v1.3.1 From 2d58ad346c9e8840d3864080d62999e686962a76 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 23 May 2019 22:24:57 -0500 Subject: Add support for new Nexus API game codes to NXM link handling --- src/downloadmanager.cpp | 19 +- src/organizer_en.ts | 962 ++++++++++++++++++++++++------------------------ 2 files changed, 505 insertions(+), 476 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 78162f11..d4a6c37f 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -549,10 +549,21 @@ void DownloadManager::addNXMDownload(const QString &url) NXMUrl nxmInfo(url); QStringList validGames; + MOBase::IPluginGame* foundGame = nullptr; validGames.append(m_ManagedGame->gameShortName()); validGames.append(m_ManagedGame->validShortNames()); + for (auto game : validGames) { + MOBase::IPluginGame* gamePlugin = m_OrganizerCore->getGame(game); + if ( + nxmInfo.game().compare(gamePlugin->gameShortName(), Qt::CaseInsensitive) == 0 || + nxmInfo.game().compare(gamePlugin->gameNexusName(), Qt::CaseInsensitive) == 0 + ) { + foundGame = gamePlugin; + break; + } + } qDebug("add nxm download: %s", qUtf8Printable(url)); - if (!validGames.contains(nxmInfo.game(), Qt::CaseInsensitive)) { + if (foundGame == nullptr) { qDebug("download requested for wrong game (game: %s, url: %s)", qUtf8Printable(m_ManagedGame->gameShortName()), qUtf8Printable(nxmInfo.game())); QMessageBox::information(nullptr, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " "has been set up for \"%2\".").arg(nxmInfo.game()).arg(m_ManagedGame->gameShortName()), QMessageBox::Ok); @@ -560,7 +571,7 @@ void DownloadManager::addNXMDownload(const QString &url) } for (auto tuple : m_PendingDownloads) { - if (std::get<0>(tuple).compare(nxmInfo.game(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) { + if (std::get<0>(tuple).compare(foundGame->gameShortName(), Qt::CaseInsensitive) == 0, std::get<1>(tuple) == nxmInfo.modId() && std::get<2>(tuple) == nxmInfo.fileId()) { QString debugStr("download requested is already queued (mod: %1, file: %2)"); QString infoStr(tr("There is already a download queued for this file.\n\nMod %1\nFile %2")); @@ -620,7 +631,7 @@ void DownloadManager::addNXMDownload(const QString &url) emit aboutToUpdate(); - m_PendingDownloads.append(std::make_tuple(nxmInfo.game(), nxmInfo.modId(), nxmInfo.fileId())); + m_PendingDownloads.append(std::make_tuple(foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId())); emit update(-1); emit downloadAdded(); @@ -631,7 +642,7 @@ void DownloadManager::addNXMDownload(const QString &url) info->nexusDownloadUser = nxmInfo.userId(); QObject *test = info; - m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.game(), nxmInfo.modId(), nxmInfo.fileId(), this, qVariantFromValue(test), "")); + m_RequestIDs.insert(m_NexusInterface->requestFileInfo(foundGame->gameShortName(), nxmInfo.modId(), nxmInfo.fileId(), this, qVariantFromValue(test), "")); } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index f38044de..24a58c81 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -323,93 +323,108 @@ p, li { white-space: pre-wrap; } - Size + Mod name - Status + Version + Nexus ID + + + + + Size + + + + + Status + + + + Filetime - + < game %1 mod %2 file %3 > - + Unknown - + Pending - + Started - + Canceling - + Pausing - + Canceled - + Paused - + Error - - - + + + Fetching Info - + Downloaded - + Installed - + Uninstalled - + Pending download - + Information missing, please select "Query Info" from the context menu to re-retrieve. @@ -417,145 +432,145 @@ p, li { white-space: pre-wrap; } DownloadListWidget - + Install - + Query Info - + Visit on Nexus - + Open File - - - + + + Show in Folder - - + + Delete - + Un-Hide - + Hide - + Cancel - + Pause - + Resume - + Delete Installed... - + Delete Uninstalled... - + Delete All... - + Hide Installed... - + Hide Uninstalled... - + Hide All... - + Un-Hide All... - - + + Delete Files? - + This will permanently delete the selected download. - + This will remove all finished downloads from this list and from disk. - + This will remove all installed downloads from this list and from disk. - + This will remove all uninstalled downloads from this list and from disk. - + Are you sure? - + This will remove all finished downloads from this list (but NOT from disk). - + This will remove all installed downloads from this list (but NOT from disk). - + This will remove all uninstalled downloads from this list (but NOT from disk). @@ -588,17 +603,17 @@ p, li { white-space: pre-wrap; } - + Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + There is already a download queued for this file. Mod %1 @@ -606,12 +621,12 @@ File %2 - + Already Queued - + There is already a download started for this file. Mod %1: %2 @@ -619,266 +634,266 @@ File %3: %4 - + Already Started - - + + remove: invalid download index %1 - + failed to delete %1 - + failed to delete meta file for %1 - + restore: invalid download index: %1 - + cancel: invalid download index %1 - + pause: invalid download index %1 - + resume: invalid download index %1 - + resume (int): invalid download index %1 - + No known download urls. Sorry, this download can't be resumed. - - + + query: invalid download index %1 - + Please enter the nexus mod id - + Mod ID: - + Please select the source game code for %1 - + VisitNexus: invalid download index %1 - + Nexus ID for this Mod is unknown - + OpenFile: invalid download index %1 - + OpenFileInDownloadsFolder: invalid download index %1 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1568,7 +1583,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1759,8 +1774,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -1955,7 +1970,7 @@ p, li { white-space: pre-wrap; } - + Update @@ -1993,7 +2008,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2061,8 +2076,8 @@ Error: %1 - - + + Endorse @@ -2182,733 +2197,733 @@ Error: %1 - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + You need to be logged in with Nexus to resume a download - - - - + + + + You need to be logged in with Nexus to endorse - - + + Endorsing multiple mods will take a while. Please wait... - - + + Unendorsing multiple mods will take a while. Please wait... - - - + + + You need to be logged in with Nexus to track - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this Mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + Web page for this mod is unknown - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2916,12 +2931,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2929,22 +2944,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -2952,373 +2967,373 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Select binary - + Binary - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3581,63 +3596,64 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + The following conflicted files are provided by this mod - - + + + File - + Overwritten Mods - + The following conflicted files are provided by other mods - + Providing Mod - - Non-Conflicted files + + The following files have no conflicts - + Categories - + Primary Category - + Nexus Info - + Mod ID - + Mod ID for this mod on Nexus. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3646,22 +3662,22 @@ p, li { white-space: pre-wrap; } - + Source Game - + Source game for this mod. - + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3670,76 +3686,76 @@ p, li { white-space: pre-wrap; } - + Version - + Refresh - + Refresh all information from Nexus. - + Description - + about:blank - + Endorse - + Web page URL (only used if invalid NexusID) : - + Notes - - - + + + Enter comments about the mod here. These are displayed in the notes column of the mod list. - - - + + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - + Filetree - + Open Mod in Explorer - + A directory view of this mod - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3749,258 +3765,260 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close - + &Delete - + &Rename - + &Hide - + &Unhide - + &Open - + &New Folder - - + + Save changes? - - + + Save changes to "%1"? - + File Exists - + A file with that name exists, please enter a new one - + failed to move file - + failed to create directory "optional" - + Info requested, please wait - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + Current Version: %1 - + No update available - + <div style="text-align: center;"><h1>Uh oh!</h1><p>Sorry, there is no description available for this mod. :(</p></div> - + <a href="%1">Visit on Nexus</a> - + Failed to delete %1 - - - - + + + + Confirm - - + + Are sure you want to delete "%1"? - - + + Are sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" - + Select binary - + Binary - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Hide - + Un-Hide - - + + + Open/Execute - - + + + Preview - + Name - + Please enter a name - - + + Error - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -5780,7 +5798,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> -- cgit v1.3.1 From e349af70b72ddc0af303bf82cfc4f0e29fe1332b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 May 2019 02:19:30 -0400 Subject: put source files in specific groups so they're organized in the visual studio project --- src/CMakeLists.txt | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 178 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 40fe2630..27dfc590 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -243,16 +243,190 @@ SET(organizer_RCS ) -SOURCE_GROUP(Source FILES ${organizer_SRCS}) -SOURCE_GROUP(Headers FILES ${organizer_HDRS}) -SOURCE_GROUP(UI FILES ${organizer_UIS}) +source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp)") + +set(application + iuserinterface + main + mainwindow + moapplication + moshortcut + selfupdater + singleinstance +) + +set(browser + browserdialog + browserview +) + +set(core + categories + shared/directoryentry + directoryrefresher + executableslist + installationmanager + instancemanager + loadmechanism + nexusinterface + nxmaccessmanager + organizercore + organizerproxy +) + +set(dialogs + aboutdialog + activatemodsdialog + categoriesdialog + credentialsdialog + editexecutablesdialog + filedialogmemory + forcedloaddialog + forcedloaddialogwidget + listdialog + messagedialog + motddialog + overwriteinfodialog + queryoverwritedialog + problemsdialog + savetextasdialog + selectiondialog + syncoverwritedialog + transfersavesdialog + waitingonclosedialog +) + +set(downloads + downloadlist + downloadlistsortproxy + downloadlistwidget + downloadmanager +) + +set(locking + ilockedwaitingforprocess + lockeddialog + lockeddialogbase +) + +set(modinfo + modinfo + modinfobackup + modinfodialog + modinfoforeign + modinfooverwrite + modinforegular + modinfoseparator + modinfowithconflictinfo +) + +set(modlist + modlist + modlistsortproxy + modlistview +) + +set(plugins + plugincontainer + pluginlist + pluginlistsortproxy + pluginlistview +) + +set(previews + previewdialog + previewgenerator +) + +set(profiles + profile + profileinputdialog + profilesdialog +) + +set(resources + app_icon + version +) + +set(settings + settings + settingsdialog +) + +set(utilities + shared/appconfig + bbcode + csvbuilder + shared/error_report + eventfilter + helper + shared/leaktrace + persistentcookiejar + serverinfo + spawn + shared/stackdata + shared/util + usvfsconnector + shared/windows_error +) + +set(widgets + descriptionpage + genericicondelegate + icondelegate + lcdnumber + logbuffer + loghighlighter + modflagicondelegate + modidlineedit + noeditdelegate + qtgroupingproxy + viewmarkingscrollbar +) + +set(src_filters + application core browser dialogs downloads locking modinfo modlist plugins + previews profiles settings utilities widgets +) + +set(root_filters resources) + +foreach(filter in list ${src_filters}) + set(files) + foreach(d in lists ${${filter}}) + set(files ${files} ${d}.cpp ${d}.h ${d}.rc ${d}.inc) + endforeach() + + source_group(src\\${filter} FILES ${files}) +endforeach() + +foreach(filter in list ${root_filters}) + set(files) + foreach(d in lists ${${filter}}) + set(files ${files} ${d}.cpp ${d}.h ${d}.rc ${d}.inc) + endforeach() + + source_group(${filter} FILES ${files}) +endforeach() + +file(GLOB_RECURSE rule_files ../vsbuild/CMakeFiles/*.rule) +source_group(cmake FILES CMakeLists.txt ${rule_files}) + +source_group(qt FILES + ${organizer_QRCS} + ../vsbuild/src/ModOrganizer_autogen/mocs_compilation.cpp + ../vsbuild/src/qrc_resources.cpp + ../vsbuild/src/qrc_stylesheet_resource.cpp + ../vsbuild/src/organizer_en.qm) + # MO projects SET(dependency_project_path "${DEPENDENCIES_DIR}") SET(default_project_path "${DEPENDENCIES_DIR}/modorganizer_super") GET_FILENAME_COMPONENT(${default_project_path} ${default_project_path} REALPATH) - SET(project_path "${default_project_path}" CACHE PATH "path to the other mo projects") + #TODO this should not be a hardcoded path SET(lib_path "${project_path}/../../install/libs") -- cgit v1.3.1 From dfdea05029da0868115896af14e9f2fb2ff22eea Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 May 2019 02:20:57 -0400 Subject: greatly decreased the verbosity of builds always include the INSTALL project in the solution build --- CMakeLists.txt | 3 +++ src/CMakeLists.txt | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/CMakeLists.txt b/CMakeLists.txt index 3955cc75..f424dc78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,9 @@ PROJECT(organizer) # sets ModOrganizer as StartUp Project in Visual Studio set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY VS_STARTUP_PROJECT "ModOrganizer") +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +set(CMAKE_INSTALL_MESSAGE NEVER) + SET(DEPENDENCIES_DIR CACHE PATH "") # hint to find qt in dependencies path LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27dfc590..0d12792a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -446,7 +446,7 @@ FIND_PACKAGE(Qt5LinguistTools) QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS}) QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS}) SET(mo_translation_sources ${CMAKE_SOURCE_DIR}/src ${project_path}/uibase/src) -QT5_CREATE_TRANSLATION(organizer_translations_qm ${mo_translation_sources} ${CMAKE_SOURCE_DIR}/src/organizer_en.ts) +QT5_CREATE_TRANSLATION(organizer_translations_qm ${mo_translation_sources} ${CMAKE_SOURCE_DIR}/src/organizer_en.ts OPTIONS -silent) ADD_CUSTOM_TARGET(translations DEPENDS ${organizer_translations_qm}) INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES}) @@ -549,11 +549,11 @@ SET(windeploy_parameters "--no-translations --plugindir qtplugins --libdir dlls INSTALL( CODE "EXECUTE_PROCESS(COMMAND - ${qt5bin}/windeployqt.exe ModOrganizer.exe --webenginewidgets --websockets ${windeploy_parameters} + ${qt5bin}/windeployqt.exe --verbose 0 ModOrganizer.exe --webenginewidgets --websockets ${windeploy_parameters} WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin ) EXECUTE_PROCESS(COMMAND - ${qt5bin}/windeployqt.exe uibase.dll ${windeploy_parameters} + ${qt5bin}/windeployqt.exe --verbose 0 uibase.dll ${windeploy_parameters} WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin ) file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/platforms) -- cgit v1.3.1 From 953ab4e6f6e25b04890f7201b783c2693748af28 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 May 2019 03:53:31 -0400 Subject: turned autorcc on so no need for qt5_add_resources() anymore qt5_wrap_ui() is redundant since autouic is already on generate INSTALL.vcxproj.user to have the proper debugging settings so ModOrganizer.exe is started from install/bin --- CMakeLists.txt | 13 +++++++++++++ src/CMakeLists.txt | 5 ++--- 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/CMakeLists.txt b/CMakeLists.txt index f424dc78..ced097e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,3 +16,16 @@ LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll) ADD_SUBDIRECTORY(src) + +set(vcxproj_user_file "${CMAKE_CURRENT_BINARY_DIR}/INSTALL.vcxproj.user") +if(NOT EXISTS ${vcxproj_user_file}) + FILE(WRITE ${vcxproj_user_file} + "\n" + "\n" + "\n" + "${CMAKE_INSTALL_PREFIX}/bin\n" + "WindowsLocalDebugger" + "${CMAKE_INSTALL_PREFIX}/bin/ModOrganizer.exe\n" + "\n" + "\n") +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0d12792a..8d13b557 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -434,6 +434,7 @@ SET(lib_path "${project_path}/../../install/libs") SET(CMAKE_INCLUDE_CURRENT_DIR ON) SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTOUIC ON) +SET(CMAKE_AUTORCC ON) FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5QuickWidgets REQUIRED) FIND_PACKAGE(Qt5Quick REQUIRED) @@ -443,8 +444,6 @@ FIND_PACKAGE(Qt5WebEngineWidgets REQUIRED) FIND_PACKAGE(Qt5WebSockets REQUIRED) FIND_PACKAGE(Qt5Qml REQUIRED) FIND_PACKAGE(Qt5LinguistTools) -QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS}) -QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS}) SET(mo_translation_sources ${CMAKE_SOURCE_DIR}/src ${project_path}/uibase/src) QT5_CREATE_TRANSLATION(organizer_translations_qm ${mo_translation_sources} ${CMAKE_SOURCE_DIR}/src/organizer_en.ts OPTIONS -silent) ADD_CUSTOM_TARGET(translations DEPENDS ${organizer_translations_qm}) @@ -496,7 +495,7 @@ ELSE() SET(usvfs_name usvfs_x86) ENDIF() -ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS} ${organizer_translations_qm}) +ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm}) TARGET_LINK_LIBRARIES(ModOrganizer Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets -- cgit v1.3.1 From 350e4fa97fc0a2dd3a63ad9ce1384e5ca925af9b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 May 2019 04:13:18 -0400 Subject: using builtin properties for autogen source groups, everything is now in the autogen filter --- src/CMakeLists.txt | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d13b557..e59842fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -344,11 +344,6 @@ set(profiles profilesdialog ) -set(resources - app_icon - version -) - set(settings settings settingsdialog @@ -390,36 +385,21 @@ set(src_filters previews profiles settings utilities widgets ) -set(root_filters resources) - foreach(filter in list ${src_filters}) set(files) foreach(d in lists ${${filter}}) - set(files ${files} ${d}.cpp ${d}.h ${d}.rc ${d}.inc) + set(files ${files} ${d}.cpp ${d}.h ${d}.inc) endforeach() source_group(src\\${filter} FILES ${files}) endforeach() -foreach(filter in list ${root_filters}) - set(files) - foreach(d in lists ${${filter}}) - set(files ${files} ${d}.cpp ${d}.h ${d}.rc ${d}.inc) - endforeach() - - source_group(${filter} FILES ${files}) -endforeach() - file(GLOB_RECURSE rule_files ../vsbuild/CMakeFiles/*.rule) -source_group(cmake FILES CMakeLists.txt ${rule_files}) - -source_group(qt FILES - ${organizer_QRCS} - ../vsbuild/src/ModOrganizer_autogen/mocs_compilation.cpp - ../vsbuild/src/qrc_resources.cpp - ../vsbuild/src/qrc_stylesheet_resource.cpp - ../vsbuild/src/organizer_en.qm) +file(GLOB qm_files ../vsbuild/src/*.qm) +source_group(cmake FILES CMakeLists.txt) +source_group(autogen FILES ${rule_files} ${qm_files}) +source_group(resources FILES ${organizer_RCS} ${organizer_QRCS}) # MO projects SET(dependency_project_path "${DEPENDENCIES_DIR}") @@ -435,6 +415,7 @@ SET(CMAKE_INCLUDE_CURRENT_DIR ON) SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTOUIC ON) SET(CMAKE_AUTORCC ON) + FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5QuickWidgets REQUIRED) FIND_PACKAGE(Qt5Quick REQUIRED) @@ -444,7 +425,12 @@ FIND_PACKAGE(Qt5WebEngineWidgets REQUIRED) FIND_PACKAGE(Qt5WebSockets REQUIRED) FIND_PACKAGE(Qt5Qml REQUIRED) FIND_PACKAGE(Qt5LinguistTools) + SET(mo_translation_sources ${CMAKE_SOURCE_DIR}/src ${project_path}/uibase/src) +set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP autogen) +set_property(GLOBAL PROPERTY AUTOMOC_SOURCE_GROUP autogen) +set_property(GLOBAL PROPERTY AUTORCC_SOURCE_GROUP autogen) + QT5_CREATE_TRANSLATION(organizer_translations_qm ${mo_translation_sources} ${CMAKE_SOURCE_DIR}/src/organizer_en.ts OPTIONS -silent) ADD_CUSTOM_TARGET(translations DEPENDS ${organizer_translations_qm}) -- cgit v1.3.1 From f8d319cfba8a1dfce919680ef5e4046a68ae9803 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 May 2019 06:25:58 -0400 Subject: added precompiled headers --- src/CMakeLists.txt | 24 +++++ src/pch.cpp | 1 + src/pch.h | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+) create mode 100644 src/pch.cpp create mode 100644 src/pch.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e59842fb..496bd5fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,28 @@ IF(CMAKE_SIZEOF_VOID_P EQUAL 8) SET(PROJ_ARCH x64) ENDIF() +macro(add_msvc_precompiled_header PrecompiledHeader PrecompiledSource SourcesVar HeadersVar) + if(MSVC) + get_filename_component(PrecompiledBasename ${PrecompiledHeader} NAME_WE) + set(PrecompiledBinary "${CMAKE_CURRENT_BINARY_DIR}/${PrecompiledBasename}.pch") + set(Sources ${${SourcesVar}}) + + set_source_files_properties( + ${PrecompiledSource} PROPERTIES + COMPILE_FLAGS "/Yc\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_OUTPUTS "${PrecompiledBinary}") + + set_source_files_properties( + ${Sources} PROPERTIES + COMPILE_FLAGS "/Yu\"${PrecompiledHeader}\" /FI\"${PrecompiledHeader}\" /Fp\"${PrecompiledBinary}\"" + OBJECT_DEPENDS "${PrecompiledBinary}") + + list(APPEND ${SourcesVar} ${PrecompiledSource}) + list(APPEND ${HeadersVar} ${PrecompiledHeader}) + endif(MSVC) +endmacro(add_msvc_precompiled_header) + + SET(organizer_SRCS transfersavesdialog.cpp syncoverwritedialog.cpp @@ -407,6 +429,8 @@ SET(default_project_path "${DEPENDENCIES_DIR}/modorganizer_super") GET_FILENAME_COMPONENT(${default_project_path} ${default_project_path} REALPATH) SET(project_path "${default_project_path}" CACHE PATH "path to the other mo projects") +add_msvc_precompiled_header("pch.h" "pch.cpp" organizer_SRCS organizer_HDRS) + #TODO this should not be a hardcoded path SET(lib_path "${project_path}/../../install/libs") diff --git a/src/pch.cpp b/src/pch.cpp new file mode 100644 index 00000000..1d9f38c5 --- /dev/null +++ b/src/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/pch.h b/src/pch.h new file mode 100644 index 00000000..1cedb945 --- /dev/null +++ b/src/pch.h @@ -0,0 +1,257 @@ +// std +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// windows +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// boost +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// openssl +#include + +// qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -- cgit v1.3.1 From f4b85d0264059cbc234cfaf2bc25a1e2e8b26e74 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 24 May 2019 06:33:50 -0400 Subject: Update pch.h removed headers that were redundant or actually from MO --- src/pch.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'src') diff --git a/src/pch.h b/src/pch.h index 1cedb945..e86c0da6 100644 --- a/src/pch.h +++ b/src/pch.h @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -58,7 +57,6 @@ #include #include #include -#include // openssl #include @@ -236,7 +234,6 @@ #include #include #include -#include #include #include #include -- 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') 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 9a014db4b3a64dc93450dff8afe980db3694c595 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 May 2019 02:12:11 -0400 Subject: moved GetFileExecutionContext() out of OrganizerCore, no reason for it to be there added ExploreFile(), changed all mentions of ShellExecute() in MainWindow to use it --- src/mainwindow.cpp | 43 ++++++--------- src/organizercore.cpp | 150 +++++++++++++++++++++++++++++--------------------- src/organizercore.h | 13 +++-- 3 files changed, 113 insertions(+), 93 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 88d7ba9d..ce6620c0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3241,12 +3241,12 @@ void MainWindow::openExplorer_clicked() if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - ::ShellExecuteW(nullptr, L"explore", ToWString(info->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(info->absolutePath()); } } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(modInfo->absolutePath()); } } @@ -3261,18 +3261,14 @@ void MainWindow::openOriginExplorer_clicked() continue; } ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - std::vector flags = modInfo->getFlags(); - - ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(modInfo->absolutePath()); } } else { QModelIndex idx = selection->currentIndex(); QString fileName = idx.data().toString(); ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - std::vector flags = modInfo->getFlags(); - - ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(modInfo->absolutePath()); } } @@ -3287,7 +3283,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(modInfo->absolutePath()); } } @@ -3308,7 +3304,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(modInfo->absolutePath()); } } } @@ -4275,64 +4271,61 @@ void MainWindow::disableVisibleMods() void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); - ::ShellExecuteW(nullptr, L"explore", ToWString(dataPath).c_str(), nullptr, nullptr, SW_SHOWNORMAL); - - //opens BaseDirectory instead - //::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(dataPath); } void MainWindow::openLogsFolder() { QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); - ::ShellExecuteW(nullptr, L"explore", ToWString(logsPath).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(logsPath); } void MainWindow::openInstallFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(qApp->applicationDirPath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(qApp->applicationDirPath()); } void MainWindow::openPluginsFolder() { QString pluginsPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - ::ShellExecuteW(nullptr, L"explore", ToWString(pluginsPath).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(pluginsPath); } void MainWindow::openProfileFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); } void MainWindow::openIniFolder() { if (m_OrganizerCore.currentProfile()->localSettingsEnabled()) { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); } else { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->documentsDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); } } void MainWindow::openDownloadsFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getDownloadDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OrganizerCore.settings().getDownloadDirectory()); } void MainWindow::openModsFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getModDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OrganizerCore.settings().getModDirectory()); } void MainWindow::openGameFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OrganizerCore.managedGame()->gameDirectory()); } void MainWindow::openMyGamesFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->documentsDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); } @@ -5164,7 +5157,7 @@ void MainWindow::addAsExecutable() QString arguments; FileExecutionTypes type; - if (!m_OrganizerCore.getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { + if (!GetFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { return; } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index bdaf4ffc..2542545f 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -267,6 +267,91 @@ bool checkService() return serviceRunning; } + +bool 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 = targetInfo.absoluteFilePath().toStdWString(); + 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 = QString::fromWCharArray(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 ExploreFile(const QString& path) +{ + const auto ws = path.toStdWString(); + + const auto h = ::ShellExecuteW( + nullptr, L"explore", ws.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + + // anything <= 32 is not an actual HINSTANCE and signals failure + return (h > reinterpret_cast(32)); +} + +bool ExploreFile(const QFileInfo& info) +{ + return ExploreFile(info.absolutePath()); +} + +bool ExploreFile(const QDir& dir) +{ + return ExploreFile(dir.absolutePath()); +} + + OrganizerCore::OrganizerCore(const QSettings &initSettings) : m_UserInterface(nullptr) , m_PluginContainer(nullptr) @@ -1220,76 +1305,13 @@ 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)) { + if (!GetFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { return false; } diff --git a/src/organizercore.h b/src/organizercore.h index 94cfa5ae..103443eb 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -56,12 +56,21 @@ namespace MOBase { class IPluginGame; } + enum class FileExecutionTypes { executable = 1, other = 2 }; +bool GetFileExecutionContext( + QWidget* parent, const QFileInfo &targetInfo, + QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); + +bool ExploreFile(const QString& path); +bool ExploreFile(const QFileInfo& info); +bool ExploreFile(const QDir& dir); + class OrganizerCore : public QObject, public MOBase::IPluginDiagnose { @@ -147,10 +156,6 @@ 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 = "", -- cgit v1.3.1 From ebb855dc1421e89813a1f4a74a4963d76aba9747 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 May 2019 03:10:37 -0400 Subject: ExploreFile() will select the file in explorer when the path is a file replaced more ShellExecute() calls with ExploreFile() --- src/downloadmanager.cpp | 25 ++++++++++++------------- src/modinfodialog.cpp | 2 +- src/organizercore.cpp | 44 +++++++++++++++++++++++++++++++++++++++----- src/organizercore.h | 2 +- src/overwriteinfodialog.cpp | 3 ++- 5 files changed, 55 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 78162f11..686092b5 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1037,7 +1037,7 @@ void DownloadManager::openFile(int index) return; } - ::ShellExecuteW(nullptr, L"explore", ToWString(QDir::toNativeSeparators(m_OutputDirectory)).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_OutputDirectory); return; } @@ -1047,23 +1047,22 @@ void DownloadManager::openInDownloadsFolder(int index) reportError(tr("OpenFileInDownloadsFolder: invalid download index %1").arg(index)); return; } - QString params = "/select,\""; - QDir path = QDir(m_OutputDirectory); - if (path.exists(getFileName(index))) { - params = params + QDir::toNativeSeparators(getFilePath(index)) + "\""; - ::ShellExecuteW(nullptr, nullptr, L"explorer", ToWString(params).c_str(), nullptr, SW_SHOWNORMAL); - return; - } - else if (path.exists(getFileName(index) + ".unfinished")) { - params = params + QDir::toNativeSeparators(getFilePath(index)+".unfinished") + "\""; + const auto path = getFilePath(index); - ::ShellExecuteW(nullptr, nullptr, L"explorer", ToWString(params).c_str(), nullptr, SW_SHOWNORMAL); + if (QFile::exists(path)) { + ExploreFile(path); return; } + else { + const auto unfinished = path + ".unfinished"; + if (QFile::exists(unfinished)) { + ExploreFile(unfinished); + return; + } + } - ::ShellExecuteW(nullptr, L"explore", ToWString(QDir::toNativeSeparators(m_OutputDirectory)).c_str(), nullptr, nullptr, SW_SHOWNORMAL); - return; + ExploreFile(m_OutputDirectory); } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 83c0169a..5fb3e9cf 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1242,7 +1242,7 @@ bool ModInfoDialog::recursiveDelete(const QModelIndex &index) void ModInfoDialog::on_openInExplorerButton_clicked() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_ModInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_ModInfo->absolutePath()); } void ModInfoDialog::deleteFile(const QModelIndex &index) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2542545f..d228dfe1 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -330,25 +330,59 @@ bool GetFileExecutionContext( } } -bool ExploreFile(const QString& path) + +bool ExploreDirectory(const QFileInfo& info) { - const auto ws = path.toStdWString(); + const auto path = QDir::toNativeSeparators(info.absoluteFilePath()); + const auto ws_path = path.toStdWString(); const auto h = ::ShellExecuteW( - nullptr, L"explore", ws.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + nullptr, L"explore", ws_path.c_str(), nullptr, nullptr, SW_SHOWNORMAL); // anything <= 32 is not an actual HINSTANCE and signals failure return (h > reinterpret_cast(32)); } +bool ExploreFileInDirectory(const QFileInfo& info) +{ + const auto path = QDir::toNativeSeparators(info.absoluteFilePath()); + const auto params = "/select,\"" + path + "\""; + const auto ws_params = params.toStdWString(); + + const auto h = ::ShellExecuteW( + nullptr, nullptr, L"explorer", ws_params.c_str(), nullptr, SW_SHOWNORMAL); + + // anything <= 32 is not an actual HINSTANCE and signals failure + return (h > reinterpret_cast(32)); +} + + bool ExploreFile(const QFileInfo& info) { - return ExploreFile(info.absolutePath()); + if (info.isFile()) { + return ExploreFileInDirectory(info); + } else if (info.isDir()) { + return ExploreDirectory(info); + } else { + // try the parent directory + const auto parent = info.dir(); + + if (parent.exists()) { + return ExploreDirectory(parent.absolutePath()); + } + } + + return false; +} + +bool ExploreFile(const QString& path) +{ + return ExploreFile(QFileInfo(path)); } bool ExploreFile(const QDir& dir) { - return ExploreFile(dir.absolutePath()); + return ExploreFile(QFileInfo(dir.absolutePath())); } diff --git a/src/organizercore.h b/src/organizercore.h index 103443eb..3f773277 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -67,8 +67,8 @@ bool GetFileExecutionContext( QWidget* parent, const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); -bool ExploreFile(const QString& path); bool ExploreFile(const QFileInfo& info); +bool ExploreFile(const QString& path); bool ExploreFile(const QDir& dir); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 3d82cf17..e9d7ce06 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "ui_overwriteinfodialog.h" #include "report.h" #include "utility.h" +#include "organizercore.h" #include #include #include @@ -263,7 +264,7 @@ void OverwriteInfoDialog::createDirectoryTriggered() void OverwriteInfoDialog::on_explorerButton_clicked() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_ModInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ExploreFile(m_ModInfo->absolutePath()); } void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) -- cgit v1.3.1 From 54a743faea566d9040a1f972aef6d077ff68b198 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 May 2019 03:23:08 -0400 Subject: reordered action variables to match the order in the context menu, added a few missing initializations --- src/modinfodialog.cpp | 16 +++++++++------- src/modinfodialog.h | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 5fb3e9cf..9bd36f7e 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -322,7 +322,8 @@ bool ExpanderWidget::opened() const ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), - m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr), + m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_RenameAction(nullptr), + m_DeleteAction(nullptr), m_HideAction(nullptr), m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) { @@ -480,16 +481,17 @@ void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath)); ui->fileTree->setColumnWidth(0, 300); - m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); + m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); + m_OpenAction = new QAction(tr("&Open"), ui->fileTree); m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); + m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); m_HideAction = new QAction(tr("&Hide"), ui->fileTree); m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); - m_OpenAction = new QAction(tr("&Open"), ui->fileTree); - m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); - QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); - QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); - QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); + QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); + QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); + QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); + QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index fdea2d4d..6bf30a52 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -414,10 +414,10 @@ private: std::set m_RequestIDs; bool m_RequestStarted; - QAction *m_DeleteAction; - QAction *m_RenameAction; - QAction *m_OpenAction; QAction *m_NewFolderAction; + QAction *m_OpenAction; + QAction *m_RenameAction; + QAction *m_DeleteAction; QAction *m_HideAction; QAction *m_UnhideAction; -- cgit v1.3.1 From 76708b9694070bcaa5775a097ae73ffc163ca31b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 25 May 2019 08:34:52 -0400 Subject: put explore and open functions in namespace shell previewDataFile() was duplicated in both MainWindow and ModInfoDialog, moved to OrganizerCore added preview menu item to filetree better logging when shell operations fail --- src/downloadmanager.cpp | 8 +- src/mainwindow.cpp | 98 ++++--------------- src/modinfodialog.cpp | 133 ++++++++++---------------- src/modinfodialog.h | 12 ++- src/organizercore.cpp | 228 +++++++++++++++++++++++++++++++++++++++++--- src/organizercore.h | 15 ++- src/overwriteinfodialog.cpp | 2 +- 7 files changed, 311 insertions(+), 185 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 686092b5..1412df51 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1037,7 +1037,7 @@ void DownloadManager::openFile(int index) return; } - ExploreFile(m_OutputDirectory); + shell::ExploreFile(m_OutputDirectory); return; } @@ -1051,18 +1051,18 @@ void DownloadManager::openInDownloadsFolder(int index) const auto path = getFilePath(index); if (QFile::exists(path)) { - ExploreFile(path); + shell::ExploreFile(path); return; } else { const auto unfinished = path + ".unfinished"; if (QFile::exists(unfinished)) { - ExploreFile(unfinished); + shell::ExploreFile(unfinished); return; } } - ExploreFile(m_OutputDirectory); + shell::ExploreFile(m_OutputDirectory); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ce6620c0..94cbe9b1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3241,12 +3241,12 @@ void MainWindow::openExplorer_clicked() if (selection->hasSelection() && selection->selectedRows().count() > 1) { for (QModelIndex idx : selection->selectedRows()) { ModInfo::Ptr info = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - ExploreFile(info->absolutePath()); + shell::ExploreFile(info->absolutePath()); } } else { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - ExploreFile(modInfo->absolutePath()); + shell::ExploreFile(modInfo->absolutePath()); } } @@ -3261,14 +3261,14 @@ void MainWindow::openOriginExplorer_clicked() continue; } ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - ExploreFile(modInfo->absolutePath()); + shell::ExploreFile(modInfo->absolutePath()); } } else { QModelIndex idx = selection->currentIndex(); QString fileName = idx.data().toString(); ModInfo::Ptr modInfo = ModInfo::getByIndex(ModInfo::getIndex(m_OrganizerCore.pluginList()->origin(fileName))); - ExploreFile(modInfo->absolutePath()); + shell::ExploreFile(modInfo->absolutePath()); } } @@ -3283,7 +3283,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - ExploreFile(modInfo->absolutePath()); + shell::ExploreFile(modInfo->absolutePath()); } } @@ -3304,7 +3304,7 @@ void MainWindow::openExplorer_activated() std::vector flags = modInfo->getFlags(); if (modInfo->isRegular() || (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end())) { - ExploreFile(modInfo->absolutePath()); + shell::ExploreFile(modInfo->absolutePath()); } } } @@ -4271,61 +4271,61 @@ void MainWindow::disableVisibleMods() void MainWindow::openInstanceFolder() { QString dataPath = qApp->property("dataPath").toString(); - ExploreFile(dataPath); + shell::ExploreFile(dataPath); } void MainWindow::openLogsFolder() { QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); - ExploreFile(logsPath); + shell::ExploreFile(logsPath); } void MainWindow::openInstallFolder() { - ExploreFile(qApp->applicationDirPath()); + shell::ExploreFile(qApp->applicationDirPath()); } void MainWindow::openPluginsFolder() { QString pluginsPath = QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - ExploreFile(pluginsPath); + shell::ExploreFile(pluginsPath); } void MainWindow::openProfileFolder() { - ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); } void MainWindow::openIniFolder() { if (m_OrganizerCore.currentProfile()->localSettingsEnabled()) { - ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); + shell::ExploreFile(m_OrganizerCore.currentProfile()->absolutePath()); } else { - ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); } } void MainWindow::openDownloadsFolder() { - ExploreFile(m_OrganizerCore.settings().getDownloadDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().getDownloadDirectory()); } void MainWindow::openModsFolder() { - ExploreFile(m_OrganizerCore.settings().getModDirectory()); + shell::ExploreFile(m_OrganizerCore.settings().getModDirectory()); } void MainWindow::openGameFolder() { - ExploreFile(m_OrganizerCore.managedGame()->gameDirectory()); + shell::ExploreFile(m_OrganizerCore.managedGame()->gameDirectory()); } void MainWindow::openMyGamesFolder() { - ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); + shell::ExploreFile(m_OrganizerCore.managedGame()->documentsDirectory()); } @@ -5304,67 +5304,7 @@ void MainWindow::disableSelectedMods_clicked() void MainWindow::previewDataFile() { QString fileName = QDir::fromNativeSeparators(m_ContextItem->data(0, Qt::UserRole).toString()); - - // what we have is an absolute path to the file in its actual location (for the primary origin) - // what we want is the path relative to the virtual data directory - - // we need to look in the virtual directory for the file to make sure the info is up to date. - - // check if the file comes from the actual data folder instead of a mod - QDir gameDirectory = m_OrganizerCore.managedGame()->dataDirectory().absolutePath(); - QString relativePath = gameDirectory.relativeFilePath(fileName); - QDir dirRelativePath = gameDirectory.relativeFilePath(fileName); - // if the file is on a different drive the dirRelativePath will actually be an absolute path so we make sure that is not the case - if (!dirRelativePath.isAbsolute() && !relativePath.startsWith("..")) { - fileName = relativePath; - } - else { - // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory - int offset = m_OrganizerCore.settings().getModDirectory().size() + 1; - offset = fileName.indexOf("/", offset); - fileName = fileName.mid(offset + 1); - } - - - - const FileEntry::Ptr file = m_OrganizerCore.directoryStructure()->searchFile(ToWString(fileName), nullptr); - - if (file.get() == nullptr) { - reportError(tr("file not found: %1").arg(qUtf8Printable(fileName))); - return; - } - - // set up preview dialog - PreviewDialog preview(fileName); - auto addFunc = [&] (int originId) { - FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId); - QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; - if (QFile::exists(filePath)) { - // it's very possible the file doesn't exist, because it's inside an archive. we don't support that - QWidget *wid = m_PluginContainer.previewGenerator().genPreview(filePath); - if (wid == nullptr) { - reportError(tr("failed to generate preview for %1").arg(filePath)); - } else { - preview.addVariant(ToQString(origin.getName()), wid); - } - } - }; - - addFunc(file->getOrigin()); - for (auto alt : file->getAlternatives()) { - addFunc(alt.first); - } - if (preview.numVariants() > 0) { - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(preview.objectName()); - if (settings.contains(key)) { - preview.restoreGeometry(settings.value(key).toByteArray()); - } - preview.exec(); - settings.setValue(key, preview.saveGeometry()); - } else { - QMessageBox::information(this, tr("Sorry"), tr("Sorry, can't preview anything. This function currently does not support extracting from bsas.")); - } + m_OrganizerCore.previewFileWithAlternatives(this, fileName); } void MainWindow::openDataFile() @@ -5374,7 +5314,7 @@ void MainWindow::openDataFile() } QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - m_OrganizerCore.executeFile(this, targetInfo); + m_OrganizerCore.executeFileVirtualized(this, targetInfo); } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 9bd36f7e..c3ef7c47 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -322,9 +322,9 @@ bool ExpanderWidget::opened() const ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), - m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_RenameAction(nullptr), - m_DeleteAction(nullptr), m_HideAction(nullptr), m_UnhideAction(nullptr), - m_Directory(directory), m_Origin(nullptr), + m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_PreviewAction(nullptr), + m_RenameAction(nullptr), m_DeleteAction(nullptr), m_HideAction(nullptr), + m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) { ui->setupUi(this); @@ -483,16 +483,18 @@ void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); m_OpenAction = new QAction(tr("&Open"), ui->fileTree); + m_PreviewAction = new QAction(tr("&Preview"), ui->fileTree); m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); m_HideAction = new QAction(tr("&Hide"), ui->fileTree); m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); - QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); - QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); - QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); - QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); - QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); + connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); + connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); + connect(m_PreviewAction, SIGNAL(triggered()), this, SLOT(previewTriggered())); + connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); + connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); + connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); } @@ -1244,12 +1246,11 @@ bool ModInfoDialog::recursiveDelete(const QModelIndex &index) void ModInfoDialog::on_openInExplorerButton_clicked() { - ExploreFile(m_ModInfo->absolutePath()); + shell::ExploreFile(m_ModInfo->absolutePath()); } void ModInfoDialog::deleteFile(const QModelIndex &index) { - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) : m_FileSystemModel->remove(index); if (!res) { @@ -1406,21 +1407,29 @@ void ModInfoDialog::changeFiletreeVisibility(bool hide) } -void ModInfoDialog::openFile(const QModelIndex &index) +void ModInfoDialog::openTriggered() { - QString fileName = m_FileSystemModel->filePath(index); + if (m_FileSelection.size() == 1) { + const auto index = m_FileSelection.at(0); + if (!index.isValid()) { + return; + } - HINSTANCE res = ::ShellExecuteW(nullptr, L"open", ToWString(fileName).c_str(), nullptr, nullptr, SW_SHOW); - if ((unsigned long long)res <= 32) { - qCritical("failed to invoke %s: %d", qUtf8Printable(fileName), res); + QString fileName = m_FileSystemModel->filePath(index); + shell::OpenFile(fileName); } } - -void ModInfoDialog::openTriggered() +void ModInfoDialog::previewTriggered() { - foreach(QModelIndex idx, m_FileSelection) { - openFile(idx); + if (m_FileSelection.size() == 1) { + const auto index = m_FileSelection.at(0); + if (!index.isValid()) { + return; + } + + QString fileName = m_FileSystemModel->filePath(index); + m_OrganizerCore->previewFile(this, m_ModInfo->name(), fileName); } } @@ -1464,6 +1473,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) if (selectionModel->hasSelection()) { bool enableOpen = true; + bool enablePreview = true; bool enableRename = true; bool enableDelete = true; bool enableHide = true; @@ -1484,10 +1494,15 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) if (!hasFiles) { enableOpen = false; + enablePreview = false; } const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + if (!canPreviewFile(false, fileName)) { + enablePreview = false; + } + if (!canHideFile(false, fileName)) { enableHide = false; } @@ -1499,6 +1514,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) // this is a multiple selection, don't show open action so users don't open // a thousand files enableOpen = false; + enablePreview = false; enableRename = false; if (m_FileSelection.size() < max_scan_for_visibility) { @@ -1530,6 +1546,10 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) menu.addAction(m_OpenAction); } + if (enablePreview) { + menu.addAction(m_PreviewAction); + } + if (enableRename) { menu.addAction(m_RenameAction); } @@ -1755,7 +1775,7 @@ void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) } QFileInfo targetInfo(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore->executeFile(this, targetInfo); + m_OrganizerCore->executeFileVirtualized(this, targetInfo); } void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) @@ -1765,63 +1785,17 @@ void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) } QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); + m_OrganizerCore->previewFileWithAlternatives(this, fileName); +} - // what we have is an absolute path to the file in its actual location (for the primary origin) - // what we want is the path relative to the virtual data directory - - // we need to look in the virtual directory for the file to make sure the info is up to date. - - // check if the file comes from the actual data folder instead of a mod - QDir gameDirectory = m_OrganizerCore->managedGame()->dataDirectory().absolutePath(); - QString relativePath = gameDirectory.relativeFilePath(fileName); - QDir direRelativePath = gameDirectory.relativeFilePath(fileName); - // if the file is on a different drive the dirRelativePath will actually be an absolute path so we make sure that is not the case - if (!direRelativePath.isAbsolute() && !relativePath.startsWith("..")) { - fileName = relativePath; - } - else { - // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory - int offset = m_OrganizerCore->settings().getModDirectory().size() + 1; - offset = fileName.indexOf("/", offset); - fileName = fileName.mid(offset + 1); - } - - - - const FileEntry::Ptr file = m_OrganizerCore->directoryStructure()->searchFile(ToWString(fileName), nullptr); - - if (file.get() == nullptr) { - reportError(tr("file not found: %1").arg(qUtf8Printable(fileName))); - return; - } - - // set up preview dialog - PreviewDialog preview(fileName); - auto addFunc = [&](int originId) { - FilesOrigin &origin = m_OrganizerCore->directoryStructure()->getOriginByID(originId); - QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; - if (QFile::exists(filePath)) { - // it's very possible the file doesn't exist, because it's inside an archive. we don't support that - QWidget *wid = m_PluginContainer->previewGenerator().genPreview(filePath); - if (wid == nullptr) { - reportError(tr("failed to generate preview for %1").arg(filePath)); - } - else { - preview.addVariant(ToQString(origin.getName()), wid); - } - } - }; +bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) const +{ + if (isArchive) { + return false; + } - addFunc(file->getOrigin()); - for (auto alt : file->getAlternatives()) { - addFunc(alt.first); - } - if (preview.numVariants() > 0) { - preview.exec(); - } - else { - QMessageBox::information(this, tr("Sorry"), tr("Sorry, can't preview anything. This function currently does not support extracting from bsas.")); - } + const auto ext = QFileInfo(filename).suffix(); + return m_PluginContainer->previewGenerator().previewSupported(ext); } bool ModInfoDialog::canHideFile(bool isArchive, const QString& filename) const @@ -1866,12 +1840,9 @@ bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const { - const QString fileName = item->data(0, Qt::UserRole).toString(); - if (!m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - return false; - } - - return true; + return canPreviewFile( + item->data(1, Qt::UserRole + 2).toBool(), + item->data(0, Qt::UserRole).toString()); } void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6bf30a52..c0730afa 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -313,7 +313,6 @@ private: QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); - void openFile(const QModelIndex &index); void saveIniTweaks(); void saveCategories(QTreeWidgetItem *currentNode); void saveCurrentTextFile(); @@ -348,10 +347,11 @@ private slots: void delete_activated(); - void deleteTriggered(); - void renameTriggered(); - void openTriggered(); void createDirectoryTriggered(); + void openTriggered(); + void previewTriggered(); + void renameTriggered(); + void deleteTriggered(); void hideTriggered(); void unhideTriggered(); @@ -416,6 +416,7 @@ private: QAction *m_NewFolderAction; QAction *m_OpenAction; + QAction *m_PreviewAction; QAction *m_RenameAction; QAction *m_DeleteAction; QAction *m_HideAction; @@ -439,11 +440,12 @@ private: bool canUnhideConflictItem(const QTreeWidgetItem* item) const; bool canPreviewConflictItem(const QTreeWidgetItem* item) const; - void previewDataFile(const QTreeWidgetItem* item); void openDataFile(const QTreeWidgetItem* item); + void previewDataFile(const QTreeWidgetItem* item); void changeConflictFilesVisibility(bool hide); void changeFiletreeVisibility(bool hide); + bool canPreviewFile(bool isArchive, const QString& filename) const; bool canHideFile(bool isArchive, const QString& filename) const; bool canUnhideFile(bool isArchive, const QString& filename) const; }; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d228dfe1..3a6b810e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -35,6 +35,7 @@ #include "instancemanager.h" #include #include "helper.h" +#include "previewdialog.h" #include #include @@ -331,16 +332,103 @@ bool GetFileExecutionContext( } -bool ExploreDirectory(const QFileInfo& info) +const char* ShellExecuteError(int i) { - const auto path = QDir::toNativeSeparators(info.absoluteFilePath()); - const auto ws_path = path.toStdWString(); + switch (i) { + case 0: + return "The operating system is out of memory or resources"; + + case ERROR_FILE_NOT_FOUND: + return "The specified file was not found"; + + case ERROR_PATH_NOT_FOUND: + return "The specified path was not found"; + + case ERROR_BAD_FORMAT: + return "The .exe file is invalid (non-Win32 .exe or error in .exe image)"; + + case SE_ERR_ACCESSDENIED: + return "The operating system denied access to the specified file"; + + case SE_ERR_ASSOCINCOMPLETE: + return "The file name association is incomplete or invalid"; + + case SE_ERR_DDEBUSY: + return "The DDE transaction could not be completed because other DDE " + "transactions were being processed"; + + case SE_ERR_DDEFAIL: + return "The DDE transaction failed"; + + case SE_ERR_DDETIMEOUT: + return "The DDE transaction could not be completed because the request " + "timed out"; + + case SE_ERR_DLLNOTFOUND: + return "The specified DLL was not found"; + + case SE_ERR_NOASSOC: + return "There is no application associated with the given file name " + "extension"; + + case SE_ERR_OOM: + return "There was not enough memory to complete the operation"; + + case SE_ERR_SHARE: + return "A sharing violation occurred"; + + default: + return "Unknown error"; + } +} + +void LogShellFailure( + const wchar_t* operation, const wchar_t* file, const wchar_t* params, + HINSTANCE h) +{ + const auto code = static_cast(reinterpret_cast(h)); + + QString s = "failed to invoke"; + + if (operation) { + s += " " + QString::fromWCharArray(operation); + } + + if (file) { + s += " " + QString::fromWCharArray(file); + } + if (params) { + s += " " + QString::fromWCharArray(params); + } + + qCritical( + "failed to invoke %s: %s (error %d)", + s, ShellExecuteError(code), code); +} + +bool ShellExecuteWrapper( + const wchar_t* operation, const wchar_t* file, const wchar_t* params) +{ const auto h = ::ShellExecuteW( - nullptr, L"explore", ws_path.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + 0, operation, file, params, nullptr, SW_SHOWNORMAL); // anything <= 32 is not an actual HINSTANCE and signals failure - return (h > reinterpret_cast(32)); + if (h <= reinterpret_cast(32)) + { + LogShellFailure(operation, file, params, h); + return false; + } + + return true; +} + +bool ExploreDirectory(const QFileInfo& info) +{ + const auto path = QDir::toNativeSeparators(info.absoluteFilePath()); + const auto ws_path = path.toStdWString(); + + return ShellExecuteWrapper(L"explore", ws_path.c_str(), nullptr); } bool ExploreFileInDirectory(const QFileInfo& info) @@ -349,14 +437,13 @@ bool ExploreFileInDirectory(const QFileInfo& info) const auto params = "/select,\"" + path + "\""; const auto ws_params = params.toStdWString(); - const auto h = ::ShellExecuteW( - nullptr, nullptr, L"explorer", ws_params.c_str(), nullptr, SW_SHOWNORMAL); - - // anything <= 32 is not an actual HINSTANCE and signals failure - return (h > reinterpret_cast(32)); + return ShellExecuteWrapper(nullptr, L"explorer", ws_params.c_str()); } +namespace shell +{ + bool ExploreFile(const QFileInfo& info) { if (info.isFile()) { @@ -385,6 +472,14 @@ bool ExploreFile(const QDir& dir) return ExploreFile(QFileInfo(dir.absolutePath())); } +bool OpenFile(const QString& path) +{ + const auto ws_path = path.toStdWString(); + return ShellExecuteWrapper(L"open", ws_path.c_str(), nullptr); +} + +} // namespace shell + OrganizerCore::OrganizerCore(const QSettings &initSettings) : m_UserInterface(nullptr) @@ -1339,7 +1434,8 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res; } -bool OrganizerCore::executeFile(QWidget* parent, const QFileInfo& targetInfo) +bool OrganizerCore::executeFileVirtualized( + QWidget* parent, const QFileInfo& targetInfo) { QFileInfo binaryInfo; QString arguments; @@ -1372,6 +1468,116 @@ bool OrganizerCore::executeFile(QWidget* parent, const QFileInfo& targetInfo) return false; } +bool OrganizerCore::previewFileWithAlternatives( + QWidget* parent, QString fileName) +{ + // what we have is an absolute path to the file in its actual location (for the primary origin) + // what we want is the path relative to the virtual data directory + + // we need to look in the virtual directory for the file to make sure the info is up to date. + + // check if the file comes from the actual data folder instead of a mod + QDir gameDirectory = managedGame()->dataDirectory().absolutePath(); + QString relativePath = gameDirectory.relativeFilePath(fileName); + QDir dirRelativePath = gameDirectory.relativeFilePath(fileName); + + // if the file is on a different drive the dirRelativePath will actually be an + // absolute path so we make sure that is not the case + if (!dirRelativePath.isAbsolute() && !relativePath.startsWith("..")) { + fileName = relativePath; + } + else { + // crude: we search for the next slash after the base mod directory to skip + // everything up to the data-relative directory + int offset = settings().getModDirectory().size() + 1; + offset = fileName.indexOf("/", offset); + fileName = fileName.mid(offset + 1); + } + + + + const FileEntry::Ptr file = directoryStructure()->searchFile(ToWString(fileName), nullptr); + + if (file.get() == nullptr) { + reportError(tr("file not found: %1").arg(qUtf8Printable(fileName))); + return false; + } + + // set up preview dialog + PreviewDialog preview(fileName); + auto addFunc = [&](int originId) { + FilesOrigin &origin = directoryStructure()->getOriginByID(originId); + QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; + if (QFile::exists(filePath)) { + // it's very possible the file doesn't exist, because it's inside an archive. we don't support that + QWidget *wid = m_PluginContainer->previewGenerator().genPreview(filePath); + if (wid == nullptr) { + reportError(tr("failed to generate preview for %1").arg(filePath)); + } + else { + preview.addVariant(ToQString(origin.getName()), wid); + } + } + }; + + addFunc(file->getOrigin()); + for (auto alt : file->getAlternatives()) { + addFunc(alt.first); + } + + if (preview.numVariants() > 0) { + QSettings &s = settings().directInterface(); + QString key = QString("geometry/%1").arg(preview.objectName()); + if (s.contains(key)) { + preview.restoreGeometry(s.value(key).toByteArray()); + } + + preview.exec(); + + s.setValue(key, preview.saveGeometry()); + + return true; + } + else { + QMessageBox::information( + parent, tr("Sorry"), + tr("Sorry, can't preview anything. This function currently does not support extracting from bsas.")); + + return false; + } +} + +bool OrganizerCore::previewFile( + QWidget* parent, const QString& originName, const QString& path) +{ + if (!QFile::exists(path)) { + reportError(tr("File '%1' not found.").arg(path)); + return false; + } + + PreviewDialog preview(path); + + QWidget *wid = m_PluginContainer->previewGenerator().genPreview(path); + if (wid == nullptr) { + reportError(tr("Failed to generate preview for %1").arg(path)); + return false; + } + + preview.addVariant(originName, wid); + + QSettings &s = settings().directInterface(); + QString key = QString("geometry/%1").arg(preview.objectName()); + if (s.contains(key)) { + preview.restoreGeometry(s.value(key).toByteArray()); + } + + preview.exec(); + + s.setValue(key, preview.saveGeometry()); + + return true; +} + void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, diff --git a/src/organizercore.h b/src/organizercore.h index 3f773277..41f0fd46 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -67,9 +67,14 @@ bool GetFileExecutionContext( QWidget* parent, const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); -bool ExploreFile(const QFileInfo& info); -bool ExploreFile(const QString& path); -bool ExploreFile(const QDir& dir); +namespace shell +{ + bool ExploreFile(const QFileInfo& info); + bool ExploreFile(const QString& path); + bool ExploreFile(const QDir& dir); + + bool OpenFile(const QString& path); +} class OrganizerCore : public QObject, public MOBase::IPluginDiagnose @@ -156,7 +161,9 @@ public: void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } - bool executeFile(QWidget* parent, const QFileInfo& targetInfo); + bool executeFileVirtualized(QWidget* parent, const QFileInfo& targetInfo); + bool previewFileWithAlternatives(QWidget* parent, QString filename); + bool previewFile(QWidget* parent, const QString& originName, const QString& path); void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index e9d7ce06..d6764852 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -264,7 +264,7 @@ void OverwriteInfoDialog::createDirectoryTriggered() void OverwriteInfoDialog::on_explorerButton_clicked() { - ExploreFile(m_ModInfo->absolutePath()); + shell::ExploreFile(m_ModInfo->absolutePath()); } void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) -- cgit v1.3.1 From c82c7af678c088a6b94fc8a4a0f31fc9498e8220 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 04:00:54 -0400 Subject: changed rest of ShellExecuteW() calls to use shell::Execute(), shell::OpenLink() or shell::OpenFile() --- src/downloadmanager.cpp | 4 ++-- src/modinfodialog.cpp | 3 +-- src/motddialog.cpp | 3 ++- src/organizercore.cpp | 14 ++++++++++++++ src/organizercore.h | 3 +++ src/overwriteinfodialog.cpp | 7 +------ src/problemsdialog.cpp | 3 ++- src/selfupdater.cpp | 19 ++++++------------- src/settings.cpp | 23 ++++++++++++----------- 9 files changed, 43 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 1412df51..ecc3cfd6 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1030,10 +1030,10 @@ void DownloadManager::openFile(int index) reportError(tr("OpenFile: invalid download index %1").arg(index)); return; } + QDir path = QDir(m_OutputDirectory); if (path.exists(getFileName(index))) { - - ::ShellExecuteW(nullptr, L"open", ToWString(QDir::toNativeSeparators(getFilePath(index))).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + shell::OpenFile(getFilePath(index)); return; } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c3ef7c47..19a03ea7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1075,10 +1075,9 @@ void ModInfoDialog::linkClicked(const QUrl &url) //Ideally we'd ask the mod for the game and the web service then pass the game //and URL to the web service if (NexusInterface::instance(m_PluginContainer)->isURLGameRelated(url)) { - emit linkActivated(url.toString()); } else { - ::ShellExecuteW(nullptr, L"open", ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + shell::OpenLink(url); } } diff --git a/src/motddialog.cpp b/src/motddialog.cpp index 96d88542..9be41d96 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "bbcode.h" #include "utility.h" #include "ui_motddialog.h" +#include "organizercore.h" #include MotDDialog::MotDDialog(const QString &message, QWidget *parent) @@ -43,5 +44,5 @@ void MotDDialog::on_okButton_clicked() void MotDDialog::linkClicked(const QUrl &url) { - ::ShellExecuteW(nullptr, L"open", MOBase::ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + shell::OpenLink(url); } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3a6b810e..a10c23d7 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -478,6 +478,20 @@ bool OpenFile(const QString& path) return ShellExecuteWrapper(L"open", ws_path.c_str(), nullptr); } +bool OpenLink(const QUrl& url) +{ + const auto ws_url = url.toString().toStdWString(); + return ShellExecuteWrapper(L"open", ws_url.c_str(), nullptr); +} + +bool Execute(const QString& program, const QString& params) +{ + const auto program_ws = program.toStdWString(); + const auto params_ws = params.toStdWString(); + + return ShellExecuteWrapper(L"open", program_ws.c_str(), params_ws.c_str()); +} + } // namespace shell diff --git a/src/organizercore.h b/src/organizercore.h index 41f0fd46..c9434e92 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -74,6 +74,9 @@ namespace shell bool ExploreFile(const QDir& dir); bool OpenFile(const QString& path); + bool OpenLink(const QUrl& url); + + bool Execute(const QString& program, const QString& params); } diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index d6764852..9b3e55df 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -218,12 +218,7 @@ void OverwriteInfoDialog::renameTriggered() void OverwriteInfoDialog::openFile(const QModelIndex &index) { - QString fileName = m_FileSystemModel->filePath(index); - - HINSTANCE res = ::ShellExecuteW(nullptr, L"open", ToWString(fileName).c_str(), nullptr, nullptr, SW_SHOW); - if ((INT_PTR)res <= 32) { - qCritical("failed to invoke %s: %d", qUtf8Printable(fileName), res); - } + shell::OpenFile(m_FileSystemModel->filePath(index)); } diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 795baab0..56109d34 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -1,5 +1,6 @@ #include "problemsdialog.h" #include "ui_problemsdialog.h" +#include "organizercore.h" #include #include #include @@ -87,5 +88,5 @@ void ProblemsDialog::startFix() void ProblemsDialog::urlClicked(const QUrl &url) { - ::ShellExecuteW(nullptr, L"open", ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + shell::OpenLink(url); } diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 4c0f9a8d..271c621b 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -31,6 +31,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "bbcode.h" #include "plugincontainer.h" +#include "organizercore.h" #include #include #include @@ -178,7 +179,7 @@ void SelfUpdater::startUpdate() tr("New update available (%1)") .arg(m_UpdateCandidate["tag_name"].toString()), tr("Do you want to install update? All your mods and setup will be left untouched.\nSelect Show Details option to see the full change-log."), QMessageBox::Yes | QMessageBox::Cancel, m_Parent); - + query.setDetailedText(m_UpdateCandidate["body"].toString()); query.button(QMessageBox::Yes)->setText(tr("Install")); @@ -329,22 +330,14 @@ void SelfUpdater::downloadCancel() void SelfUpdater::installUpdate() { - const QString mopath - = QDir::fromNativeSeparators(qApp->property("dataPath").toString()); - - std::wstring parameters = ToWString("/DIR=\"" + qApp->applicationDirPath() + "\" "); + const QString parameters = "/DIR=\"" + qApp->applicationDirPath() + "\" "; - HINSTANCE res = ::ShellExecuteW( - nullptr, L"open", m_UpdateFile.fileName().toStdWString().c_str(), parameters.c_str(), - nullptr, SW_SHOW); - - if (res > (HINSTANCE)32) { + if (shell::Execute(m_UpdateFile.fileName(), parameters)) { QCoreApplication::quit(); } else { - reportError(tr("Failed to start %1: %2") - .arg(m_UpdateFile.fileName()) - .arg((INT_PTR)res)); + reportError(tr("Failed to start %1").arg(m_UpdateFile.fileName())); } + m_UpdateFile.remove(); } diff --git a/src/settings.cpp b/src/settings.cpp index 231487bb..a844fdc9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -130,18 +130,19 @@ bool Settings::pluginBlacklisted(const QString &fileName) const void Settings::registerAsNXMHandler(bool force) { - std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); - std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); - std::wstring mode = force ? L"forcereg" : L"reg"; - std::wstring parameters = mode + L" " + m_GamePlugin->gameShortName().toStdWString(); - for (QString altGame : m_GamePlugin->validShortNames()) { - parameters += L"," + altGame.toStdWString(); + const auto nxmPath = QCoreApplication::applicationDirPath() + "/nxmhandler.exe"; + const auto executable = QCoreApplication::applicationFilePath(); + + QString mode = force ? "forcereg" : "reg"; + QString parameters = mode + " " + m_GamePlugin->gameShortName(); + for (const QString& altGame : m_GamePlugin->validShortNames()) { + parameters += "," + altGame; } - parameters += L" \"" + executable + L"\""; - HINSTANCE res = ::ShellExecuteW(nullptr, L"open", nxmPath.c_str(), parameters.c_str(), nullptr, SW_SHOWNORMAL); - if ((INT_PTR)res <= 32) { - QMessageBox::critical(nullptr, tr("Failed"), - tr("Sorry, failed to start the helper application")); + parameters += " \"" + executable + "\""; + + if (!shell::Execute(nxmPath, parameters)) { + QMessageBox::critical( + nullptr, tr("Failed"), tr("Failed to start the helper application")); } } -- cgit v1.3.1 From 4183e5837f6363193b1a3d7f7ed56cd9ab89ec36 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 04:27:16 -0400 Subject: set use_folders, puts all cmake generated projects into a folder --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 496bd5fb..fcfb8a12 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -451,6 +451,7 @@ FIND_PACKAGE(Qt5Qml REQUIRED) FIND_PACKAGE(Qt5LinguistTools) SET(mo_translation_sources ${CMAKE_SOURCE_DIR}/src ${project_path}/uibase/src) +set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY AUTOGEN_SOURCE_GROUP autogen) set_property(GLOBAL PROPERTY AUTOMOC_SOURCE_GROUP autogen) set_property(GLOBAL PROPERTY AUTORCC_SOURCE_GROUP autogen) -- cgit v1.3.1 From 0439e18fe4cd2500c4ab6b3ff219c53153cf2175 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 05:31:02 -0400 Subject: changed enum case to follow convention --- src/mainwindow.cpp | 4 ++-- src/organizercore.cpp | 12 ++++++------ src/organizercore.h | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 94cbe9b1..b1728617 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5163,7 +5163,7 @@ void MainWindow::addAsExecutable() switch (type) { - case FileExecutionTypes::executable: { + case FileExecutionTypes::Executable: { QString name = QInputDialog::getText(this, tr("Enter Name"), tr("Please enter a name for the executable"), QLineEdit::Normal, targetInfo.baseName()); @@ -5182,7 +5182,7 @@ void MainWindow::addAsExecutable() break; } - case FileExecutionTypes::other: // fall-through + case FileExecutionTypes::Other: // fall-through default: { QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); break; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index a10c23d7..04c94164 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -279,11 +279,11 @@ bool GetFileExecutionContext( (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; + type = FileExecutionTypes::Executable; return true; } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { binaryInfo = targetInfo; - type = FileExecutionTypes::executable; + type = FileExecutionTypes::Executable; return true; } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { // types that need to be injected into @@ -323,10 +323,10 @@ bool GetFileExecutionContext( arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); } - type = FileExecutionTypes::executable; + type = FileExecutionTypes::Executable; return true; } else { - type = FileExecutionTypes::other; + type = FileExecutionTypes::Other; return true; } } @@ -1461,7 +1461,7 @@ bool OrganizerCore::executeFileVirtualized( switch (type) { - case FileExecutionTypes::executable: { + case FileExecutionTypes::Executable: { spawnBinaryDirect( binaryInfo, arguments, currentProfile()->name(), targetInfo.absolutePath(), "", ""); @@ -1469,7 +1469,7 @@ bool OrganizerCore::executeFileVirtualized( return true; } - case FileExecutionTypes::other: { + case FileExecutionTypes::Other: { ::ShellExecuteW(nullptr, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); diff --git a/src/organizercore.h b/src/organizercore.h index c9434e92..6f9defc2 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -59,8 +59,8 @@ namespace MOBase { enum class FileExecutionTypes { - executable = 1, - other = 2 + Executable = 1, + Other = 2 }; bool GetFileExecutionContext( -- cgit v1.3.1 From 21185182c6551d053cccdcf087e0c219a3785cf8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 06:18:11 -0400 Subject: moved getFileExecutionContext() and its enum into OrganizerCore --- src/mainwindow.cpp | 4 +- src/organizercore.cpp | 127 +++++++++++++++++++++++++------------------------- src/organizercore.h | 20 ++++---- 3 files changed, 76 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b1728617..810caa01 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5152,12 +5152,14 @@ void MainWindow::addAsExecutable() return; } + using FileExecutionTypes = OrganizerCore::FileExecutionTypes; + QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); QFileInfo binaryInfo; QString arguments; FileExecutionTypes type; - if (!GetFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { + if (!OrganizerCore::getFileExecutionContext(this, targetInfo, binaryInfo, arguments, type)) { return; } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 04c94164..d3de3e01 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -269,69 +269,6 @@ bool checkService() } -bool 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 = targetInfo.absoluteFilePath().toStdWString(); - 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 = QString::fromWCharArray(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; - } -} - - const char* ShellExecuteError(int i) { switch (i) { @@ -1448,6 +1385,68 @@ 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 = targetInfo.absoluteFilePath().toStdWString(); + 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 = QString::fromWCharArray(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::executeFileVirtualized( QWidget* parent, const QFileInfo& targetInfo) { @@ -1455,7 +1454,7 @@ bool OrganizerCore::executeFileVirtualized( QString arguments; FileExecutionTypes type; - if (!GetFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { + if (!getFileExecutionContext(parent, targetInfo, binaryInfo, arguments, type)) { return false; } diff --git a/src/organizercore.h b/src/organizercore.h index 6f9defc2..9c4e3ae2 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -57,16 +57,6 @@ namespace MOBase { } -enum class FileExecutionTypes -{ - Executable = 1, - Other = 2 -}; - -bool GetFileExecutionContext( - QWidget* parent, const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); - namespace shell { bool ExploreFile(const QFileInfo& info); @@ -111,6 +101,12 @@ private: typedef boost::signals2::signal SignalModInstalled; public: + enum class FileExecutionTypes + { + Executable = 1, + Other = 2 + }; + static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } OrganizerCore(const QSettings &initSettings); @@ -164,6 +160,10 @@ 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 executeFileVirtualized(QWidget* parent, const QFileInfo& targetInfo); bool previewFileWithAlternatives(QWidget* parent, QString filename); bool previewFile(QWidget* parent, const QString& originName, const QString& path); -- cgit v1.3.1 From fce3c9f8c3058d9975ccc634be3572a431c1a50d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 06:52:45 -0400 Subject: moved shell functions to uibase --- src/motddialog.cpp | 3 + src/organizercore.cpp | 163 -------------------------------------------------- src/organizercore.h | 13 ---- 3 files changed, 3 insertions(+), 176 deletions(-) (limited to 'src') diff --git a/src/motddialog.cpp b/src/motddialog.cpp index 9be41d96..ca1e60ad 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -22,8 +22,11 @@ along with Mod Organizer. If not, see . #include "utility.h" #include "ui_motddialog.h" #include "organizercore.h" +#include #include +using namespace MOBase; + MotDDialog::MotDDialog(const QString &message, QWidget *parent) : QDialog(parent), ui(new Ui::MotDDialog) { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d3de3e01..19b53b8d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -269,169 +269,6 @@ bool checkService() } -const char* ShellExecuteError(int i) -{ - switch (i) { - case 0: - return "The operating system is out of memory or resources"; - - case ERROR_FILE_NOT_FOUND: - return "The specified file was not found"; - - case ERROR_PATH_NOT_FOUND: - return "The specified path was not found"; - - case ERROR_BAD_FORMAT: - return "The .exe file is invalid (non-Win32 .exe or error in .exe image)"; - - case SE_ERR_ACCESSDENIED: - return "The operating system denied access to the specified file"; - - case SE_ERR_ASSOCINCOMPLETE: - return "The file name association is incomplete or invalid"; - - case SE_ERR_DDEBUSY: - return "The DDE transaction could not be completed because other DDE " - "transactions were being processed"; - - case SE_ERR_DDEFAIL: - return "The DDE transaction failed"; - - case SE_ERR_DDETIMEOUT: - return "The DDE transaction could not be completed because the request " - "timed out"; - - case SE_ERR_DLLNOTFOUND: - return "The specified DLL was not found"; - - case SE_ERR_NOASSOC: - return "There is no application associated with the given file name " - "extension"; - - case SE_ERR_OOM: - return "There was not enough memory to complete the operation"; - - case SE_ERR_SHARE: - return "A sharing violation occurred"; - - default: - return "Unknown error"; - } -} - -void LogShellFailure( - const wchar_t* operation, const wchar_t* file, const wchar_t* params, - HINSTANCE h) -{ - const auto code = static_cast(reinterpret_cast(h)); - - QString s = "failed to invoke"; - - if (operation) { - s += " " + QString::fromWCharArray(operation); - } - - if (file) { - s += " " + QString::fromWCharArray(file); - } - - if (params) { - s += " " + QString::fromWCharArray(params); - } - - qCritical( - "failed to invoke %s: %s (error %d)", - s, ShellExecuteError(code), code); -} - -bool ShellExecuteWrapper( - const wchar_t* operation, const wchar_t* file, const wchar_t* params) -{ - const auto h = ::ShellExecuteW( - 0, operation, file, params, nullptr, SW_SHOWNORMAL); - - // anything <= 32 is not an actual HINSTANCE and signals failure - if (h <= reinterpret_cast(32)) - { - LogShellFailure(operation, file, params, h); - return false; - } - - return true; -} - -bool ExploreDirectory(const QFileInfo& info) -{ - const auto path = QDir::toNativeSeparators(info.absoluteFilePath()); - const auto ws_path = path.toStdWString(); - - return ShellExecuteWrapper(L"explore", ws_path.c_str(), nullptr); -} - -bool ExploreFileInDirectory(const QFileInfo& info) -{ - const auto path = QDir::toNativeSeparators(info.absoluteFilePath()); - const auto params = "/select,\"" + path + "\""; - const auto ws_params = params.toStdWString(); - - return ShellExecuteWrapper(nullptr, L"explorer", ws_params.c_str()); -} - - -namespace shell -{ - -bool ExploreFile(const QFileInfo& info) -{ - if (info.isFile()) { - return ExploreFileInDirectory(info); - } else if (info.isDir()) { - return ExploreDirectory(info); - } else { - // try the parent directory - const auto parent = info.dir(); - - if (parent.exists()) { - return ExploreDirectory(parent.absolutePath()); - } - } - - return false; -} - -bool ExploreFile(const QString& path) -{ - return ExploreFile(QFileInfo(path)); -} - -bool ExploreFile(const QDir& dir) -{ - return ExploreFile(QFileInfo(dir.absolutePath())); -} - -bool OpenFile(const QString& path) -{ - const auto ws_path = path.toStdWString(); - return ShellExecuteWrapper(L"open", ws_path.c_str(), nullptr); -} - -bool OpenLink(const QUrl& url) -{ - const auto ws_url = url.toString().toStdWString(); - return ShellExecuteWrapper(L"open", ws_url.c_str(), nullptr); -} - -bool Execute(const QString& program, const QString& params) -{ - const auto program_ws = program.toStdWString(); - const auto params_ws = params.toStdWString(); - - return ShellExecuteWrapper(L"open", program_ws.c_str(), params_ws.c_str()); -} - -} // namespace shell - - OrganizerCore::OrganizerCore(const QSettings &initSettings) : m_UserInterface(nullptr) , m_PluginContainer(nullptr) diff --git a/src/organizercore.h b/src/organizercore.h index 9c4e3ae2..e24227b7 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -57,19 +57,6 @@ namespace MOBase { } -namespace shell -{ - bool ExploreFile(const QFileInfo& info); - bool ExploreFile(const QString& path); - bool ExploreFile(const QDir& dir); - - bool OpenFile(const QString& path); - bool OpenLink(const QUrl& url); - - bool Execute(const QString& program, const QString& params); -} - - class OrganizerCore : public QObject, public MOBase::IPluginDiagnose { -- cgit v1.3.1 From e821768f7997d8d86585db9490554a1202aa1ad9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 08:07:10 -0400 Subject: added /permissive- --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 40fe2630..57cd2f77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -333,7 +333,7 @@ TARGET_LINK_LIBRARIES(ModOrganizer Dbghelp advapi32 Version Shlwapi liblz4 Crypt32) IF (MSVC) - SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS "/std:c++latest") + SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS "/std:c++latest /permissive-") ENDIF() IF (MSVC AND "${CMAKE_SIZEOF_VOID_P}" EQUAL 4) # 32 bits -- cgit v1.3.1 From 5716676e083bde4a6f2ddb57a683df85b96c2d04 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 08:14:21 -0400 Subject: fixes for /permissive-: - no implicit conversions for enum classes - can't bind rvalue to non-const ref - string literals are const --- src/forcedloaddialogwidget.cpp | 8 ++++---- src/forcedloaddialogwidget.h | 4 ++-- src/instancemanager.cpp | 2 +- src/organizercore.cpp | 2 +- src/settingsdialog.cpp | 2 +- src/settingsdialog.h | 2 +- src/usvfsconnector.cpp | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/forcedloaddialogwidget.cpp b/src/forcedloaddialogwidget.cpp index 10069c35..b92838c3 100644 --- a/src/forcedloaddialogwidget.cpp +++ b/src/forcedloaddialogwidget.cpp @@ -54,12 +54,12 @@ void ForcedLoadDialogWidget::setForced(bool forced) ui->processEdit->setEnabled(!forced); } -void ForcedLoadDialogWidget::setLibraryPath(QString &path) +void ForcedLoadDialogWidget::setLibraryPath(const QString &path) { ui->libraryPathEdit->setText(path); } -void ForcedLoadDialogWidget::setProcess(QString &name) +void ForcedLoadDialogWidget::setProcess(const QString &name) { ui->processEdit->setText(name); } @@ -71,7 +71,7 @@ void ForcedLoadDialogWidget::on_enabledBox_toggled() void ForcedLoadDialogWidget::on_libraryPathBrowseButton_clicked() { - QDir gameDir(m_GamePlugin->gameDirectory()); + QDir gameDir(m_GamePlugin->gameDirectory()); QString startPath = gameDir.absolutePath(); QString result = QFileDialog::getOpenFileName(nullptr, "Select a library...", startPath, "Dynamic link library (*.dll)", nullptr, QFileDialog::ReadOnly); if (!result.isEmpty()) { @@ -100,7 +100,7 @@ void ForcedLoadDialogWidget::on_processBrowseButton_clicked() QString fileName = fileInfo.fileName(); if (fileInfo.exists()) { - ui->processEdit->setText(fileName); + ui->processEdit->setText(fileName); } else { qCritical("%ls does not exist", fileInfo.filePath().toStdWString().c_str()); } diff --git a/src/forcedloaddialogwidget.h b/src/forcedloaddialogwidget.h index eb7c3f4d..239653c8 100644 --- a/src/forcedloaddialogwidget.h +++ b/src/forcedloaddialogwidget.h @@ -23,8 +23,8 @@ public: void setEnabled(bool enabled); void setForced(bool forced); - void setLibraryPath(QString &path); - void setProcess(QString &name); + void setLibraryPath(const QString &path); + void setProcess(const QString &name); private slots: void on_enabledBox_toggled(); diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 1c65f635..5b57827a 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -227,7 +227,7 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const if (choice.type() == QVariant::String) { return choice.toString(); } else { - switch (choice.value()) { + switch (static_cast(choice.value())) { case Special::NewInstance: return queryInstanceName(instanceList); case Special::Portable: return QString(); case Special::Manage: { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 8212d248..9488f83a 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -111,7 +111,7 @@ static bool renameFile(const QString &oldName, const QString &newName, static std::wstring getProcessName(HANDLE process) { wchar_t buffer[MAX_PATH]; - wchar_t *fileName = L"unknown"; + const wchar_t *fileName = L"unknown"; if (process == nullptr) return fileName; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 74091469..7f53a9bb 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -88,7 +88,7 @@ QString SettingsDialog::getColoredButtonStyleSheet() const "}"); } -void SettingsDialog::setButtonColor(QPushButton *button, QColor &color) +void SettingsDialog::setButtonColor(QPushButton *button, const QColor &color) { button->setStyleSheet( QString("QPushButton {" diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 92a97c3f..afe988bd 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -53,7 +53,7 @@ public: */ QString getColoredButtonStyleSheet() const; - void setButtonColor(QPushButton *button, QColor &color); + void setButtonColor(QPushButton *button, const QColor &color); public slots: diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 5ad19fb0..b752667d 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -92,7 +92,7 @@ void LogWorker::exit() LogLevel logLevel(int level) { - switch (level) { + switch (static_cast(level)) { case LogLevel::Info: return LogLevel::Info; case LogLevel::Warning: @@ -106,7 +106,7 @@ LogLevel logLevel(int level) CrashDumpsType crashDumpsType(int type) { - switch (type) { + switch (static_cast(type)) { case CrashDumpsType::Mini: return CrashDumpsType::Mini; case CrashDumpsType::Data: -- cgit v1.3.1 From d0f087be8fa37fdfee94fad60260831bf64a1dda Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 26 May 2019 08:50:13 -0400 Subject: don't activate the window when hovering save files don't popup the widget if the window doesn't have focus --- src/mainwindow.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 810caa01..0ef9bc80 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1025,6 +1025,15 @@ void MainWindow::setBrowserGeometry(const QByteArray &geometry) void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) { + // don't display the widget if the main window doesn't have focus + // + // this goes against the standard behaviour for tooltips, which are displayed + // on hover regardless of focus, but this widget is so large and busy that + // it's probably better this way + if (!isActiveWindow()){ + return; + } + QString const &save = newItem->data(Qt::UserRole).toString(); if (m_CurrentSaveView == nullptr) { IPluginGame const *game = m_OrganizerCore.managedGame(); @@ -1056,8 +1065,6 @@ void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) m_CurrentSaveView->show(); m_CurrentSaveView->setProperty("displayItem", qVariantFromValue(static_cast(newItem))); - - ui->savegameList->activateWindow(); } -- cgit v1.3.1 From 2d52299743d056ae6da3c98ca9dc34abca3138bf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 27 May 2019 12:07:10 -0400 Subject: multiple selection for noconflict and overwritten lists set uniformRowHeights for all three lists for faster rendering, all items are text only all three lists use the same code for the context menu: - createConflictMenuActions() returns a struct with the QActions that are valid for the selection - showConflictMenu() plugs in the handlers and shows the menu --- src/modinfodialog.cpp | 190 +++++++++++++++++++++----------------------------- src/modinfodialog.h | 37 ++++++---- src/modinfodialog.ui | 21 ++++-- 3 files changed, 119 insertions(+), 129 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 19a03ea7..024679f3 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1636,13 +1636,12 @@ FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const return renamer.rename(oldName, newName); } -void ModInfoDialog::changeConflictFilesVisibility(bool hide) +void ModInfoDialog::changeConflictItemsVisibility( + const QList& items, bool hide) { bool changed = false; bool stop = false; - const auto items = ui->overwriteTree->selectedItems(); - qDebug().nospace() << (hide ? "hiding" : "unhiding") << " " << items.size() << " conflict files"; @@ -1707,63 +1706,21 @@ void ModInfoDialog::changeConflictFilesVisibility(bool hide) } } -void ModInfoDialog::hideConflictFiles() -{ - changeConflictFilesVisibility(true); -} - -void ModInfoDialog::unhideConflictFiles() -{ - changeConflictFilesVisibility(false); -} - -void ModInfoDialog::previewOverwriteDataFile() -{ - // the menu item is only shown for a single selection, but check just in case - const auto selection = ui->overwriteTree->selectedItems(); - if (!selection.empty()) { - previewDataFile(selection[0]); - } -} - -void ModInfoDialog::openOverwriteDataFile() +void ModInfoDialog::openConflictItems(const QList& items) { - // the menu item is only shown for a single selection, but check just in case - const auto selection = ui->overwriteTree->selectedItems(); - if (!selection.empty()) { - openDataFile(selection[0]); + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + openDataFile(item); } } -void ModInfoDialog::previewOverwrittenDataFile() +void ModInfoDialog::previewConflictItems(const QList& items) { - const auto selection = ui->overwrittenTree->selectedItems(); - if (!selection.empty()) { - previewDataFile(selection[0]); - } -} - -void ModInfoDialog::openOverwrittenDataFile() -{ - const auto selection = ui->overwrittenTree->selectedItems(); - if (!selection.empty()) { - openDataFile(selection[0]); - } -} - -void ModInfoDialog::previewNoConflictDataFile() -{ - const auto selection = ui->noConflictTree->selectedItems(); - if (!selection.empty()) { - previewDataFile(selection[0]); - } -} - -void ModInfoDialog::openNoConflictDataFile() -{ - const auto selection = ui->noConflictTree->selectedItems(); - if (!selection.empty()) { - openDataFile(selection[0]); + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + previewDataFile(item); } } @@ -1846,17 +1803,71 @@ bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) { - const auto selection = ui->overwriteTree->selectedItems(); - if (selection.empty()) { + showConflictMenu(pos, ui->overwriteTree); +} + +void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) +{ + showConflictMenu(pos, ui->overwrittenTree); +} + +void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) +{ + showConflictMenu(pos, ui->noConflictTree); +} + +void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) +{ + auto actions = createConflictMenuActions(tree->selectedItems()); + + QMenu menu; + + if (actions.open) { + connect(actions.open, &QAction::triggered, [&]{ + openConflictItems(tree->selectedItems()); + }); + + menu.addAction(actions.open); + } + + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewConflictItems(tree->selectedItems()); + }); + + menu.addAction(actions.preview); + } + + if (actions.hide) { + connect(actions.hide, &QAction::triggered, [&]{ + changeConflictItemsVisibility(tree->selectedItems(), false); + }); + + menu.addAction(actions.hide); + } + + if (actions.unhide) { + connect(actions.unhide, &QAction::triggered, [&]{ + changeConflictItemsVisibility(tree->selectedItems(), true); + }); + + menu.addAction(actions.unhide); + } + + if (menu.isEmpty()) { return; } - // for a single selection, hide/unhide is not shown for files from - // archives and whether the action is hide or unhide depends on the current - // state - // - // for multiple selection, both actions are shown unconditionally and - // handled in hideConflictFiles() and unhideConflictFiles() + menu.exec(tree->viewport()->mapToGlobal(pos)); +} + +ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( + const QList selection) +{ + if (selection.empty()) { + return {}; + } + bool enableHide = true; bool enableUnhide = true; bool enableOpen = true; @@ -1866,7 +1877,7 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po // this is a single selection const auto* item = selection[0]; if (!item) { - return; + return {}; } enableHide = canHideConflictItem(item); @@ -1903,66 +1914,27 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po } } - - QMenu menu; + ConflictActions actions; if (enableHide) { - menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); + actions.hide = new QAction(tr("Hide")); } // note that it is possible for hidden files to appear if they override other // hidden files from another mod if (enableUnhide) { - menu.addAction(tr("Unhide"), this, SLOT(unhideConflictFiles())); + actions.unhide = new QAction(tr("Unhide")); } if (enableOpen) { - menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); + actions.open = new QAction(tr("Open/Execute")); } if (enablePreview) { - menu.addAction(tr("Preview"), this, SLOT(previewOverwriteDataFile())); + actions.preview = new QAction(tr("Preview")); } - menu.exec(ui->overwriteTree->viewport()->mapToGlobal(pos)); -} - -void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) -{ - auto* item = ui->overwrittenTree->itemAt(pos.x(), pos.y()); - - if (item != nullptr) { - if (!item->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - - menu.addAction(tr("Open/Execute"), this, SLOT(openOverwrittenDataFile())); - - if (canPreviewConflictItem(item)) { - menu.addAction(tr("Preview"), this, SLOT(previewOverwrittenDataFile())); - } - - menu.exec(ui->overwrittenTree->viewport()->mapToGlobal(pos)); - } - } -} - -void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) -{ - auto* item = ui->noConflictTree->itemAt(pos.x(), pos.y()); - - if (item != nullptr) { - if (!item->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - - menu.addAction(tr("Open/Execute"), this, SLOT(openNoConflictDataFile())); - - if (canPreviewConflictItem(item)) { - menu.addAction(tr("Preview"), this, SLOT(previewNoConflictDataFile())); - } - - menu.exec(ui->noConflictTree->viewport()->mapToGlobal(pos)); - } - } + return actions; } void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index c0730afa..983da8d4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -329,18 +329,6 @@ private: int tabIndex(const QString &tabId); private slots: - - void hideConflictFiles(); - void unhideConflictFiles(); - void previewOverwriteDataFile(); - void openOverwriteDataFile(); - - void previewOverwrittenDataFile(); - void openOverwrittenDataFile(); - - void previewNoConflictDataFile(); - void openNoConflictDataFile(); - void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); void linkClicked(QString url); @@ -393,6 +381,18 @@ private slots: void createTweak(); private: + struct ConflictActions + { + QAction* hide; + QAction* unhide; + QAction* open; + QAction* preview; + + ConflictActions() + : hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr) + { + } + }; Ui::ModInfoDialog *ui; @@ -442,12 +442,21 @@ private: void openDataFile(const QTreeWidgetItem* item); void previewDataFile(const QTreeWidgetItem* item); - void changeConflictFilesVisibility(bool hide); - void changeFiletreeVisibility(bool hide); + void changeFiletreeVisibility(bool visible); + + void openConflictItems(const QList& items); + void previewConflictItems(const QList& items); + void changeConflictItemsVisibility( + const QList& items, bool visible); bool canPreviewFile(bool isArchive, const QString& filename) const; bool canHideFile(bool isArchive, const QString& filename) const; bool canUnhideFile(bool isArchive, const QString& filename) const; + + void showConflictMenu(const QPoint &pos, QTreeWidget* tree); + + ConflictActions createConflictMenuActions( + const QList selection); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 1187de87..840c81ee 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -453,24 +453,21 @@ text-align: left; Qt::ElideLeft + + true + true true - - false - 2 365 - - false - 200 @@ -526,9 +523,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true @@ -596,9 +599,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true -- cgit v1.3.1 From 3732d24e0d228eb1e00c32a9fdc8cb371f9be280 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 27 May 2019 20:03:40 -0400 Subject: findFile() seems to return null in some cases and crash on the line below this is a quick fix for this particular crash, there's probably something deeper to fix --- src/pluginlist.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index ec9bdac3..2edb92f5 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -139,7 +139,7 @@ void PluginList::highlightPlugins(const QItemSelectionModel *selection, const MO if (plugins.size() > 0) { for (auto plugin : plugins) { MOShared::FileEntry::Ptr file = directoryEntry.findFile(plugin.toStdWString()); - if (file->getOrigin() != origin.getID()) { + if (file && file->getOrigin() != origin.getID()) { const std::vector>> alternatives = file->getAlternatives(); if (std::find_if(alternatives.begin(), alternatives.end(), [&](const std::pair>& element) { return element.first == origin.getID(); }) == alternatives.end()) continue; -- cgit v1.3.1 From adb5ace7997c4cf8ed5e0bee726478c8e1593524 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 10:14:15 -0400 Subject: added selectedOrigin to previewFileWithAlternatives() this is so that an origin other than the primary can be shown first and is used from previewDataFile() in ModInfoDialog since showing a preview for a conflicting file could initially show the file from the wrong mod removed unused, uninitialized and dangerous ModInfoDialog::m_OriginID --- src/modinfodialog.cpp | 4 +++- src/modinfodialog.h | 1 - src/organizercore.cpp | 41 +++++++++++++++++++++++++++++++++++++---- src/organizercore.h | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 024679f3..fd8093d1 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1740,8 +1740,10 @@ void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) return; } + const int originId = (m_Origin ? m_Origin->getID() : -1); + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore->previewFileWithAlternatives(this, fileName); + m_OrganizerCore->previewFileWithAlternatives(this, fileName, originId); } bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) const diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 983da8d4..5f7d831b 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -397,7 +397,6 @@ private: Ui::ModInfoDialog *ui; ModInfo::Ptr m_ModInfo; - int m_OriginID; QSignalMapper m_ThumbnailMapper; QString m_RootPath; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 19b53b8d..00292e32 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1319,7 +1319,7 @@ bool OrganizerCore::executeFileVirtualized( } bool OrganizerCore::previewFileWithAlternatives( - QWidget* parent, QString fileName) + QWidget* parent, QString fileName, int selectedOrigin) { // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory @@ -1370,9 +1370,42 @@ bool OrganizerCore::previewFileWithAlternatives( } }; - addFunc(file->getOrigin()); - for (auto alt : file->getAlternatives()) { - addFunc(alt.first); + if (selectedOrigin == -1) { + // don't bother with the vector of origins, just add them as they come + addFunc(file->getOrigin()); + for (auto alt : file->getAlternatives()) { + addFunc(alt.first); + } + } else { + std::vector origins; + + // start with the primary origin + origins.push_back(file->getOrigin()); + + // add other origins, push to front if it's the selected one + for (auto alt : file->getAlternatives()) { + if (alt.first == selectedOrigin) { + origins.insert(origins.begin(), alt.first); + } else { + origins.push_back(alt.first); + } + } + + // can't be empty; either the primary origin was the selected one, or it + // was one of the alternatives, which got inserted in front + + if (origins[0] != selectedOrigin) { + // sanity check, this shouldn't happen unless the caller passed an + // incorrect id + + qWarning().nospace() + << "selected preview origin " << selectedOrigin << " not found in " + << "list of alternatives"; + } + + for (int id : origins) { + addFunc(id); + } } if (preview.numVariants() > 0) { diff --git a/src/organizercore.h b/src/organizercore.h index e24227b7..8ed34e24 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -152,7 +152,7 @@ public: QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); bool executeFileVirtualized(QWidget* parent, const QFileInfo& targetInfo); - bool previewFileWithAlternatives(QWidget* parent, QString filename); + bool previewFileWithAlternatives(QWidget* parent, QString filename, int selectedOrigin=-1); bool previewFile(QWidget* parent, const QString& originName, const QString& path); void spawnBinary(const QFileInfo &binary, const QString &arguments = "", -- cgit v1.3.1 From 1ec2260db594a728ce811c78c83d9bdf460dafc5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 10:26:42 -0400 Subject: reversed 'hide' parameter to 'visible' for changeConflictItemsVisibility() when I pass true to it, I expect it to be visible, not "yes, this should be hidden" I had actually started this earlier and ended up in a broken, in-between state, which should be fixed now --- src/modinfodialog.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index fd8093d1..4cb23081 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1637,16 +1637,18 @@ FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const } void ModInfoDialog::changeConflictItemsVisibility( - const QList& items, bool hide) + const QList& items, bool visible) { bool changed = false; bool stop = false; qDebug().nospace() - << (hide ? "hiding" : "unhiding") << " " + << (visible ? "unhiding" : "hiding") << " " << items.size() << " conflict files"; - QFlags flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE); + QFlags flags = + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + if (items.size() > 1) { flags |= FileRenamer::MULTIPLE; } @@ -1660,19 +1662,19 @@ void ModInfoDialog::changeConflictItemsVisibility( auto result = FileRenamer::RESULT_CANCEL; - if (hide) { - if (!canHideConflictItem(item)) { - qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; + if (visible) { + if (!canUnhideConflictItem(item)) { + qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; continue; } - result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); + result = unhideFile(renamer, item->data(0, Qt::UserRole).toString()); } else { - if (!canUnhideConflictItem(item)) { - qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; + if (!canHideConflictItem(item)) { + qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; continue; } - result = unhideFile(renamer, item->data(0, Qt::UserRole).toString()); + result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); } switch (result) { @@ -1695,7 +1697,7 @@ void ModInfoDialog::changeConflictItemsVisibility( } } - qDebug().nospace() << (hide ? "hiding" : "unhiding") << " conflict files done"; + qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; if (changed) { qDebug().nospace() << "triggering refresh"; -- cgit v1.3.1 From d3c244c83427174f80d59d3ebbafd2c4263f9b4f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 10:30:26 -0400 Subject: also reversed 'hide' parameter to 'visible' for changeFiletreeVisibility() --- src/modinfodialog.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4cb23081..06c065fe 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1326,25 +1326,27 @@ void ModInfoDialog::renameTriggered() void ModInfoDialog::hideTriggered() { - changeFiletreeVisibility(true); + changeFiletreeVisibility(false); } void ModInfoDialog::unhideTriggered() { - changeFiletreeVisibility(false); + changeFiletreeVisibility(true); } -void ModInfoDialog::changeFiletreeVisibility(bool hide) +void ModInfoDialog::changeFiletreeVisibility(bool visible) { bool changed = false; bool stop = false; qDebug().nospace() - << (hide ? "hiding" : "unhiding") << " " + << (visible ? "unhiding" : "hiding") << " " << m_FileSelection.size() << " filetree files"; - QFlags flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE); + QFlags flags = + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + if (m_FileSelection.size() > 1) { flags |= FileRenamer::MULTIPLE; } @@ -1359,19 +1361,18 @@ void ModInfoDialog::changeFiletreeVisibility(bool hide) const QString path = m_FileSystemModel->filePath(index); auto result = FileRenamer::RESULT_CANCEL; - if (hide) { - if (!canHideFile(false, path)) { - qDebug().nospace() << "cannot hide " << path << ", skipping"; - continue; - } - result = hideFile(renamer, path); - - } else { + if (visible) { if (!canUnhideFile(false, path)) { qDebug().nospace() << "cannot unhide " << path << ", skipping"; continue; } result = unhideFile(renamer, path); + } else { + if (!canHideFile(false, path)) { + qDebug().nospace() << "cannot hide " << path << ", skipping"; + continue; + } + result = hideFile(renamer, path); } switch (result) { @@ -1394,7 +1395,7 @@ void ModInfoDialog::changeFiletreeVisibility(bool hide) } } - qDebug().nospace() << (hide ? "hiding" : "unhiding") << " filetree files done"; + qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; if (changed) { qDebug().nospace() << "triggering refresh"; -- cgit v1.3.1 From ee92fa9fc89ab047a63716c48e13d3d0a76af4fb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 10:53:55 -0400 Subject: reverted change that showed the conflicted file first in the preview dialog I'm not sure how I got to the conclusion that this was a better idea: preview was always an option for those files, and it has always shown the winning file, so I changed some long-standing behaviour for no reason. I reverted that change, although I'm leaving the functionality in previewFileWithAlternatives() to specify the selected origin because it works and could be useful in the future --- src/modinfodialog.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 06c065fe..b6a21705 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1743,10 +1743,8 @@ void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) return; } - const int originId = (m_Origin ? m_Origin->getID() : -1); - QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore->previewFileWithAlternatives(this, fileName, originId); + m_OrganizerCore->previewFileWithAlternatives(this, fileName); } bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) const -- cgit v1.3.1 From f9e9d79a7d433b79a0beb5d11272a606f066affe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 12:43:57 -0400 Subject: moved all widgets in the conflict tab into a parent widget instead of directly being in the tab, allows for future additions to the tab --- src/modinfodialog.ui | 453 ++++++++++++++++++++++++++------------------------- 1 file changed, 234 insertions(+), 219 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 840c81ee..1fe431dd 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -400,243 +400,258 @@ Most mods do not have optional esps, so chances are good you are looking at an e Conflicts - + 0 - - - - - - - - 0 - 0 - - - - border: none; -text-align: left; - - - The following conflicted files are provided by this mod - - - - - - - QFrame::Sunken - - - 1 - - - QLCDNumber::Flat - - - - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - true - - - 2 + + + + 0 + 100 + - - 365 - - - 200 - - - - File + + + 0 - - - - Overwritten Mods + + 0 - - - - - - - - - - - - 0 - 0 - - - - border: none; + + 0 + + + 0 + + + + + + + + + + 0 + 0 + + + + border: none; text-align: left; - + + + The following conflicted files are provided by this mod + + + + + + + QFrame::Sunken + + + 1 + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + + Qt::ElideLeft + + + true + + + true + + + false + + + 2 + + + 365 + + + false + + + 200 + + - The following conflicted files are provided by other mods - - - - - - - QFrame::Sunken + File - - QLCDNumber::Flat - - - - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - true - - - 2 - - - 365 - - - 200 - - - - File - - - - - Providing Mod - - - - - - - - - - - - - 0 - 0 - + + + + Overwritten Mods - - border: none; + + + + + + + + + + + + 0 + 0 + + + + border: none; text-align: left; - + + + The following conflicted files are provided by other mods + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + Qt::ElideLeft + + + true + + + true + + + 2 + + + 365 + + + 200 + + - The following files have no conflicts + File - - - - - - QFrame::Sunken + + + + Providing Mod - - QLCDNumber::Flat + + + + + + + + + + + + 0 + 0 + + + + border: none; +text-align: left; + + + The following files have no conflicts + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + Qt::ElideLeft + + + true + + + true + + + 1 + + + + File - - - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - true - - - 1 - - - - File - - + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + - - - - Qt::Vertical - - - - 20 - 0 - - - - -- cgit v1.3.1 From 2687fa8231cf4b4d9d9bc5bd90ca7c287f97ead2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 14:18:32 -0400 Subject: added new tabs in the conflict tab --- src/modinfodialog.ui | 568 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 351 insertions(+), 217 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 1fe431dd..190f5b56 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -401,255 +401,384 @@ Most mods do not have optional esps, so chances are good you are looking at an e Conflicts - - 0 - - - - - 0 - 100 - + + + 1 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - + + + General + + + + + + + 0 + 100 + + + + + 0 + + + 0 + + + 0 + + + 0 + - - - - 0 - 0 - - - - border: none; + + + + + + + + 0 + 0 + + + + border: none; text-align: left; + + + The following conflicted files are provided by this mod + + + + + + + QFrame::Sunken + + + 1 + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu - - The following conflicted files are provided by this mod + + QAbstractItemView::ExtendedSelection + + Qt::ElideLeft + + + true + + + true + + + false + + + 2 + + + 365 + + + false + + + 200 + + + + File + + + + + Overwritten Mods + + - - - QFrame::Sunken + + + + + + + + 0 + 0 + + + + border: none; +text-align: left; + + + The following conflicted files are provided by other mods + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu - - 1 + + Qt::ElideLeft + + + true + + + true - - QLCDNumber::Flat + + 2 + + 365 + + + 200 + + + + File + + + + + Providing Mod + + - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - false - - - 2 - - - 365 - - - false - - - 200 - - - - File - - - - - Overwritten Mods - - - - - - - - - - - - 0 - 0 - - - - border: none; + + + + + + + + 0 + 0 + + + + border: none; text-align: left; + + + The following files have no conflicts + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + Qt::ElideLeft - - The following conflicted files are provided by other mods + + true + + + true + + + 1 + + + File + + - - - QFrame::Sunken + + + Qt::Vertical - - QLCDNumber::Flat + + + 20 + 0 + - + - - - - - - - Qt::CustomContextMenu - - - Qt::ElideLeft - - - true - - - true - - - 2 - - - 365 - - - 200 - - - - File - - - - - Providing Mod - - - - - - - - + + + + + + + Advanced + + + + + + + 0 + + + 0 + + + 0 + + + 0 + - - - - 0 - 0 - - - - border: none; -text-align: left; - - - The following files have no conflicts - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 3 + + + 243 + + + + Overwrites + + + + + File + + + + + Overwritten by + + + + + - - - QFrame::Sunken - - - QLCDNumber::Flat - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Show files that have no conflicts + + + + + + + + 20 + + + 0 + + + 0 + + + 0 + + + + + Filter + + + + + + + - - - - - - - Qt::CustomContextMenu - - - Qt::ElideLeft - - - true - - - true - - - 1 - - - - File - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - + + + + @@ -1052,6 +1181,11 @@ p, li { white-space: pre-wrap; } QLineEdit
    modidlineedit.h
    + + MOBase::LineEditClear + QLineEdit +
    lineeditclear.h
    +
    -- cgit v1.3.1 From 157168e73a7628522f81ddca77716024638b412f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 14:31:49 -0400 Subject: split refreshLists() in two --- src/modinfodialog.cpp | 28 ++++++++++++++++++---------- src/modinfodialog.h | 3 +++ 2 files changed, 21 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b6a21705..d1ce7f65 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -596,6 +596,12 @@ QByteArray ModInfoDialog::saveConflictExpandersState() const } void ModInfoDialog::refreshLists() +{ + refreshConflictLists(); + refreshFiles(); +} + +void ModInfoDialog::refreshConflictLists() { int numNonConflicting = 0; int numOverwrite = 0; @@ -616,7 +622,7 @@ void ModInfoDialog::refreshLists() if (!alternatives.empty()) { std::wostringstream altString; for (std::vector>>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { + altIter != alternatives.end(); ++altIter) { if (altIter != alternatives.begin()) { altString << ", "; } @@ -669,6 +675,13 @@ void ModInfoDialog::refreshLists() } } + ui->overwriteCount->display(numOverwrite); + ui->overwrittenCount->display(numOverwritten); + ui->noConflictCount->display(numNonConflicting); +} + +void ModInfoDialog::refreshFiles() +{ if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -677,7 +690,7 @@ void ModInfoDialog::refreshLists() if (fileName.endsWith(".txt", Qt::CaseInsensitive)) { ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1)); } else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && - !fileName.endsWith("meta.ini")) { + !fileName.endsWith("meta.ini")) { QString namePart = fileName.mid(m_RootPath.length() + 1); if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList); @@ -689,8 +702,8 @@ void ModInfoDialog::refreshLists() ui->iniFileList->addItem(namePart); } } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || - fileName.endsWith(".esm", Qt::CaseInsensitive) || - fileName.endsWith(".esl", Qt::CaseInsensitive)) { + fileName.endsWith(".esm", Qt::CaseInsensitive) || + fileName.endsWith(".esl", Qt::CaseInsensitive)) { QString relativePath = fileName.mid(m_RootPath.length() + 1); if (relativePath.contains('/')) { QFileInfo fileInfo(fileName); @@ -701,7 +714,7 @@ void ModInfoDialog::refreshLists() ui->activeESPList->addItem(relativePath); } } else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) || - (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { + (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { QImage image = QImage(fileName); if (!image.isNull()) { if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { @@ -719,13 +732,8 @@ void ModInfoDialog::refreshLists() } } } - - ui->overwriteCount->display(numOverwrite); - ui->overwrittenCount->display(numOverwritten); - ui->noConflictCount->display(numNonConflicting); } - void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) { for (int i = 0; i < static_cast(factory.numCategories()); ++i) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 5f7d831b..95b9db9c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -429,6 +429,9 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; + void refreshConflictLists(); + void refreshFiles(); + void restoreTabState(const QByteArray &state); void restoreConflictExpandersState(const QByteArray &state); -- cgit v1.3.1 From 3666995ce1c02bcb3b7937dfc112c84e81c4fead Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 14:39:22 -0400 Subject: changed iterator loops to ranged for added some autos here and there added some whitespace --- src/modinfodialog.cpp | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d1ce7f65..f330703a 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -613,62 +613,76 @@ void ModInfoDialog::refreshConflictLists() if (m_Origin != nullptr) { std::vector files = m_Origin->getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - QString relativeName = QDir::fromNativeSeparators(ToQString((*iter)->getRelativePath())); + + for (const auto& file : m_Origin->getFiles()) { + QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); QString fileName = relativeName.mid(0).prepend(m_RootPath); - bool archive; - if ((*iter)->getOrigin(archive) == m_Origin->getID()) { - std::vector>> alternatives = (*iter)->getAlternatives(); + bool archive = false; + + if (file->getOrigin(archive) == m_Origin->getID()) { + const auto& alternatives = file->getAlternatives(); + if (!alternatives.empty()) { - std::wostringstream altString; - for (std::vector>>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << ", "; + QString altString; + + for (const auto& alt : alternatives) { + if (!altString.isEmpty()) { + altString += ", "; } - altString << m_Directory->getOriginByID(altIter->first).getName(); + + altString += ToQString(m_Directory->getOriginByID(alt.first).getName()); } + QStringList fields(relativeName); - fields.append(ToQString(altString.str())); + fields.append(altString); QTreeWidgetItem *item = new QTreeWidgetItem(fields); item->setData(0, Qt::UserRole, fileName); item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.back().first).getName())); item->setData(1, Qt::UserRole + 1, alternatives.back().first); item->setData(1, Qt::UserRole + 2, archive); + if (archive) { QFont font = item->font(0); font.setItalic(true); item->setFont(0, font); item->setFont(1, font); } + ui->overwriteTree->addTopLevelItem(item); ++numOverwrite; - } else {// otherwise, put the file in the nonconflict tree + } else { + // otherwise, put the file in the nonconflict tree QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); item->setData(0, Qt::UserRole, fileName); + if (archive) { QFont font = item->font(0); font.setItalic(true); item->setFont(0, font); } + ui->noConflictTree->addTopLevelItem(item); ++numNonConflicting; } } else { - FilesOrigin &realOrigin = m_Directory->getOriginByID((*iter)->getOrigin(archive)); + const FilesOrigin &realOrigin = m_Directory->getOriginByID(file->getOrigin(archive)); + QStringList fields(relativeName); fields.append(ToQString(realOrigin.getName())); + QTreeWidgetItem *item = new QTreeWidgetItem(fields); item->setData(0, Qt::UserRole, fileName); item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); item->setData(1, Qt::UserRole + 2, archive); + if (archive) { QFont font = item->font(0); font.setItalic(true); item->setFont(0, font); item->setFont(1, font); } + ui->overwrittenTree->addTopLevelItem(item); ++numOverwritten; } -- cgit v1.3.1 From d168b5db084457a106c5bb155be6eca5013f77c0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 14:55:01 -0400 Subject: added AlternativesVector typedef to FileEntry split item creation from refreshConflictLists() --- src/modinfodialog.cpp | 130 ++++++++++++++++++++++++++------------------ src/modinfodialog.h | 11 ++++ src/shared/directoryentry.h | 6 +- 3 files changed, 92 insertions(+), 55 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f330703a..f5b6abc8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -623,67 +623,21 @@ void ModInfoDialog::refreshConflictLists() const auto& alternatives = file->getAlternatives(); if (!alternatives.empty()) { - QString altString; + ui->overwriteTree->addTopLevelItem(createOverwriteItem( + archive, fileName, relativeName, alternatives)); - for (const auto& alt : alternatives) { - if (!altString.isEmpty()) { - altString += ", "; - } - - altString += ToQString(m_Directory->getOriginByID(alt.first).getName()); - } - - QStringList fields(relativeName); - fields.append(altString); - - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.back().first).getName())); - item->setData(1, Qt::UserRole + 1, alternatives.back().first); - item->setData(1, Qt::UserRole + 2, archive); - - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - item->setFont(1, font); - } - - ui->overwriteTree->addTopLevelItem(item); ++numOverwrite; } else { // otherwise, put the file in the nonconflict tree - QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); - item->setData(0, Qt::UserRole, fileName); + ui->noConflictTree->addTopLevelItem(createNoConflictItem( + archive, fileName, relativeName)); - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - } - - ui->noConflictTree->addTopLevelItem(item); ++numNonConflicting; } } else { - const FilesOrigin &realOrigin = m_Directory->getOriginByID(file->getOrigin(archive)); - - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); + ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( + file, archive, fileName, relativeName)); - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); - item->setData(1, Qt::UserRole + 2, archive); - - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - item->setFont(1, font); - } - - ui->overwrittenTree->addTopLevelItem(item); ++numOverwritten; } } @@ -694,6 +648,78 @@ void ModInfoDialog::refreshConflictLists() ui->noConflictCount->display(numNonConflicting); } +QTreeWidgetItem* ModInfoDialog::createOverwriteItem( + bool archive, const QString& fileName, const QString& relativeName, + const FileEntry::AlternativesVector& alternatives) +{ + QString altString; + + for (const auto& alt : alternatives) { + if (!altString.isEmpty()) { + altString += ", "; + } + + altString += ToQString(m_Directory->getOriginByID(alt.first).getName()); + } + + QStringList fields(relativeName); + fields.append(altString); + + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + item->setData(0, Qt::UserRole, fileName); + item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.back().first).getName())); + item->setData(1, Qt::UserRole + 1, alternatives.back().first); + item->setData(1, Qt::UserRole + 2, archive); + + if (archive) { + QFont font = item->font(0); + font.setItalic(true); + item->setFont(0, font); + item->setFont(1, font); + } + + return item; +} + +QTreeWidgetItem* ModInfoDialog::createNoConflictItem( + bool archive, const QString& fileName, const QString& relativeName) +{ + QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); + item->setData(0, Qt::UserRole, fileName); + + if (archive) { + QFont font = item->font(0); + font.setItalic(true); + item->setFont(0, font); + } + + return item; +} + +QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( + const MOShared::FileEntry::Ptr& file, + bool archive, const QString& fileName, const QString& relativeName) +{ + const FilesOrigin &realOrigin = m_Directory->getOriginByID(file->getOrigin(archive)); + + QStringList fields(relativeName); + fields.append(ToQString(realOrigin.getName())); + + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + item->setData(0, Qt::UserRole, fileName); + item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); + item->setData(1, Qt::UserRole + 2, archive); + + if (archive) { + QFont font = item->font(0); + font.setItalic(true); + item->setFont(0, font); + item->setFont(1, font); + } + + return item; +} + void ModInfoDialog::refreshFiles() { if (m_RootPath.length() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 95b9db9c..3c193cd3 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -432,6 +432,17 @@ private: void refreshConflictLists(); void refreshFiles(); + QTreeWidgetItem* createOverwriteItem( + bool archive, const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives); + + QTreeWidgetItem* createNoConflictItem( + bool archive, const QString& fileName, const QString& relativeName); + + QTreeWidgetItem* createOverwrittenItem( + const MOShared::FileEntry::Ptr& file, + bool archive, const QString& fileName, const QString& relativeName); + void restoreTabState(const QByteArray &state); void restoreConflictExpandersState(const QByteArray &state); diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 12cef11d..e7af1ae7 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -49,8 +49,8 @@ class FileEntry { public: typedef unsigned int Index; - typedef boost::shared_ptr Ptr; + typedef std::vector>> AlternativesVector; public: @@ -72,7 +72,7 @@ public: // gets the list of alternative origins (origins with lower priority than the primary one). // if sortOrigins has been called, it is sorted by priority (ascending) - const std::vector>> &getAlternatives() const { return m_Alternatives; } + const AlternativesVector &getAlternatives() const { return m_Alternatives; } const std::wstring &getName() const { return m_Name; } int getOrigin() const { return m_Origin; } @@ -98,7 +98,7 @@ private: std::wstring m_Name; int m_Origin = -1; std::pair m_Archive; - std::vector>> m_Alternatives; + AlternativesVector m_Alternatives; DirectoryEntry *m_Parent; mutable FILETIME m_FileTime; -- cgit v1.3.1 From ab385347afc3317049d85a5ee4b011592d8b3dce Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 15:39:09 -0400 Subject: allow for refreshing individual conflict tabs advanced conflict list now has items --- src/modinfodialog.cpp | 132 +++++++++++++++++++++++++++++++++++++++----------- src/modinfodialog.h | 11 +++-- src/modinfodialog.ui | 3 ++ 3 files changed, 116 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f5b6abc8..2d2cc559 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -437,6 +437,10 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_overwriteExpander.set(ui->overwriteExpander, ui->overwriteTree, true); m_overwrittenExpander.set(ui->overwrittenExpander, ui->overwrittenTree, true); m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); + + connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { + refreshConflictLists(false, true); + }); } @@ -597,55 +601,76 @@ QByteArray ModInfoDialog::saveConflictExpandersState() const void ModInfoDialog::refreshLists() { - refreshConflictLists(); + refreshConflictLists(true, true); refreshFiles(); } -void ModInfoDialog::refreshConflictLists() +void ModInfoDialog::refreshConflictLists( + bool refreshGeneral, bool refreshAdvanced) { int numNonConflicting = 0; int numOverwrite = 0; int numOverwritten = 0; - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - ui->noConflictTree->clear(); + if (refreshGeneral) { + ui->overwriteTree->clear(); + ui->overwrittenTree->clear(); + ui->noConflictTree->clear(); + } + + if (refreshAdvanced) { + ui->conflictsAdvancedList->clear(); + } if (m_Origin != nullptr) { std::vector files = m_Origin->getFiles(); for (const auto& file : m_Origin->getFiles()) { - QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - QString fileName = relativeName.mid(0).prepend(m_RootPath); + const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + const QString fileName = relativeName.mid(0).prepend(m_RootPath); + bool archive = false; + const int fileOrigin = file->getOrigin(archive); + const auto& alternatives = file->getAlternatives(); - if (file->getOrigin(archive) == m_Origin->getID()) { - const auto& alternatives = file->getAlternatives(); + if (refreshGeneral) { + if (fileOrigin == m_Origin->getID()) { + if (!alternatives.empty()) { + ui->overwriteTree->addTopLevelItem(createOverwriteItem( + archive, fileName, relativeName, alternatives)); - if (!alternatives.empty()) { - ui->overwriteTree->addTopLevelItem(createOverwriteItem( - archive, fileName, relativeName, alternatives)); + ++numOverwrite; + } else { + // otherwise, put the file in the noconflict tree + ui->noConflictTree->addTopLevelItem(createNoConflictItem( + archive, fileName, relativeName)); - ++numOverwrite; + ++numNonConflicting; + } } else { - // otherwise, put the file in the nonconflict tree - ui->noConflictTree->addTopLevelItem(createNoConflictItem( - archive, fileName, relativeName)); + ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( + fileOrigin, archive, fileName, relativeName)); - ++numNonConflicting; + ++numOverwritten; } - } else { - ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( - file, archive, fileName, relativeName)); + } + + if (refreshAdvanced) { + auto* advancedItem = createAdvancedConflictItem( + fileOrigin, archive, fileName, relativeName, alternatives); - ++numOverwritten; + if (advancedItem) { + ui->conflictsAdvancedList->addTopLevelItem(advancedItem); + } } } } - ui->overwriteCount->display(numOverwrite); - ui->overwrittenCount->display(numOverwritten); - ui->noConflictCount->display(numNonConflicting); + if (refreshGeneral) { + ui->overwriteCount->display(numOverwrite); + ui->overwrittenCount->display(numOverwritten); + ui->noConflictCount->display(numNonConflicting); + } } QTreeWidgetItem* ModInfoDialog::createOverwriteItem( @@ -697,10 +722,10 @@ QTreeWidgetItem* ModInfoDialog::createNoConflictItem( } QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( - const MOShared::FileEntry::Ptr& file, - bool archive, const QString& fileName, const QString& relativeName) + int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName) { - const FilesOrigin &realOrigin = m_Directory->getOriginByID(file->getOrigin(archive)); + const FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); QStringList fields(relativeName); fields.append(ToQString(realOrigin.getName())); @@ -720,6 +745,59 @@ QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( return item; } +QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( + int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives) +{ + QString before, after; + + if (!alternatives.empty()) { + int beforePrio = 0; + int afterPrio = std::numeric_limits::max(); + + for (const auto& alt : alternatives) + { + auto altOrigin = m_Directory->getOriginByID(alt.first); + + if (altOrigin.getPriority() > beforePrio) { + if (altOrigin.getPriority() < m_Origin->getPriority()) { + before = ToQString(altOrigin.getName()); + beforePrio = altOrigin.getPriority(); + } + } + + if (altOrigin.getPriority() < afterPrio) { + if (altOrigin.getPriority() > m_Origin->getPriority()) { + after = ToQString(altOrigin.getName()); + afterPrio = altOrigin.getPriority(); + } + } + } + + if (after.isEmpty()) { + FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); + + if (realOrigin.getID() != m_Origin->getID()) { + after = ToQString(realOrigin.getName()); + } + } + } + + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { + if (before.isEmpty() && after.isEmpty()) { + return nullptr; + } + } + + QTreeWidgetItem* item = new QTreeWidgetItem; + item->setText(0, before); + item->setText(1, relativeName); + item->setText(2, after); + + return item; +} + void ModInfoDialog::refreshFiles() { if (m_RootPath.length() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 3c193cd3..69b0069e 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -429,7 +429,7 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; - void refreshConflictLists(); + void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); void refreshFiles(); QTreeWidgetItem* createOverwriteItem( @@ -440,8 +440,13 @@ private: bool archive, const QString& fileName, const QString& relativeName); QTreeWidgetItem* createOverwrittenItem( - const MOShared::FileEntry::Ptr& file, - bool archive, const QString& fileName, const QString& relativeName); + int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName); + + QTreeWidgetItem* createAdvancedConflictItem( + int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives); void restoreTabState(const QByteArray &state); void restoreConflictExpandersState(const QByteArray &state); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 190f5b56..b31f49d3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -744,6 +744,9 @@ text-align: left; Show files that have no conflicts + + true + -- cgit v1.3.1 From 260462fe6dd37899744b95f777cdff3c738fad2c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 16:10:10 -0400 Subject: added show all/nearest mods radio buttons --- src/modinfodialog.cpp | 48 ++++++++++++++++++++++++++++++++++++++---------- src/modinfodialog.ui | 17 +++++++++++++++++ 2 files changed, 55 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2d2cc559..e35aa0e1 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -441,6 +441,14 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { refreshConflictLists(false, true); }); + + connect(ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&] { + refreshConflictLists(false, true); + }); + + connect(ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&] { + refreshConflictLists(false, true); + }); } @@ -760,26 +768,46 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( { auto altOrigin = m_Directory->getOriginByID(alt.first); - if (altOrigin.getPriority() > beforePrio) { + if (ui->conflictsAdvancedShowAll->isChecked()) { if (altOrigin.getPriority() < m_Origin->getPriority()) { - before = ToQString(altOrigin.getName()); - beforePrio = altOrigin.getPriority(); + if (!before.isEmpty()) { + before += ", "; + } + + before += ToQString(altOrigin.getName()); + } else if (altOrigin.getPriority() > m_Origin->getPriority()) { + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(altOrigin.getName()); + } + } else { + if (altOrigin.getPriority() > beforePrio) { + if (altOrigin.getPriority() < m_Origin->getPriority()) { + before = ToQString(altOrigin.getName()); + beforePrio = altOrigin.getPriority(); + } } - } - if (altOrigin.getPriority() < afterPrio) { - if (altOrigin.getPriority() > m_Origin->getPriority()) { - after = ToQString(altOrigin.getName()); - afterPrio = altOrigin.getPriority(); + if (altOrigin.getPriority() < afterPrio) { + if (altOrigin.getPriority() > m_Origin->getPriority()) { + after = ToQString(altOrigin.getName()); + afterPrio = altOrigin.getPriority(); + } } } } - if (after.isEmpty()) { + if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); if (realOrigin.getID() != m_Origin->getID()) { - after = ToQString(realOrigin.getName()); + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(realOrigin.getName()); } } } diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index b31f49d3..e391da46 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -749,6 +749,23 @@ text-align: left; + + + + Show all conflicting mods + + + true + + + + + + + Show nearest conflicting mod + + + -- cgit v1.3.1 From a202170aab35bc486114896e011239b1ef560aad Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 16:22:57 -0400 Subject: comments, switched some of the ifs for clarity --- src/modinfodialog.cpp | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index e35aa0e1..c936f150 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -766,16 +766,21 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( for (const auto& alt : alternatives) { - auto altOrigin = m_Directory->getOriginByID(alt.first); + const auto altOrigin = m_Directory->getOriginByID(alt.first); if (ui->conflictsAdvancedShowAll->isChecked()) { + // fills 'before' and 'after' with all the alternatives that come + // before and after this mod in terms of priority + if (altOrigin.getPriority() < m_Origin->getPriority()) { + // add all the mods having a lower priority than this one if (!before.isEmpty()) { before += ", "; } before += ToQString(altOrigin.getName()); } else if (altOrigin.getPriority() > m_Origin->getPriority()) { + // add all the mods having a higher priority than this one if (!after.isEmpty()) { after += ", "; } @@ -783,15 +788,26 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( after += ToQString(altOrigin.getName()); } } else { - if (altOrigin.getPriority() > beforePrio) { - if (altOrigin.getPriority() < m_Origin->getPriority()) { + // keep track of the nearest mods that come before and after this one + // in terms of priority + + if (altOrigin.getPriority() < m_Origin->getPriority()) { + // the alternative has a lower priority than this mod + + if (altOrigin.getPriority() > beforePrio) { + // the alternative has a higher priority and therefore is closer + // to this mod, use it before = ToQString(altOrigin.getName()); beforePrio = altOrigin.getPriority(); } } - if (altOrigin.getPriority() < afterPrio) { - if (altOrigin.getPriority() > m_Origin->getPriority()) { + if (altOrigin.getPriority() > m_Origin->getPriority()) { + // the alternative has a higher priority than this mod + + if (altOrigin.getPriority() < afterPrio) { + // the alternative has a lower priority and there is closer + // to this mod, use it after = ToQString(altOrigin.getName()); afterPrio = altOrigin.getPriority(); } @@ -799,9 +815,19 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } } + // the primary origin is never in the list of alternatives, so it has to + // be handled separately + // + // if 'after' is not empty, it means at least one alternative with a higher + // priority than this mod was found; if the user only wants to see the + // nearest mods, it's not worth checking for the primary origin because it + // will always have a higher priority than the alternatives (or it wouldn't + // be the primary) if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); + // if no mods overwrite this file, the primary origin is the same as this + // mod, so ignore that if (realOrigin.getID() != m_Origin->getID()) { if (!after.isEmpty()) { after += ", "; @@ -813,6 +839,8 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { + // if both before and after are empty, it means this file has no conflicts + // at all, only display it if the user wants it if (before.isEmpty() && after.isEmpty()) { return nullptr; } -- cgit v1.3.1 From 64604c966aaade7ceb9f5e547dfad8f51372434c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 16:44:11 -0400 Subject: elide first two columns of the advanced list --- src/modinfodialog.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c936f150..9db31da3 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -319,6 +319,20 @@ bool ExpanderWidget::opened() const } +class ElideLeftDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + +protected: + void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const + { + QStyledItemDelegate::initStyleOption(option, index); + option->textElideMode = Qt::ElideLeft; + } +}; + + ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), @@ -438,6 +452,17 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_overwrittenExpander.set(ui->overwrittenExpander, ui->overwrittenTree, true); m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); + + // left-elide the overwrites column so that the nearest are visible + ui->conflictsAdvancedList->setItemDelegateForColumn( + 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); + + // left-elide the file column to see filenames + ui->conflictsAdvancedList->setItemDelegateForColumn( + 1, new ElideLeftDelegate(ui->conflictsAdvancedList)); + + // don't elide the overwritten by column so that the nearest are visible + connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { refreshConflictLists(false, true); }); -- cgit v1.3.1 From 4d90e502266dfb886e1d089fb26780d19a38e5a7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 29 May 2019 09:45:30 -0400 Subject: added new filterwidget, will eventually get the operators functionality from the mod/pluging lists --- src/CMakeLists.txt | 3 ++ src/filterwidget.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/filterwidget.h | 27 ++++++++++++++++ src/modinfodialog.cpp | 2 ++ src/modinfodialog.h | 3 ++ src/modinfodialog.ui | 7 +--- 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/filterwidget.cpp create mode 100644 src/filterwidget.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cae26334..93597d62 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -115,6 +115,7 @@ SET(organizer_SRCS lcdnumber.cpp forcedloaddialog.cpp forcedloaddialogwidget.cpp + filterwidget.cpp shared/windows_error.cpp shared/error_report.cpp @@ -211,6 +212,7 @@ SET(organizer_HDRS lcdnumber.h forcedloaddialog.h forcedloaddialogwidget.h + filterwidget.h shared/windows_error.h shared/error_report.h @@ -391,6 +393,7 @@ set(utilities set(widgets descriptionpage genericicondelegate + filterwidget icondelegate lcdnumber logbuffer diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp new file mode 100644 index 00000000..39324bc6 --- /dev/null +++ b/src/filterwidget.cpp @@ -0,0 +1,88 @@ +#include "filterwidget.h" +#include "eventfilter.h" + +FilterWidget::FilterWidget() + : m_edit(nullptr), m_clear(nullptr), m_buddy(nullptr) +{ +} + +void FilterWidget::set(QLineEdit* edit) +{ + if (m_clear) { + delete m_clear; + m_clear = nullptr; + } + + m_edit = edit; + if (!m_edit) { + return; + } + + createClear(); + hookEvents(); +} + +void FilterWidget::clear() +{ + if (!m_edit) { + return; + } + + m_edit->clear(); +} + +void FilterWidget::createClear() +{ + m_clear = new QToolButton(m_edit); + + QPixmap pixmap(":/MO/gui/edit_clear"); + m_clear->setIcon(QIcon(pixmap)); + m_clear->setIconSize(pixmap.size()); + m_clear->setCursor(Qt::ArrowCursor); + m_clear->setStyleSheet("QToolButton { border: none; padding: 0px; }"); + m_clear->hide(); + + QObject::connect(m_clear, &QToolButton::clicked, [&]{ clear(); }); + QObject::connect(m_edit, &QLineEdit::textChanged, [&]{ onTextChanged(); }); + + repositionClearButton(); +} + +void FilterWidget::hookEvents() +{ + auto* f = new EventFilter(m_edit, [&](auto* w, auto* e) { + if (e->type() == QEvent::Resize) { + onResized(); + } + + return false; + }); + + m_edit->installEventFilter(f); +} + +void FilterWidget::onTextChanged() +{ + m_clear->setVisible(!m_edit->text().isEmpty()); +} + +void FilterWidget::onResized() +{ + repositionClearButton(); +} + +void FilterWidget::repositionClearButton() +{ + if (!m_clear) { + return; + } + + const QSize sz = m_clear->sizeHint(); + const int frame = m_edit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + const auto r = m_edit->rect(); + + const auto x = r.right() - frame - sz.width(); + const auto y = (r.bottom() + 1 - sz.height()) / 2; + + m_clear->move(x, y); +} diff --git a/src/filterwidget.h b/src/filterwidget.h new file mode 100644 index 00000000..07e216f1 --- /dev/null +++ b/src/filterwidget.h @@ -0,0 +1,27 @@ +#ifndef FILTERWIDGET_H +#define FILTERWIDGET_H + +class FilterWidget +{ +public: + FilterWidget(); + + void set(QLineEdit* edit); + void buddy(QWidget* w); + + void clear(); + +private: + QLineEdit* m_edit; + QToolButton* m_clear; + QWidget* m_buddy; + + void createClear(); + void hookEvents(); + void repositionClearButton(); + + void onTextChanged(); + void onResized(); +}; + +#endif // FILTERWIDGET_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 9db31da3..1e933a74 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -453,6 +453,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); + m_advancedConflictFilter.set(ui->conflictsAdvancedFilter); + // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 69b0069e..5169a993 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "tutorabledialog.h" #include "plugincontainer.h" #include "organizercore.h" +#include "filterwidget.h" #include #include @@ -427,6 +428,7 @@ private: std::map m_RealTabPos; ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; + FilterWidget m_advancedConflictFilter; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); @@ -448,6 +450,7 @@ private: const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); + void restoreTabState(const QByteArray &state); void restoreConflictExpandersState(const QByteArray &state); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e391da46..b55c0f07 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -782,7 +782,7 @@ text-align: left; 0 - + Filter @@ -1201,11 +1201,6 @@ p, li { white-space: pre-wrap; } QLineEdit
    modidlineedit.h
    - - MOBase::LineEditClear - QLineEdit -
    lineeditclear.h
    -
    -- cgit v1.3.1 From 98b3be3e9bbca73640842f6dadaa159dcec04f7b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 29 May 2019 10:08:46 -0400 Subject: removed buddy from FilterWidget, the intention was to hook a ctrl+f shortcut to it, but that won't work as a general solution, it would need to be on tabs for the modinfo dialog added a simple matches() that just searches for the string FilterWidget can cleanly unhook itself --- src/filterwidget.cpp | 40 +++++++++++++++++++++++++++++++++------- src/filterwidget.h | 12 +++++++++--- src/modinfodialog.cpp | 7 +++++++ 3 files changed, 49 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 39324bc6..7c47980e 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -2,24 +2,23 @@ #include "eventfilter.h" FilterWidget::FilterWidget() - : m_edit(nullptr), m_clear(nullptr), m_buddy(nullptr) + : m_edit(nullptr), m_eventFilter(nullptr), m_clear(nullptr) { } void FilterWidget::set(QLineEdit* edit) { - if (m_clear) { - delete m_clear; - m_clear = nullptr; - } + unhook(); m_edit = edit; + if (!m_edit) { return; } createClear(); hookEvents(); + clear(); } void FilterWidget::clear() @@ -31,6 +30,23 @@ void FilterWidget::clear() m_edit->clear(); } +bool FilterWidget::matches(const QString& s) const +{ + return s.contains(m_text); +} + +void FilterWidget::unhook() +{ + if (m_clear) { + delete m_clear; + m_clear = nullptr; + } + + if (m_edit) { + m_edit->removeEventFilter(m_eventFilter); + } +} + void FilterWidget::createClear() { m_clear = new QToolButton(m_edit); @@ -50,7 +66,7 @@ void FilterWidget::createClear() void FilterWidget::hookEvents() { - auto* f = new EventFilter(m_edit, [&](auto* w, auto* e) { + m_eventFilter = new EventFilter(m_edit, [&](auto* w, auto* e) { if (e->type() == QEvent::Resize) { onResized(); } @@ -58,12 +74,22 @@ void FilterWidget::hookEvents() return false; }); - m_edit->installEventFilter(f); + m_edit->installEventFilter(m_eventFilter); } void FilterWidget::onTextChanged() { m_clear->setVisible(!m_edit->text().isEmpty()); + + const auto text = m_edit->text(); + + if (text != m_text) { + m_text = text; + + if (changed) { + changed(); + } + } } void FilterWidget::onResized() diff --git a/src/filterwidget.h b/src/filterwidget.h index 07e216f1..ca731dc1 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -1,21 +1,27 @@ #ifndef FILTERWIDGET_H #define FILTERWIDGET_H +class EventFilter; + class FilterWidget { public: + std::function changed; + FilterWidget(); void set(QLineEdit* edit); - void buddy(QWidget* w); - void clear(); + bool matches(const QString& s) const; + private: QLineEdit* m_edit; + EventFilter* m_eventFilter; QToolButton* m_clear; - QWidget* m_buddy; + QString m_text; + void unhook(); void createClear(); void hookEvents(); void repositionClearButton(); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1e933a74..0f18033a 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -454,6 +454,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_advancedConflictFilter.set(ui->conflictsAdvancedFilter); + m_advancedConflictFilter.changed = [&]{ refreshConflictLists(false, true); }; // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( @@ -873,6 +874,12 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } } + if (!m_advancedConflictFilter.matches(before) && + !m_advancedConflictFilter.matches(relativeName) && + !m_advancedConflictFilter.matches(after)) { + return nullptr; + } + QTreeWidgetItem* item = new QTreeWidgetItem; item->setText(0, before); item->setText(1, relativeName); -- cgit v1.3.1 From 8efab8bf129b6f9928f478a429e20cf018e5e373 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 10:54:36 -0400 Subject: FilterWidget takes a predicate to match strings copied the logic from modlistsortproxy so the basic boolean syntax in filters works --- src/filterwidget.cpp | 34 ++++++++++++++++++++++++++++++++-- src/filterwidget.h | 2 +- src/modinfodialog.cpp | 13 +++++++++---- 3 files changed, 42 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 7c47980e..16a46b0e 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -30,9 +30,39 @@ void FilterWidget::clear() m_edit->clear(); } -bool FilterWidget::matches(const QString& s) const +bool FilterWidget::matches(std::function pred) const { - return s.contains(m_text); + const QStringList ORList = [&] { + QString filterCopy = QString(m_text); + filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); + return filterCopy.split(";", QString::SkipEmptyParts); + }(); + + if (ORList.isEmpty() || !pred) { + return true; + } + + // split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + QStringList ANDKeywords = ORSegment.split(" ", QString::SkipEmptyParts); + bool segmentGood = true; + + // check each word in the segment for match, each word needs to be matched + // but it doesn't matter where. + for (auto& currentKeyword : ANDKeywords) { + if (!pred(currentKeyword)) { + segmentGood = false; + } + } + + if (segmentGood) { + // the last AND loop didn't break so the ORSegments is true so mod + // matches filter + return true; + } + } + + return false; } void FilterWidget::unhook() diff --git a/src/filterwidget.h b/src/filterwidget.h index ca731dc1..4fee5983 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -13,7 +13,7 @@ public: void set(QLineEdit* edit); void clear(); - bool matches(const QString& s) const; + bool matches(std::function pred) const; private: QLineEdit* m_edit; diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 0f18033a..4852150f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -874,10 +874,15 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } } - if (!m_advancedConflictFilter.matches(before) && - !m_advancedConflictFilter.matches(relativeName) && - !m_advancedConflictFilter.matches(after)) { - return nullptr; + bool matched = m_advancedConflictFilter.matches([&](auto&& what) { + return + before.contains(what, Qt::CaseInsensitive) || + relativeName.contains(what, Qt::CaseInsensitive) || + after.contains(what, Qt::CaseInsensitive); + }); + + if (!matched) { + return nullptr; } QTreeWidgetItem* item = new QTreeWidgetItem; -- cgit v1.3.1 From b7580c5ad766ebf984641674fdc0f121d754c82e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 11:31:21 -0400 Subject: restore conflicts selected tab and checkboxes --- src/modinfodialog.cpp | 28 +++++++++++++++++++++++----- src/modinfodialog.h | 4 ++-- src/modinfodialog.ui | 2 +- 3 files changed, 26 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4852150f..1dd44112 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -553,13 +553,13 @@ int ModInfoDialog::tabIndex(const QString &tabId) void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); - s.directInterface().setValue("mod_info_conflict_expanders", saveConflictExpandersState()); + s.directInterface().setValue("mod_info_conflicts", saveConflictsState()); } void ModInfoDialog::restoreState(const Settings& s) { restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - restoreConflictExpandersState(s.directInterface().value("mod_info_conflict_expanders").toByteArray()); + restoreConflictsState(s.directInterface().value("mod_info_conflicts").toByteArray()); } void ModInfoDialog::restoreTabState(const QByteArray &state) @@ -593,7 +593,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) ui->tabWidget->blockSignals(false); } -void ModInfoDialog::restoreConflictExpandersState(const QByteArray &state) +void ModInfoDialog::restoreConflictsState(const QByteArray &state) { QDataStream stream(state); @@ -608,6 +608,20 @@ void ModInfoDialog::restoreConflictExpandersState(const QByteArray &state) m_overwrittenExpander.toggle(overwrittenExpanded); m_nonconflictExpander.toggle(noConflictExpanded); } + + int index = 0; + bool noConflictChecked = false; + bool showAllChecked = false; + bool showNearestChecked = false; + + stream >> index >> noConflictChecked >> showAllChecked >> showNearestChecked; + + if (stream.status() == QDataStream::Ok) { + ui->tabConflictsTabs->setCurrentIndex(index); + ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked); + ui->conflictsAdvancedShowAll->setChecked(showAllChecked); + ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked); + } } QByteArray ModInfoDialog::saveTabState() const @@ -622,7 +636,7 @@ QByteArray ModInfoDialog::saveTabState() const return result; } -QByteArray ModInfoDialog::saveConflictExpandersState() const +QByteArray ModInfoDialog::saveConflictsState() const { QByteArray result; QDataStream stream(&result, QIODevice::WriteOnly); @@ -630,7 +644,11 @@ QByteArray ModInfoDialog::saveConflictExpandersState() const stream << m_overwriteExpander.opened() << m_overwrittenExpander.opened() - << m_nonconflictExpander.opened(); + << m_nonconflictExpander.opened() + << ui->tabConflictsTabs->currentIndex() + << ui->conflictsAdvancedShowNoConflict->isChecked() + << ui->conflictsAdvancedShowAll->isChecked() + << ui->conflictsAdvancedShowNearest->isChecked(); return result; } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 5169a993..40e45eb4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -452,10 +452,10 @@ private: void restoreTabState(const QByteArray &state); - void restoreConflictExpandersState(const QByteArray &state); + void restoreConflictsState(const QByteArray &state); QByteArray saveTabState() const; - QByteArray saveConflictExpandersState() const; + QByteArray saveConflictsState() const; bool canHideConflictItem(const QTreeWidgetItem* item) const; bool canUnhideConflictItem(const QTreeWidgetItem* item) const; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index b55c0f07..6c1ed36d 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -402,7 +402,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + 1 -- cgit v1.3.1 From 65428e89ad9c2489d40cc01e026e6a73e64df258 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 11:59:19 -0400 Subject: put back changes from another PR lost because of a bad rebase --- src/modinfodialog.ui | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 6c1ed36d..8744b216 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -404,7 +404,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 1 + 0 @@ -481,24 +481,21 @@ text-align: left; Qt::ElideLeft + + true + true true - - false - 2 365 - - false - 200 @@ -554,9 +551,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true @@ -624,9 +627,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true -- cgit v1.3.1 From 0f6f0c23943116ea668ab035055cb8b0e4a6574e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 12:49:25 -0400 Subject: replaced all the manual UserRole stuff with a few constants and helper functions setConflictItem() is now used by all conflict lists to setup the data (filename, archive, etc.) and visuals (italic for archives) merged openDataFile() and previewDataFile() into their caller as they weren't used anywhere else previewDataFile() used to do a fromNativeSeparators() before previewing, moved that to previewFileWithAlternatives() instead brought overwrittenTree double click in line with overwriteTree, there's no difference between apply() and close() because there's only a close button --- src/modinfodialog.cpp | 133 +++++++++++++++++++++++++++----------------------- src/modinfodialog.h | 10 +++- src/modinfodialog.ui | 9 ++++ src/organizercore.cpp | 2 + 4 files changed, 91 insertions(+), 63 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1dd44112..e639dcc7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -57,6 +57,10 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +const auto FILENAME_USERROLE = Qt::UserRole + 1; +const auto ORIGIN_USERROLE = Qt::UserRole + 2; +const auto ARCHIVE_USERROLE = Qt::UserRole + 3; + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); @@ -744,18 +748,10 @@ QTreeWidgetItem* ModInfoDialog::createOverwriteItem( QStringList fields(relativeName); fields.append(altString); - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.back().first).getName())); - item->setData(1, Qt::UserRole + 1, alternatives.back().first); - item->setData(1, Qt::UserRole + 2, archive); + const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()); - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - item->setFont(1, font); - } + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + setConflictItem(item, fileName, origin, archive); return item; } @@ -764,13 +760,7 @@ QTreeWidgetItem* ModInfoDialog::createNoConflictItem( bool archive, const QString& fileName, const QString& relativeName) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); - item->setData(0, Qt::UserRole, fileName); - - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - } + setConflictItem(item, fileName, "", archive); return item; } @@ -785,16 +775,7 @@ QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( fields.append(ToQString(realOrigin.getName())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); - item->setData(1, Qt::UserRole + 2, archive); - - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - item->setFont(1, font); - } + setConflictItem(item, fileName, ToQString(realOrigin.getName()), archive); return item; } @@ -908,9 +889,44 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( item->setText(1, relativeName); item->setText(2, after); + setConflictItem(item, fileName, "", archive); + return item; } +void ModInfoDialog::setConflictItem( + QTreeWidgetItem* item, + const QString& fileName, const QString& origin, bool archive) const +{ + item->setData(0, FILENAME_USERROLE, fileName); + item->setData(0, ORIGIN_USERROLE, origin); + item->setData(0, ARCHIVE_USERROLE, archive); + + if (archive) { + QFont font = item->font(0); + font.setItalic(true); + + for (int i=0; icolumnCount(); ++i) { + item->setFont(i, font); + } + } +} + +QString ModInfoDialog::conflictFileName(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, FILENAME_USERROLE).toString(); +} + +QString ModInfoDialog::conflictOrigin(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, ORIGIN_USERROLE).toString(); +} + +bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, ARCHIVE_USERROLE).toBool(); +} + void ModInfoDialog::refreshFiles() { if (m_RootPath.length() > 0) { @@ -1860,8 +1876,12 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - this->close(); - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); + const auto origin = conflictOrigin(item); + + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } } FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) @@ -1907,14 +1927,14 @@ void ModInfoDialog::changeConflictItemsVisibility( qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; continue; } - result = unhideFile(renamer, item->data(0, Qt::UserRole).toString()); + result = unhideFile(renamer, conflictFileName(item)); } else { if (!canHideConflictItem(item)) { qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; continue; } - result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); + result = hideFile(renamer, conflictFileName(item)); } switch (result) { @@ -1953,7 +1973,9 @@ void ModInfoDialog::openConflictItems(const QList& items) // the menu item is only shown for a single selection, but handle all of them // in case this changes for (auto* item : items) { - openDataFile(item); + if (item) { + m_OrganizerCore->executeFileVirtualized(this, conflictFileName(item)); + } } } @@ -1962,28 +1984,10 @@ void ModInfoDialog::previewConflictItems(const QList& items) // the menu item is only shown for a single selection, but handle all of them // in case this changes for (auto* item : items) { - previewDataFile(item); - } -} - -void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) -{ - if (!item) { - return; - } - - QFileInfo targetInfo(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore->executeFileVirtualized(this, targetInfo); -} - -void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) -{ - if (!item) { - return; + if (item) { + m_OrganizerCore->previewFileWithAlternatives(this, conflictFileName(item)); + } } - - QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore->previewFileWithAlternatives(this, fileName); } bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) const @@ -2028,19 +2032,17 @@ bool ModInfoDialog::canUnhideFile(bool isArchive, const QString& filename) const bool ModInfoDialog::canHideConflictItem(const QTreeWidgetItem* item) const { - return canHideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); + return canHideFile(conflictIsArchive(item), conflictFileName(item)); } bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const { - return canUnhideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); + return canUnhideFile(conflictIsArchive(item), conflictFileName(item)); } bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const { - return canPreviewFile( - item->data(1, Qt::UserRole + 2).toBool(), - item->data(0, Qt::UserRole).toString()); + return canPreviewFile(conflictIsArchive(item), conflictFileName(item)); } void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) @@ -2058,6 +2060,11 @@ void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &p showConflictMenu(pos, ui->noConflictTree); } +void ModInfoDialog::on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos) +{ + showConflictMenu(pos, ui->conflictsAdvancedList); +} + void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) { auto actions = createConflictMenuActions(tree->selectedItems()); @@ -2181,8 +2188,12 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); - this->accept(); + const auto origin = conflictOrigin(item); + + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } } void ModInfoDialog::on_refreshButton_clicked() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 40e45eb4..59912127 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -368,6 +368,7 @@ private slots: void on_overwriteTree_customContextMenuRequested(const QPoint &pos); void on_overwrittenTree_customContextMenuRequested(const QPoint &pos); void on_noConflictTree_customContextMenuRequested(const QPoint &pos); + void on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); @@ -450,6 +451,13 @@ private: const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); + void setConflictItem( + QTreeWidgetItem* item, + const QString& fileName, const QString& origin, bool archive) const; + + QString conflictFileName(const QTreeWidgetItem* conflictItem) const; + QString conflictOrigin(const QTreeWidgetItem* conflictItem) const; + bool conflictIsArchive(const QTreeWidgetItem* conflictItem) const; void restoreTabState(const QByteArray &state); void restoreConflictsState(const QByteArray &state); @@ -461,8 +469,6 @@ private: bool canUnhideConflictItem(const QTreeWidgetItem* item) const; bool canPreviewConflictItem(const QTreeWidgetItem* item) const; - void openDataFile(const QTreeWidgetItem* item); - void previewDataFile(const QTreeWidgetItem* item); void changeFiletreeVisibility(bool visible); void openConflictItems(const QList& items); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 8744b216..93291c02 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -707,6 +707,15 @@ text-align: left; + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + + true + 3 diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3ad4e586..892162f6 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1321,6 +1321,8 @@ bool OrganizerCore::executeFileVirtualized( bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { + fileName = QDir::fromNativeSeparators(fileName); + // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory -- cgit v1.3.1 From 0da1c2591f0a67560b241515d996c39598da6735 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 13:09:17 -0400 Subject: don't show open menu item if the files are from an archive --- src/modinfodialog.cpp | 13 ++++++++++++- src/modinfodialog.h | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index e639dcc7..97507777 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -2000,6 +2000,12 @@ bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) cons return m_PluginContainer->previewGenerator().previewSupported(ext); } +bool ModInfoDialog::canOpenFile(bool isArchive, const QString&) const +{ + // can open anything as long as it's not in an archive + return !isArchive; +} + bool ModInfoDialog::canHideFile(bool isArchive, const QString& filename) const { if (isArchive) { @@ -2040,6 +2046,11 @@ bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const return canUnhideFile(conflictIsArchive(item), conflictFileName(item)); } +bool ModInfoDialog::canOpenConflictItem(const QTreeWidgetItem* item) const +{ + return canOpenFile(conflictIsArchive(item), conflictFileName(item)); +} + bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const { return canPreviewFile(conflictIsArchive(item), conflictFileName(item)); @@ -2131,8 +2142,8 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableHide = canHideConflictItem(item); enableUnhide = canUnhideConflictItem(item); + enableOpen = canOpenConflictItem(item); enablePreview = canPreviewConflictItem(item); - // open is always enabled } else { // this is a multiple selection, don't show open/preview so users don't open diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 59912127..35f87820 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -467,6 +467,7 @@ private: bool canHideConflictItem(const QTreeWidgetItem* item) const; bool canUnhideConflictItem(const QTreeWidgetItem* item) const; + bool canOpenConflictItem(const QTreeWidgetItem* item) const; bool canPreviewConflictItem(const QTreeWidgetItem* item) const; void changeFiletreeVisibility(bool visible); @@ -477,6 +478,7 @@ private: const QList& items, bool visible); bool canPreviewFile(bool isArchive, const QString& filename) const; + bool canOpenFile(bool isArchive, const QString& filename) const; bool canHideFile(bool isArchive, const QString& filename) const; bool canUnhideFile(bool isArchive, const QString& filename) const; -- cgit v1.3.1 From 7421aaa33b37bc13a9c5a3f5df2f06b6bf0f09e5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 13:59:08 -0400 Subject: conflict lists - sorting on advanced list - save state of all four lists - call refreshLists() just before showing because the state has to be loaded first, which happens after the ctor --- src/modinfodialog.cpp | 36 ++++++++++++++++++++++++++++++++++-- src/modinfodialog.h | 2 ++ src/modinfodialog.ui | 3 +++ 3 files changed, 39 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 97507777..b8a8ae69 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -397,8 +397,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - refreshLists(); - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); @@ -501,6 +499,12 @@ ModInfoDialog::~ModInfoDialog() } +int ModInfoDialog::exec() +{ + refreshLists(); + return TutorableDialog::exec(); +} + void ModInfoDialog::initINITweaks() { int numTweaks = m_Settings->beginReadArray("INI Tweaks"); @@ -558,12 +562,40 @@ void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); s.directInterface().setValue("mod_info_conflicts", saveConflictsState()); + + s.directInterface().setValue( + "mod_info_conflicts_overwrite", + ui->overwriteTree->header()->saveState()); + + s.directInterface().setValue( + "mod_info_conflicts_noconflict", + ui->noConflictTree->header()->saveState()); + + s.directInterface().setValue( + "mod_info_conflicts_overwritten", + ui->overwrittenTree->header()->saveState()); + + s.directInterface().setValue( + "mod_info_advanced_conflicts", + ui->conflictsAdvancedList->header()->saveState()); } void ModInfoDialog::restoreState(const Settings& s) { restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); restoreConflictsState(s.directInterface().value("mod_info_conflicts").toByteArray()); + + ui->overwriteTree->header()->restoreState( + s.directInterface().value("mod_info_conflicts_overwrite").toByteArray()); + + ui->noConflictTree->header()->restoreState( + s.directInterface().value("mod_info_conflicts_noconflict").toByteArray()); + + ui->overwrittenTree->header()->restoreState( + s.directInterface().value("mod_info_conflicts_overwritten").toByteArray()); + + ui->conflictsAdvancedList->header()->restoreState( + s.directInterface().value("mod_info_advanced_conflicts").toByteArray()); } void ModInfoDialog::restoreTabState(const QByteArray &state) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 35f87820..85505487 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -280,6 +280,8 @@ public: **/ void openTab(int tab); + int exec() override; + void saveState(Settings& s) const; void restoreState(const Settings& s); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 93291c02..20437e58 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -716,6 +716,9 @@ text-align: left; true + + true + 3 -- cgit v1.3.1 From a6dbb1d7668a2219811fec71a55294e1db6d14aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 14:09:36 -0400 Subject: advanced conflict list tooltips --- src/modinfodialog.ui | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 20437e58..e009a599 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -762,6 +762,9 @@ text-align: left; + + Whether files that have no conflicts should be visible in the list + Show files that have no conflicts @@ -772,6 +775,9 @@ text-align: left; + + Shows all mods overwriting or being overwritten by this mod + Show all conflicting mods @@ -782,6 +788,9 @@ text-align: left; + + Shows only the nearest conflicting mods, in order of priority + Show nearest conflicting mod -- cgit v1.3.1 From fa7e34e84a09ed7a2f453a35a48e71477c841e5b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 15:15:47 -0400 Subject: conflict lists: - the file index is now stored in the row data - the alternate origins are now added to the context menu in a "go to" submenu - context menu items are now visually disabled instead of omitted from the menu --- src/modinfodialog.cpp | 171 ++++++++++++++++++++++++++++++++---------- src/modinfodialog.h | 33 +++++--- src/shared/directoryentry.cpp | 5 ++ src/shared/directoryentry.h | 1 + 4 files changed, 160 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b8a8ae69..60cf7578 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -58,8 +58,10 @@ using namespace MOBase; using namespace MOShared; const auto FILENAME_USERROLE = Qt::UserRole + 1; -const auto ORIGIN_USERROLE = Qt::UserRole + 2; +const auto ALT_ORIGIN_USERROLE = Qt::UserRole + 2; const auto ARCHIVE_USERROLE = Qt::UserRole + 3; +const auto INDEX_USERROLE = Qt::UserRole + 4; +const auto HAS_ALTS_USERROLE = Qt::UserRole + 5; class ModFileListWidget : public QListWidgetItem { @@ -78,8 +80,8 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS } // if there are more than 50 selected items in the conflict tree or filetree, -// don't bother checking whether they're visible, just show both menu items -const int max_scan_for_visibility = 50; +// don't bother checking whether menu items apply to them, just show all of them +const int max_scan_for_context_menu = 50; FileRenamer::FileRenamer(QWidget* parent, QFlags flags) @@ -727,19 +729,19 @@ void ModInfoDialog::refreshConflictLists( if (fileOrigin == m_Origin->getID()) { if (!alternatives.empty()) { ui->overwriteTree->addTopLevelItem(createOverwriteItem( - archive, fileName, relativeName, alternatives)); + file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree ui->noConflictTree->addTopLevelItem(createNoConflictItem( - archive, fileName, relativeName)); + file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( - fileOrigin, archive, fileName, relativeName)); + file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; } @@ -747,7 +749,8 @@ void ModInfoDialog::refreshConflictLists( if (refreshAdvanced) { auto* advancedItem = createAdvancedConflictItem( - fileOrigin, archive, fileName, relativeName, alternatives); + file->getIndex(), fileOrigin, archive, + fileName, relativeName, alternatives); if (advancedItem) { ui->conflictsAdvancedList->addTopLevelItem(advancedItem); @@ -764,7 +767,8 @@ void ModInfoDialog::refreshConflictLists( } QTreeWidgetItem* ModInfoDialog::createOverwriteItem( - bool archive, const QString& fileName, const QString& relativeName, + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, const FileEntry::AlternativesVector& alternatives) { QString altString; @@ -783,22 +787,23 @@ QTreeWidgetItem* ModInfoDialog::createOverwriteItem( const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()); QTreeWidgetItem *item = new QTreeWidgetItem(fields); - setConflictItem(item, fileName, origin, archive); + setConflictItem(item, index, fileName, true, origin, archive); return item; } QTreeWidgetItem* ModInfoDialog::createNoConflictItem( - bool archive, const QString& fileName, const QString& relativeName) + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); - setConflictItem(item, fileName, "", archive); + setConflictItem(item, index, fileName, false, "", archive); return item; } QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( - int fileOrigin, bool archive, + FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName) { const FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); @@ -807,13 +812,13 @@ QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( fields.append(ToQString(realOrigin.getName())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); - setConflictItem(item, fileName, ToQString(realOrigin.getName()), archive); + setConflictItem(item, index, fileName, true, ToQString(realOrigin.getName()), archive); return item; } QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( - int fileOrigin, bool archive, + FileEntry::Index index,int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) { @@ -897,10 +902,12 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } } + bool hasAlts = !before.isEmpty() || !after.isEmpty(); + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it - if (before.isEmpty() && after.isEmpty()) { + if (!hasAlts) { return nullptr; } } @@ -921,18 +928,21 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( item->setText(1, relativeName); item->setText(2, after); - setConflictItem(item, fileName, "", archive); + setConflictItem(item, index, fileName, hasAlts, "", archive); return item; } void ModInfoDialog::setConflictItem( - QTreeWidgetItem* item, - const QString& fileName, const QString& origin, bool archive) const + QTreeWidgetItem* item, FileEntry::Index index, + const QString& fileName, bool hasAltOrigins, const QString& altOrigin, + bool archive) const { item->setData(0, FILENAME_USERROLE, fileName); - item->setData(0, ORIGIN_USERROLE, origin); + item->setData(0, ALT_ORIGIN_USERROLE, altOrigin); item->setData(0, ARCHIVE_USERROLE, archive); + item->setData(0, INDEX_USERROLE, index); + item->setData(0, HAS_ALTS_USERROLE, hasAltOrigins); if (archive) { QFont font = item->font(0); @@ -949,9 +959,14 @@ QString ModInfoDialog::conflictFileName(const QTreeWidgetItem* conflictItem) con return conflictItem->data(0, FILENAME_USERROLE).toString(); } -QString ModInfoDialog::conflictOrigin(const QTreeWidgetItem* conflictItem) const +QString ModInfoDialog::conflictAltOrigin(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, ALT_ORIGIN_USERROLE).toString(); +} + +bool ModInfoDialog::conflictHasAlts(const QTreeWidgetItem* conflictItem) const { - return conflictItem->data(0, ORIGIN_USERROLE).toString(); + return conflictItem->data(0, HAS_ALTS_USERROLE).toBool(); } bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const @@ -959,6 +974,12 @@ bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const return conflictItem->data(0, ARCHIVE_USERROLE).toBool(); } +FileEntry::Index ModInfoDialog::conflictFileIndex(const QTreeWidgetItem* conflictItem) const +{ + static_assert(std::is_same_v); + return conflictItem->data(0, INDEX_USERROLE).toUInt(); +} + void ModInfoDialog::refreshFiles() { if (m_RootPath.length() > 0) { @@ -1804,7 +1825,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) enablePreview = false; enableRename = false; - if (m_FileSelection.size() < max_scan_for_visibility) { + if (m_FileSelection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it enableHide = false; @@ -1908,7 +1929,7 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - const auto origin = conflictOrigin(item); + const auto origin = conflictAltOrigin(item); if (!origin.isEmpty()) { close(); @@ -2114,6 +2135,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) QMenu menu; + // open if (actions.open) { connect(actions.open, &QAction::triggered, [&]{ openConflictItems(tree->selectedItems()); @@ -2122,6 +2144,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.open); } + // preview if (actions.preview) { connect(actions.preview, &QAction::triggered, [&]{ previewConflictItems(tree->selectedItems()); @@ -2130,6 +2153,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.preview); } + // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ changeConflictItemsVisibility(tree->selectedItems(), false); @@ -2138,6 +2162,7 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.hide); } + // unhide if (actions.unhide) { connect(actions.unhide, &QAction::triggered, [&]{ changeConflictItemsVisibility(tree->selectedItems(), true); @@ -2146,15 +2171,27 @@ void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) menu.addAction(actions.unhide); } - if (menu.isEmpty()) { - return; + // goto + if (actions.gotoMenu) { + menu.addMenu(actions.gotoMenu); + + for (auto* a : actions.gotoActions) { + connect(a, &QAction::triggered, [&, name=a->text()]{ + close(); + emit modOpen(name, TAB_CONFLICTS); + }); + + actions.gotoMenu->addAction(a); + } } - menu.exec(tree->viewport()->mapToGlobal(pos)); + if (!menu.isEmpty()) { + menu.exec(tree->viewport()->mapToGlobal(pos)); + } } ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( - const QList selection) + const QList& selection) { if (selection.empty()) { return {}; @@ -2164,6 +2201,7 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( bool enableUnhide = true; bool enableOpen = true; bool enablePreview = true; + bool enableGoto = true; if (selection.size() == 1) { // this is a single selection @@ -2176,6 +2214,7 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableUnhide = canUnhideConflictItem(item); enableOpen = canOpenConflictItem(item); enablePreview = canPreviewConflictItem(item); + enableGoto = conflictHasAlts(item); } else { // this is a multiple selection, don't show open/preview so users don't open @@ -2183,7 +2222,10 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableOpen = false; enablePreview = false; - if (selection.size() < max_scan_for_visibility) { + // don't bother with this on multiple selection, at least for now + enableGoto = false; + + if (selection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it enableHide = false; @@ -2198,8 +2240,8 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableUnhide = true; } - if (enableHide && enableUnhide) { - // found both, no need to check more + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more break; } } @@ -2208,22 +2250,71 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( ConflictActions actions; - if (enableHide) { - actions.hide = new QAction(tr("Hide")); - } + actions.hide = new QAction(tr("Hide"), this); + actions.hide->setEnabled(enableHide); // note that it is possible for hidden files to appear if they override other // hidden files from another mod - if (enableUnhide) { - actions.unhide = new QAction(tr("Unhide")); + actions.unhide = new QAction(tr("Unhide"), this); + actions.unhide->setEnabled(enableUnhide); + + actions.open = new QAction(tr("Open/Execute"), this); + actions.open->setEnabled(enableOpen); + + actions.preview = new QAction(tr("Preview"), this); + actions.preview->setEnabled(enablePreview); + + actions.gotoMenu = new QMenu(tr("Go to..."), this); + actions.gotoMenu->setEnabled(enableGoto); + + if (enableGoto) { + actions.gotoActions = createGotoActions(selection); } - if (enableOpen) { - actions.open = new QAction(tr("Open/Execute")); + return actions; +} + +std::vector ModInfoDialog::createGotoActions(const QList& selection) +{ + if (!m_Origin || selection.size() != 1) { + return {}; + } + + auto* item = selection[0]; + if (!item) { + return {}; + } + + auto file = m_Origin->findFile(conflictFileIndex(item)); + if (!file) { + return {}; } - if (enablePreview) { - actions.preview = new QAction(tr("Preview")); + + std::vector mods; + + // add all alternatives + for (const auto& alt : file->getAlternatives()) { + const auto& o = m_Directory->getOriginByID(alt.first); + if (o.getID() != m_Origin->getID()) { + mods.push_back(ToQString(o.getName())); + } + } + + // add the real origin if different from this mod + const FilesOrigin& realOrigin = m_Directory->getOriginByID(file->getOrigin()); + if (realOrigin.getID() != m_Origin->getID()) { + mods.push_back(ToQString(realOrigin.getName())); + } + + std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { + return (QString::localeAwareCompare(a, b) < 0); + }); + + std::vector actions; + + for (const auto& name : mods) { + actions.push_back(new QAction(name, this)); } return actions; @@ -2231,7 +2322,7 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - const auto origin = conflictOrigin(item); + const auto origin = conflictAltOrigin(item); if (!origin.isEmpty()) { close(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 85505487..8510c96d 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -385,15 +385,20 @@ private slots: void createTweak(); private: + using FileEntry = MOShared::FileEntry; + struct ConflictActions { QAction* hide; QAction* unhide; QAction* open; QAction* preview; + QMenu* gotoMenu; + std::vector gotoActions; - ConflictActions() - : hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr) + ConflictActions() : + hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), + gotoMenu(nullptr) { } }; @@ -438,28 +443,33 @@ private: void refreshFiles(); QTreeWidgetItem* createOverwriteItem( - bool archive, const QString& fileName, const QString& relativeName, + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); QTreeWidgetItem* createNoConflictItem( - bool archive, const QString& fileName, const QString& relativeName); + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName); QTreeWidgetItem* createOverwrittenItem( - int fileOrigin, bool archive, + FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName); QTreeWidgetItem* createAdvancedConflictItem( - int fileOrigin, bool archive, + FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); void setConflictItem( - QTreeWidgetItem* item, - const QString& fileName, const QString& origin, bool archive) const; + QTreeWidgetItem* item, FileEntry::Index index, + const QString& fileName, bool hasAltOrigins, const QString& altOrigin, + bool archive) const; QString conflictFileName(const QTreeWidgetItem* conflictItem) const; - QString conflictOrigin(const QTreeWidgetItem* conflictItem) const; + QString conflictAltOrigin(const QTreeWidgetItem* conflictItem) const; + bool conflictHasAlts(const QTreeWidgetItem* conflictItem) const; bool conflictIsArchive(const QTreeWidgetItem* conflictItem) const; + FileEntry::Index conflictFileIndex(const QTreeWidgetItem* conflictItem) const; void restoreTabState(const QByteArray &state); void restoreConflictsState(const QByteArray &state); @@ -487,7 +497,10 @@ private: void showConflictMenu(const QPoint &pos, QTreeWidget* tree); ConflictActions createConflictMenuActions( - const QList selection); + const QList& selection); + + std::vector createGotoActions( + const QList& selection); }; #endif // MODINFODIALOG_H diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 1179110a..bde515a9 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -220,6 +220,11 @@ std::vector FilesOrigin::getFiles() const return result; } +FileEntry::Ptr FilesOrigin::findFile(FileEntry::Index index) const +{ + return m_FileRegister.lock()->getFile(index); +} + bool FilesOrigin::containsArchive(std::wstring archiveName) { for (FileEntry::Index fileIdx : m_Files) diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index e7af1ae7..785c3ff6 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -135,6 +135,7 @@ public: const std::wstring &getPath() const { return m_Path; } std::vector getFiles() const; + FileEntry::Ptr findFile(FileEntry::Index index) const; void enable(bool enabled, time_t notAfter = LONG_MAX); bool isDisabled() const { return m_Disabled; } -- cgit v1.3.1 From d2eb890f2b9df13c402db6adeaafa7e3f4a7d736 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 17:28:59 -0400 Subject: refresh files in the constructor because the ui is used to determine the tab states --- src/modinfodialog.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 60cf7578..5d2dd811 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -399,6 +399,12 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } + // refresh everything but the conflict lists, which are done in exec() because + // they depend on restoring the state to some widgets; this refresh has to be + // done here because some of the checks below depend on the ui to decide which + // tabs to enable + refreshFiles(); + if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); @@ -503,7 +509,8 @@ ModInfoDialog::~ModInfoDialog() int ModInfoDialog::exec() { - refreshLists(); + // no need to refresh the other stuff, that was done in the constructor + refreshConflictLists(true, true); return TutorableDialog::exec(); } -- 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') 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') 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') 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') 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') 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') 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') 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 3c69f1e54ab1eb99b2d1b30e5dfa5f28239e4314 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 21:50:52 -0400 Subject: update styles to handle the new menu bar also formatted the Transparent styles, all the css was on one line added a separator before the settings menu item fixed medium icon item not being checkable --- src/mainwindow.ui | 4 + src/stylesheets/Night Eyes.qss | 50 +- src/stylesheets/Paper Automata.qss | 10 + src/stylesheets/Paper Dark by 6788.qss | 12 + src/stylesheets/Paper Light by 6788.qss | 12 + src/stylesheets/Parchment v1.1 by Bob.qss | 68 ++- src/stylesheets/Transparent-Style-101-Green.qss | 490 +++++++++++++++- src/stylesheets/Transparent-Style-BOS.qss | 743 +++++++++++++++++++++++- src/stylesheets/Transparent-Style-Skyrim.qss | 743 +++++++++++++++++++++++- src/stylesheets/dark.qss | 5 + src/stylesheets/skyrim.qss | 8 + src/stylesheets/vs15 Dark-Green.qss | 14 +- src/stylesheets/vs15 Dark-Orange.qss | 14 +- src/stylesheets/vs15 Dark-Purple.qss | 14 +- src/stylesheets/vs15 Dark-Red.qss | 14 +- src/stylesheets/vs15 Dark-Yellow.qss | 14 +- src/stylesheets/vs15 Dark.qss | 14 +- 17 files changed, 2135 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 29a8ac76..05d64bdf 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1407,6 +1407,7 @@ p, li { white-space: pre-wrap; } + @@ -1785,6 +1786,9 @@ p, li { white-space: pre-wrap; } + + true + M&edium Icons diff --git a/src/stylesheets/Night Eyes.qss b/src/stylesheets/Night Eyes.qss index 6c6b0629..f2f193c4 100644 --- a/src/stylesheets/Night Eyes.qss +++ b/src/stylesheets/Night Eyes.qss @@ -106,15 +106,15 @@ QTreeView::item:selected color: #FFCC88; } -QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { image: url(:/stylesheet/branch-closed.png); border: 0px; } -QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { image: url(:/stylesheet/branch-open.png); border: 0px; @@ -342,41 +342,41 @@ QScrollBar::sub-line:vertical /* Combined */ -QScrollBar::handle:horizontal:hover, +QScrollBar::handle:horizontal:hover, QScrollBar::handle:vertical:hover, -QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { background: #242424; } -QScrollBar::handle:horizontal:pressed, +QScrollBar::handle:horizontal:pressed, QScrollBar::handle:vertical:pressed, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { background: #181818; } -QScrollBar::add-page:horizontal, -QScrollBar::sub-page:horizontal, -QScrollBar::add-page:vertical, +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal, +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } -QScrollBar::up-arrow:vertical, +QScrollBar::up-arrow:vertical, QScrollBar::right-arrow:horizontal, -QScrollBar::down-arrow:vertical, +QScrollBar::down-arrow:vertical, QScrollBar::left-arrow:horizontal { height: 1px; - width: 1px; + width: 1px; border: 1px solid #181818; } @@ -426,6 +426,16 @@ QHeaderView::down-arrow /* Context Menus, Toolbar Dropdowns, & Tooltips ------------------------------- */ +QMenuBar { + background: #181818; + border: 1px solid #181818; +} + +QMenuBar::item:selected { + background: #242424; + color: #FFCC88; +} + QMenu { background: #141414; @@ -615,8 +625,8 @@ QWidget#downloadTab QAbstractScrollArea background: #242424; } -DownloadListWidget QFrame, -DownloadListWidgetCompact, +DownloadListWidget QFrame, +DownloadListWidgetCompact, DownloadListWidgetCompact QLabel { /* an entry on the Downloads tab */ @@ -641,7 +651,7 @@ DownloadListWidget QFrame:clicked /* compact downloads view */ -DownloadListWidgetCompact, +DownloadListWidgetCompact, DownloadListWidgetCompact QLabel { /* an entry on the Downloads tab */ diff --git a/src/stylesheets/Paper Automata.qss b/src/stylesheets/Paper Automata.qss index d72aa7e2..c9830331 100644 --- a/src/stylesheets/Paper Automata.qss +++ b/src/stylesheets/Paper Automata.qss @@ -593,6 +593,16 @@ QHeaderView::section:last { /* Right Click Menus, Toolbar Dropdown Menus, & Tooltips */ +QMenuBar { + background: #DAD4BB; + border: 2px solid #CDC8B0; +} + +QMenuBar::item:selected { + background: #B4AF9A; + border: none; +} + QMenu { /* right click menu */ background: #DAD4BB; diff --git a/src/stylesheets/Paper Dark by 6788.qss b/src/stylesheets/Paper Dark by 6788.qss index 9abd8086..cde25733 100644 --- a/src/stylesheets/Paper Dark by 6788.qss +++ b/src/stylesheets/Paper Dark by 6788.qss @@ -588,6 +588,18 @@ QHeaderView::down-arrow { /* Right Click Menus, Toolbar Dropdown Menus, & Tooltips */ +QMenuBar { + background: #242424; + border: 1px solid #242424; +} + +QMenuBar::item:selected { + background: #006868; + color: #FFFFFF; + border: 0px; + border-radius: 4px; +} + QMenu { /* right click menu */ background: #141414; diff --git a/src/stylesheets/Paper Light by 6788.qss b/src/stylesheets/Paper Light by 6788.qss index 4aad55a8..54af277a 100644 --- a/src/stylesheets/Paper Light by 6788.qss +++ b/src/stylesheets/Paper Light by 6788.qss @@ -590,6 +590,18 @@ QHeaderView::down-arrow { /* Right Click Menus, Toolbar Dropdown Menus, & Tooltips */ +QMenuBar { + background: #EBEBEB; + border: 1px solid #EBEBEB; +} + +QMenuBar::item:selected { + background: #008484; + color: #FFFFFF; + border: 0px; + border-radius: 4px; +} + QMenu { /* right click menu */ background: #FFFFFF; diff --git a/src/stylesheets/Parchment v1.1 by Bob.qss b/src/stylesheets/Parchment v1.1 by Bob.qss index da3546d1..45c1d57b 100644 --- a/src/stylesheets/Parchment v1.1 by Bob.qss +++ b/src/stylesheets/Parchment v1.1 by Bob.qss @@ -95,7 +95,7 @@ QTreeView { } QTreeView::branch:hover { - background: #008F8F; + background: #008F8F; color: #F7F6CF; } @@ -109,13 +109,13 @@ QTreeView::item:selected { color: #F7F6CF; } -QTreeView::branch:has-children:!has-siblings:closed, -QTreeView::branch:closed:has-children:has-siblings { +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { image: url(:/stylesheet/branch-closed.png); border: 0px; } -QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { image: url(:/stylesheet/branch-open.png); border: 0px; @@ -168,7 +168,7 @@ QCheckBox::indicator { background-color: transparent; border: none; width: 14px; - height: 14px; + height: 14px; } QGroupBox::indicator:checked, QGroupBox::indicator:indeterminate, @@ -177,7 +177,7 @@ QTreeView::indicator:indeterminate, QCheckBox::indicator:checked, QCheckBox::indicator:indeterminate { - image: url(./Parchment/checkbox-checked.png); + image: url(./Parchment/checkbox-checked.png); } QGroupBox::indicator:checked:hover, QGroupBox::indicator:indeterminate:hover, @@ -186,7 +186,7 @@ QTreeView::indicator:indeterminate:hover, QCheckBox::indicator:checked:hover, QCheckBox::indicator:indeterminate:hover { - image: url(./Parchment/checkbox-checked-hover.png); + image: url(./Parchment/checkbox-checked-hover.png); } QGroupBox::indicator:checked:disabled, QGroupBox::indicator:indeterminate:disabled, @@ -195,28 +195,28 @@ QCheckBox::indicator:indeterminate:hover { QCheckBox::indicator:checked:disabled, QCheckBox::indicator:indeterminate:disabled { - image: url(./Parchment/checkbox-checked-disabled.png); + image: url(./Parchment/checkbox-checked-disabled.png); } QGroupBox::indicator:unchecked, QTreeView::indicator:unchecked, QCheckBox::indicator:unchecked { - image: url(./Parchment/checkbox.png); + image: url(./Parchment/checkbox.png); } QGroupBox::indicator:unchecked:hover, QTreeView::indicator:unchecked:hover, QCheckBox::indicator:unchecked:hover { - image: url(./Parchment/checkbox-hover.png); + image: url(./Parchment/checkbox-hover.png); } QGroupBox::indicator:unchecked:disabled, QTreeView::indicator:unchecked:disabled, QCheckBox::indicator:unchecked:disabled { - image: url(./Parchment/checkbox-disabled.png); + image: url(./Parchment/checkbox-disabled.png); } /* Search Boxes */ @@ -323,7 +323,7 @@ QScrollBar::add-line:horizontal { margin: 0px -2px -2px 0px; } -QScrollBar::sub-line:horizontal { +QScrollBar::sub-line:horizontal { background: #F7F6CF; width: 23px; subcontrol-position: left; @@ -359,7 +359,7 @@ QScrollBar::add-line:vertical { margin: 0px -2px -2px 0px; } -QScrollBar::sub-line:vertical { +QScrollBar::sub-line:vertical { background: #F7F6CF; height: 23px; subcontrol-position: top; @@ -371,37 +371,37 @@ QScrollBar::sub-line:vertical { /* Combined */ -QScrollBar::handle:horizontal:hover, +QScrollBar::handle:horizontal:hover, QScrollBar::handle:vertical:hover, -QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { background: #008F8F; } -QScrollBar::handle:horizontal:pressed, +QScrollBar::handle:horizontal:pressed, QScrollBar::handle:vertical:pressed, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { background: #0CA6FF; } -QScrollBar::add-page:horizontal, -QScrollBar::sub-page:horizontal, -QScrollBar::add-page:vertical, +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal, +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } -QScrollBar::up-arrow:vertical, +QScrollBar::up-arrow:vertical, QScrollBar::right-arrow:horizontal, -QScrollBar::down-arrow:vertical, +QScrollBar::down-arrow:vertical, QScrollBar::left-arrow:horizontal { height: 1px; - width: 1px; + width: 1px; border: 1px solid #DBD399; } @@ -443,6 +443,16 @@ QHeaderView::down-arrow { /* Context Menus, Toolbar Dropdowns, & Tooltips */ +QMenuBar { + background: #DBD399; + border: 1px solid #DBD399; +} + +QMenuBar::item:selected { + background: #008F8F; + color: #F7F6CF; +} + QMenu { background: #F7F6CF; selection-color: #F7F6CF; @@ -465,7 +475,7 @@ QMenu::item:disabled { color: #444444; } -QMenu::separator { +QMenu::separator { background: #DBD399; height: 2px; } @@ -483,7 +493,7 @@ QToolTip { /* Progress Bars (Downloads) */ -QProgressBar { +QProgressBar { background: #F7F6CF; text-align: center; border: 0px; diff --git a/src/stylesheets/Transparent-Style-101-Green.qss b/src/stylesheets/Transparent-Style-101-Green.qss index f7bf16b6..7dbf6157 100644 --- a/src/stylesheets/Transparent-Style-101-Green.qss +++ b/src/stylesheets/Transparent-Style-101-Green.qss @@ -1 +1,489 @@ -#centralWidget { border-image: url(./Transparent-Style/Green/vault-101.png) 0 0 0 0 stretch stretch; } QDialog,Qmenu { border: none; } QWidget { color: #fff; background-color: rgb(0, 204, 0, 5); alternate-background-color: rgb(0, 204, 0, 10); } QAbstractItemView { color: #fff; background-color: rgb(0, 204, 0, 5); alternate-background-color: rgb(0, 204, 0, 10); border: 1px solid #001a00; border-style:outset; border-radius: 3px; selection-color: #001a00; outline: none; } #ProblemsDialog,#TransferSavesDialog,#SettingsDialog,#ProblemsDialog, #ModInfoDialog,#TextViewer,#PyCfgDialog,#InstallDialog { border-image:url(./Transparent-Style/Green/vault-tec-full.png) 0 0 0 0 stretch stretch; } #EditExecutablesDialog { border-image: url(./Transparent-Style/Green/vault-tec.png) 0 0 0 0 stretch stretch; } #SimpleInstallDialog,#LockedDialog,QMenu { border-image: url(./Transparent-Style/Green/vault-tec-small.png) 0 0 0 0 stretch stretch; } #nameLabel { background-color: rgb(0, 0, 0, 20); border-top:1px solid black; border-bottom:1px solid black; } #ProfilesDialog,#FomodInstallerDialog { border-image: url(./Transparent-Style/Green/vault-tec-profiles.png) 0 0 0 0 stretch stretch; } #filesView { border-image: url(./Transparent-Style/Green/vault-tec-overwrite.png) 0 0 0 0 stretch stretch; } /* ******************************************** */ /* Main Navigation Button Bar at top of window */ /* ******************************************** */ QToolBar { border: none; } QToolBar::separator { image: url(./Transparent-Style/Green/Vault-101-vault-boy.png); } QToolButton { border:2px ridge None; border-radius: 6px; margin: 3px; padding-left: 8px; padding-right: 8px; padding-top: 0px; padding-bottom: 2px; } QToolButton:hover { border: 2px ridge #fff; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); } QToolButton:pressed { background: black; } QTabWidget::pane { background-color: transparent; border:transparent; } QTabWidget::tab-bar { alignment: center; } /* **************************************** */ /* Tabs on top of Treeview */ /* **************************************** */ QTabBar { text-transform: uppercase; max-height: 22px; padding-bottom: 5px; } QTabBar::tab { border: none; border-bottom-style: none; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); padding-left: 15px; padding-right: 15px; padding-top: 3px; padding-bottom: 3px; margin-right: 2px; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; } QTabBar::tab:selected { color: #fff; background-color: #00cc00; } QTabBar::tab:!selected { color: #00cc00; margin-top: 0px; } QTabBar::tab:disabled { color: #bbbbbb; border-bottom-style: none; margin-top: 0px; background-color: #000; } #deactivateESP,#activateESP { border:px #00cc00; background:QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); } QToolButton::menu-indicator:pressed,QToolButton::menu-indicator:open { border:2px ridge #00cc00; image: url(./Transparent-Style/Green/arrow-right.png); } QTabBar QToolButton { border:2px ridge #00cc00; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); padding: 1px; margin: 0; } QTabBar QToolButton::right-arrow { image: url(./Transparent-Style/Green/arrow-right.png); } QTabBar QToolButton::left-arrow { image: url(./Transparent-Style/Green/arrow-left.png); } /* **************************************** */ /* Column names of TreeView */ /* **************************************** */ QTableView { color:#fff; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); } QHeaderView::section { color: #00cc00; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); border-radius: 6px; padding: 4px; padding-left: 5px; padding-right: 0px; } QHeaderView::section:hover { background: #121212; border: 2px solid #ff0000; padding: 0px; } QHeaderView::up-arrow,QHeaderView::down-arrow { subcontrol-origin: content; subcontrol-position: center right; width: 7px; height: 7px; margin-right: 7px; } QHeaderView::up-arrow { image: url(./Transparent-Style/Green/arrow-up.png); } QHeaderView::down-arrow { image: url(./Transparent-Style/Green/arrow-down.png); } /* **************************************** */ /* Hover */ /* **************************************** */ QMenu:item:hover,QTreeView#espList::item:hover,QTreeWidget#categoriesList::item:hover,QListView::item:hover,QTreeWidget::item:hover { color:#fff; border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:#000; } QAbstractItemView::item:hover { border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:#000; padding: 3px; } #deactivateESP:hover,#activateESP:hover,QPushButton:hover,QTableView:hover,QHeaderView::selected:hover,QTabBar::tab:!selected:hover,QComboBox:hover { border: 1px solid #ff0000; padding: 1px; text-align: center; } /* **************************************** */ /* Context menus, toolbar dropdowns #QMenu */ /* **************************************** */ QMenu { border-width: 3px; border-style: solid; } QMenu::item { padding: 6px 20px; } QMenu::item:selected { color:#fff; background-color: #001a00; border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; } QMenu::right-arrow { image: url(./Transparent-Style/Green/arrow-right.png); subcontrol-origin: padding; subcontrol-position: center right; padding-right: 0.5em; } /* ********************* */ /* LCD Counter */ /* ********************* */ QLCDNumber { Background:#000; border-color: #00cc00; border-style: solid; border-width: 1px; border-radius: 5px; } /* **************************************** */ /* Launch application Drop down Box */ /* **************************************** */ QComboBox { background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); border:1px outset #00cc00; height: 20px; width: 15px; padding: 3px; padding-left:3px; border-radius: 5px; } QComboBox::drop-down{ subcontrol-origin: padding; subcontrol-position: top right; width: 15px; padding:4px; border-radius: 5px; } QComboBox::down-arrow{ image:url(./Transparent-Style/Green/arrow-down.png); } QComboBox::item{ border:3px;padding: 3px; } /* **************************************************************** */ /* Action Buttons */ /* */ /* Open list, Rextore Backup, Create Backup, RUN, Shortcut Button */ /* Sort, Load order backup, Load Order Restore */ /* **************************************************************** */ QPushButton { color: #00cc00; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); border-width: 1px; border-color: 1px outset #00cc00; border-style: solid; border-radius: 4px; padding:5px; padding-right: 10px; padding-left: 5px; } QDialog QPushButton { min-width: 3em; min-height: 1em; padding-left: 1em; padding-right: 1em; } QPushButton::menu-indicator { image: url(./Transparent-Style/Green/arrow-down.png); background: transparent; subcontrol-origin: padding; subcontrol-position: center right; padding-right: 5%; } QPushButton:open { background-color: rgb(0, 204, 0, 100); } QPushButton:!enabled { border: 1px grey; color: grey; background: black; } QPushButton#displayCategoriesBtn { min-width: 20px; } /* **************************************** */ /* Scroll bar */ /* **************************************** */ QScrollBar { height:20px; width:20px; background-color:transparent; } QScrollBar::handle { border: 1px solid #aaa; border-radius: 4px; } QScrollBar::handle:vertical { margin: 1px 4px; min-height:20px; min-width: 10px; background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #33ff33, stop:1 #000000); } QScrollBar::handle:vertical:hover { background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #000000, stop:1 #33ff33); } QScrollBar::handle:horizontal { min-height:10px;min-width:20px;margin:4px 1px; background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #33ff33, stop:1 #000000); } QScrollBar::handle:horizontal:hover { background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #000000, stop:1 #33ff33); } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical,QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { background-color: transparent; } QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical,QScrollBar::add-line:horizontal,QScrollBar::sub-line:horizontal { height:0; width:0; } /* ******************************************************** */ /* Main Tree View Inherated from QAbstractItemView */ /* ******************************************************** */ QTreeView { } QTreeView:item { padding:4px; } QTreeView:item:selected { color: #001a00; border: none;outline: none; border-radius: 5px; border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color: rgb(0, 204, 0, 100); } QTreeView::branch:has-siblings:!adjoins-item { background:url(Vault-101/Vault-101-item-line-v.png) center center no-repeat; } QTreeView::branch:has-siblings { background:url(Vault-101/Vault-101-itemB-line.png) center center no-repeat; } QTreeView::branch:!has-children:!has-siblings:adjoins-item { background:url(Vault-101/Vault-101-itemB-end.png) center center no-repeat; } QTreeView::branch:closed:has-children:has-siblings { background:url(Vault-101/Vault-101-itemB-close.png) center center no-repeat; } QTreeView::branch:closed:has-children:!has-siblings { background:url(Vault-101/Vault-101-itemB-close-last.png) center center no-repeat; } QTreeView::branch:open:has-children:has-siblings { background:url(Vault-101/Vault-101-itemB-open.png) center center no-repeat; } QTreeView::branch:open:has-children:!has-siblings { background:url(Vault-101/Vault-101-itemB-open-last.png) center center no-repeat; } /* **************************************************************** */ /* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */ /* common */ /* **************************************************************** */ QGroupBox::indicator,QTreeView::indicator,QCheckBox::indicator { outline: none; border: none; width: 20px; height: 20px; } QGroupBox::indicator:checked,QGroupBox::indicator:indeterminate,QTreeView::indicator:checked,QTreeView::indicator:indeterminate,QCheckBox::indicator:checked,QCheckBox::indicator:indeterminate { image: url(./Transparent-Style/Green/checkbox-checked.png); } QGroupBox::indicator:checked:hover,QGroupBox::indicator:indeterminate:hover,QTreeView::indicator:checked:hover,QTreeView::indicator:indeterminate:hover,QCheckBox::indicator:checked:hover,QCheckBox::indicator:indeterminate:hover { image: url(./Transparent-Style/Green/checkbox-hover.png); } QGroupBox::indicator:checked:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:checked:disabled,QTreeView::indicator:indeterminate:disabled,QCheckBox::indicator:checked:disabled,QCheckBox::indicator:indeterminate:disabled { image: url(./Transparent-Style/Green/checkbox-checked-disabled.png); } QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QCheckBox::indicator:unchecked { image: url(./Transparent-Style/Green/checkbox.png); } QGroupBox::indicator:unchecked:hover,QTreeView::indicator:unchecked:hover,QCheckBox::indicator:unchecked:hover { image: url(./Transparent-Style/Green/checkbox-checked-hover.png); } QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QCheckBox::indicator:unchecked:disabled { image: url(./Transparent-Style/Green/checkbox-checked-disabled.png); } QTreeWidget#categoriesList::item:has-children { background-image: url(./Transparent-Style/Green/arrow-right.png); } QTreeWidget#categoriesList::item:has-children:open { background-image: url(./Transparent-Style/Green/branch-opened.png); } /* ******************************** */ /* Special styles */ /* increase categories tab width */ /* ******************************** */ QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item { padding: .3em 0; } QTreeWidget#categoriesGroup,QTreeWidget#categoriesList { min-width: 200px; } QTreeWidget#categoriesList::item { background-position: center left; background-repeat: no-repeat; padding: 0.35em 4px; } /* ********************* */ /* Display Radio Button */ /* ********************* */ QRadioButton::indicator { width: 16px; height: 16px; } QRadioButton::indicator::checked { image: url(./Transparent-Style/Green/radio-checked.png); } QRadioButton::indicator::unchecked { image: url(./Transparent-Style/Green/radio.png); } QRadioButton::indicator::unchecked:hover { image: url(./Transparent-Style/Green/radio-hover.png); } /* **************************************** */ /* Spinners #QSpinBox, #QDoubleSpinBox */ /* **************************************** */ QAbstractSpinBox { min-height: 24px; } QAbstractSpinBox::up-button,QAbstractSpinBox::down-button { border-style: solid; border-width: 1px; subcontrol-origin: padding; } QAbstractSpinBox::up-button:hover,QAbstractSpinBox::down-button:hover { background-color: #141414; } QAbstractSpinBox::up-button { subcontrol-position: top right; } QAbstractSpinBox::down-button { subcontrol-position: bottom right; } QAbstractSpinBox::up-arrow { image: url(./Transparent-Style/Green/arrow-up.png); } QAbstractSpinBox::down-arrow { image: url(./Transparent-Style/Green/arrow-down.png); } /* **************************************** */ /* Tooltips #QToolTip, #SaveGameInfoWidget */ /* **************************************** */ QToolTip { background-color:transparent; color:#C0C0C0; padding:0px; border-width:7px; border-style:solid; border-color:transparent; border-image:url(./Transparent-Style/Green/border-image.png) 27 repeat repeat; } SaveGameInfoWidget { background-color:#121212; color:#C0C0C0; } /* **************************** */ /* Handles Web, Nexus info tab */ /* **************************** */ QWebView { background-color: Black; } QLineEdit { font-family: Source Sans Pro; background:#000; border-width: 1px; border-radius:5px; margin: 0px; padding-left:2px; selection-background-color: rgb(0, 204, 0, 10); } /* Font size */ QLabel, QMenu, QPushButton, QTabBar, QTextEdit, QLineEdit, QWebView, QComboBox, QComboBox:editable, QAbstractSpinBox, QGroupBox, QCheckBox, QRadioButton { color: #cccccc; font-family: Source Sans Pro; font-size: 14px; } QAbstractItemView { color: #cccccc; font-family: Source Sans Pro; font-size: 14px; } \ No newline at end of file +#centralWidget { + border-image: url(./Transparent-Style/Green/vault-101.png) 0 0 0 0 stretch stretch; +} + QDialog,Qmenu { + border: none; +} + QWidget { + color: #fff; + background-color: rgb(0, 204, 0, 5); + alternate-background-color: rgb(0, 204, 0, 10); +} + QAbstractItemView { + color: #fff; + background-color: rgb(0, 204, 0, 5); + alternate-background-color: rgb(0, 204, 0, 10); + border: 1px solid #001a00; + border-style:outset; + border-radius: 3px; + selection-color: #001a00; + outline: none; +} + #ProblemsDialog,#TransferSavesDialog,#SettingsDialog,#ProblemsDialog, #ModInfoDialog,#TextViewer,#PyCfgDialog,#InstallDialog { + border-image:url(./Transparent-Style/Green/vault-tec-full.png) 0 0 0 0 stretch stretch; +} + #EditExecutablesDialog { + border-image: url(./Transparent-Style/Green/vault-tec.png) 0 0 0 0 stretch stretch; +} + #SimpleInstallDialog,#LockedDialog,QMenu { + border-image: url(./Transparent-Style/Green/vault-tec-small.png) 0 0 0 0 stretch stretch; +} + #nameLabel { + background-color: rgb(0, 0, 0, 20); + border-top:1px solid black; + border-bottom:1px solid black; +} + #ProfilesDialog,#FomodInstallerDialog { + border-image: url(./Transparent-Style/Green/vault-tec-profiles.png) 0 0 0 0 stretch stretch; +} + #filesView { + border-image: url(./Transparent-Style/Green/vault-tec-overwrite.png) 0 0 0 0 stretch stretch; +} +/* ******************************************** */ +/* Main Navigation Button Bar at top of window */ +/* ******************************************** */ + QToolBar { + border: none; +} + QToolBar::separator { + image: url(./Transparent-Style/Green/Vault-101-vault-boy.png); +} + QToolButton { + border:2px ridge None; + border-radius: 6px; + margin: 3px; + padding-left: 8px; + padding-right: 8px; + padding-top: 0px; + padding-bottom: 2px; +} + QToolButton:hover { + border: 2px ridge #fff; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); +} + QToolButton:pressed { + background: black; +} + QTabWidget::pane { + background-color: transparent; + border:transparent; +} + QTabWidget::tab-bar { + alignment: center; +} +/* **************************************** */ +/* Tabs on top of Treeview */ +/* **************************************** */ + QTabBar { + text-transform: uppercase; + max-height: 22px; + padding-bottom: 5px; +} + QTabBar::tab { + border: none; + border-bottom-style: none; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); + padding-left: 15px; + padding-right: 15px; + padding-top: 3px; + padding-bottom: 3px; + margin-right: 2px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + QTabBar::tab:selected { + color: #fff; + background-color: #00cc00; +} + QTabBar::tab:!selected { + color: #00cc00; + margin-top: 0px; +} + QTabBar::tab:disabled { + color: #bbbbbb; + border-bottom-style: none; + margin-top: 0px; + background-color: #000; +} + #deactivateESP,#activateESP { + border:px #00cc00; + background:QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); +} + QToolButton::menu-indicator:pressed,QToolButton::menu-indicator:open { + border:2px ridge #00cc00; + image: url(./Transparent-Style/Green/arrow-right.png); +} + QTabBar QToolButton { + border:2px ridge #00cc00; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); + padding: 1px; + margin: 0; +} + QTabBar QToolButton::right-arrow { + image: url(./Transparent-Style/Green/arrow-right.png); +} + QTabBar QToolButton::left-arrow { + image: url(./Transparent-Style/Green/arrow-left.png); +} +/* **************************************** */ +/* Column names of TreeView */ +/* **************************************** */ + QTableView { + color:#fff; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); +} + QHeaderView::section { + color: #00cc00; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); + border-radius: 6px; + padding: 4px; + padding-left: 5px; + padding-right: 0px; +} + QHeaderView::section:hover { + background: #121212; + border: 2px solid #ff0000; + padding: 0px; +} + QHeaderView::up-arrow,QHeaderView::down-arrow { + subcontrol-origin: content; + subcontrol-position: center right; + width: 7px; + height: 7px; + margin-right: 7px; +} + QHeaderView::up-arrow { + image: url(./Transparent-Style/Green/arrow-up.png); +} + QHeaderView::down-arrow { + image: url(./Transparent-Style/Green/arrow-down.png); +} +/* **************************************** */ +/* Hover */ +/* **************************************** */ + QMenu:item:hover,QMenuBar:item:selected,QTreeView#espList::item:hover,QTreeWidget#categoriesList::item:hover,QListView::item:hover,QTreeWidget::item:hover { + color:#fff; + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:#000; +} + QAbstractItemView::item:hover { + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:#000; + padding: 3px; +} + #deactivateESP:hover,#activateESP:hover,QPushButton:hover,QTableView:hover,QHeaderView::selected:hover,QTabBar::tab:!selected:hover,QComboBox:hover { + border: 1px solid #ff0000; + padding: 1px; + text-align: center; +} +/* **************************************** */ +/* Context menus, toolbar dropdowns #QMenu */ +/* **************************************** */ + QMenu { + border-width: 3px; + border-style: solid; +} + QMenu::item { + padding: 6px 20px; +} + QMenu::item:selected { + color:#fff; + background-color: #001a00; + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; +} + QMenu::right-arrow { + image: url(./Transparent-Style/Green/arrow-right.png); + subcontrol-origin: padding; + subcontrol-position: center right; + padding-right: 0.5em; +} +/* ********************* */ +/* LCD Counter */ +/* ********************* */ + QLCDNumber { + Background:#000; + border-color: #00cc00; + border-style: solid; + border-width: 1px; + border-radius: 5px; +} +/* **************************************** */ +/* Launch application Drop down Box */ +/* **************************************** */ + QComboBox { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); + border:1px outset #00cc00; + height: 20px; + width: 15px; + padding: 3px; + padding-left:3px; + border-radius: 5px; +} + QComboBox::drop-down{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + padding:4px; + border-radius: 5px; +} + QComboBox::down-arrow{ + image:url(./Transparent-Style/Green/arrow-down.png); +} + QComboBox::item{ + border:3px; + padding: 3px; +} +/* **************************************************************** */ +/* Action Buttons */ +/* */ +/* Open list, Rextore Backup, Create Backup, RUN, Shortcut Button */ +/* Sort, Load order backup, Load Order Restore */ +/* **************************************************************** */ + QPushButton { + color: #00cc00; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #697670, stop: 1 #484F53); + border-width: 1px; + border-color: 1px outset #00cc00; + border-style: solid; + border-radius: 4px; + padding:5px; + padding-right: 10px; + padding-left: 5px; +} + QDialog QPushButton { + min-width: 3em; + min-height: 1em; + padding-left: 1em; + padding-right: 1em; +} + QPushButton::menu-indicator { + image: url(./Transparent-Style/Green/arrow-down.png); + background: transparent; + subcontrol-origin: padding; + subcontrol-position: center right; + padding-right: 5%; +} + QPushButton:open { + background-color: rgb(0, 204, 0, 100); +} + QPushButton:!enabled { + border: 1px grey; + color: grey; + background: black; +} + QPushButton#displayCategoriesBtn { + min-width: 20px; +} +/* **************************************** */ +/* Scroll bar */ +/* **************************************** */ + QScrollBar { + height:20px; + width:20px; + background-color:transparent; +} + QScrollBar::handle { + border: 1px solid #aaa; + border-radius: 4px; +} + QScrollBar::handle:vertical { + margin: 1px 4px; + min-height:20px; + min-width: 10px; + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #33ff33, stop:1 #000000); +} + QScrollBar::handle:vertical:hover { + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #000000, stop:1 #33ff33); +} + QScrollBar::handle:horizontal { + min-height:10px; + min-width:20px; + margin:4px 1px; + background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #33ff33, stop:1 #000000); +} + QScrollBar::handle:horizontal:hover { + background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #000000, stop:1 #33ff33); +} + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical,QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background-color: transparent; +} + QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical,QScrollBar::add-line:horizontal,QScrollBar::sub-line:horizontal { + height:0; + width:0; +} +/* ******************************************************** */ +/* Main Tree View Inherated from QAbstractItemView */ +/* ******************************************************** */ + QTreeView { +} + QTreeView:item { + padding:4px; +} + QTreeView:item:selected { + color: #001a00; + border: none; + outline: none; + border-radius: 5px; + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color: rgb(0, 204, 0, 100); +} + QTreeView::branch:has-siblings:!adjoins-item { + background:url(Vault-101/Vault-101-item-line-v.png) center center no-repeat; +} + QTreeView::branch:has-siblings { + background:url(Vault-101/Vault-101-itemB-line.png) center center no-repeat; +} + QTreeView::branch:!has-children:!has-siblings:adjoins-item { + background:url(Vault-101/Vault-101-itemB-end.png) center center no-repeat; +} + QTreeView::branch:closed:has-children:has-siblings { + background:url(Vault-101/Vault-101-itemB-close.png) center center no-repeat; +} + QTreeView::branch:closed:has-children:!has-siblings { + background:url(Vault-101/Vault-101-itemB-close-last.png) center center no-repeat; +} + QTreeView::branch:open:has-children:has-siblings { + background:url(Vault-101/Vault-101-itemB-open.png) center center no-repeat; +} + QTreeView::branch:open:has-children:!has-siblings { + background:url(Vault-101/Vault-101-itemB-open-last.png) center center no-repeat; +} +/* **************************************************************** */ +/* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */ +/* common */ +/* **************************************************************** */ + QGroupBox::indicator,QTreeView::indicator,QCheckBox::indicator { + outline: none; + border: none; + width: 20px; + height: 20px; +} + QGroupBox::indicator:checked,QGroupBox::indicator:indeterminate,QTreeView::indicator:checked,QTreeView::indicator:indeterminate,QCheckBox::indicator:checked,QCheckBox::indicator:indeterminate { + image: url(./Transparent-Style/Green/checkbox-checked.png); +} + QGroupBox::indicator:checked:hover,QGroupBox::indicator:indeterminate:hover,QTreeView::indicator:checked:hover,QTreeView::indicator:indeterminate:hover,QCheckBox::indicator:checked:hover,QCheckBox::indicator:indeterminate:hover { + image: url(./Transparent-Style/Green/checkbox-hover.png); +} + QGroupBox::indicator:checked:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:checked:disabled,QTreeView::indicator:indeterminate:disabled,QCheckBox::indicator:checked:disabled,QCheckBox::indicator:indeterminate:disabled { + image: url(./Transparent-Style/Green/checkbox-checked-disabled.png); +} + QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QCheckBox::indicator:unchecked { + image: url(./Transparent-Style/Green/checkbox.png); +} + QGroupBox::indicator:unchecked:hover,QTreeView::indicator:unchecked:hover,QCheckBox::indicator:unchecked:hover { + image: url(./Transparent-Style/Green/checkbox-checked-hover.png); +} + QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QCheckBox::indicator:unchecked:disabled { + image: url(./Transparent-Style/Green/checkbox-checked-disabled.png); +} + QTreeWidget#categoriesList::item:has-children { + background-image: url(./Transparent-Style/Green/arrow-right.png); +} + QTreeWidget#categoriesList::item:has-children:open { + background-image: url(./Transparent-Style/Green/branch-opened.png); +} +/* ******************************** */ +/* Special styles */ +/* increase categories tab width */ +/* ******************************** */ + QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item { + padding: .3em 0; +} + QTreeWidget#categoriesGroup,QTreeWidget#categoriesList { + min-width: 200px; +} + QTreeWidget#categoriesList::item { + background-position: center left; + background-repeat: no-repeat; + padding: 0.35em 4px; +} +/* ********************* */ +/* Display Radio Button */ +/* ********************* */ + QRadioButton::indicator { + width: 16px; + height: 16px; +} + QRadioButton::indicator::checked { + image: url(./Transparent-Style/Green/radio-checked.png); +} + QRadioButton::indicator::unchecked { + image: url(./Transparent-Style/Green/radio.png); +} + QRadioButton::indicator::unchecked:hover { + image: url(./Transparent-Style/Green/radio-hover.png); +} +/* **************************************** */ +/* Spinners #QSpinBox, #QDoubleSpinBox */ +/* **************************************** */ + QAbstractSpinBox { + min-height: 24px; +} + QAbstractSpinBox::up-button,QAbstractSpinBox::down-button { + border-style: solid; + border-width: 1px; + subcontrol-origin: padding; +} + QAbstractSpinBox::up-button:hover,QAbstractSpinBox::down-button:hover { + background-color: #141414; +} + QAbstractSpinBox::up-button { + subcontrol-position: top right; +} + QAbstractSpinBox::down-button { + subcontrol-position: bottom right; +} + QAbstractSpinBox::up-arrow { + image: url(./Transparent-Style/Green/arrow-up.png); +} + QAbstractSpinBox::down-arrow { + image: url(./Transparent-Style/Green/arrow-down.png); +} +/* **************************************** */ +/* Tooltips #QToolTip, #SaveGameInfoWidget */ +/* **************************************** */ + QToolTip { + background-color:transparent; + color:#C0C0C0; + padding:0px; + border-width:7px; + border-style:solid; + border-color:transparent; + border-image:url(./Transparent-Style/Green/border-image.png) 27 repeat repeat; +} + SaveGameInfoWidget { + background-color:#121212; + color:#C0C0C0; +} +/* **************************** */ +/* Handles Web, Nexus info tab */ +/* **************************** */ + QWebView { + background-color: Black; +} + QLineEdit { + font-family: Source Sans Pro; + background:#000; + border-width: 1px; + border-radius:5px; + margin: 0px; + padding-left:2px; + selection-background-color: rgb(0, 204, 0, 10); +} +/* Font size */ + QLabel, QMenu, QPushButton, QTabBar, QTextEdit, QLineEdit, QWebView, QComboBox, QComboBox:editable, QAbstractSpinBox, QGroupBox, QCheckBox, QRadioButton { + color: #cccccc; + font-family: Source Sans Pro; + font-size: 14px; +} + QAbstractItemView { + color: #cccccc; + font-family: Source Sans Pro; + font-size: 14px; +} diff --git a/src/stylesheets/Transparent-Style-BOS.qss b/src/stylesheets/Transparent-Style-BOS.qss index cda40e9f..2c1eb3de 100644 --- a/src/stylesheets/Transparent-Style-BOS.qss +++ b/src/stylesheets/Transparent-Style-BOS.qss @@ -1 +1,742 @@ -#centralWidget { border-image: url(./Transparent-Style/Fallout-BOS.png) 0 0 0 0 stretch stretch; } QWidget#TextViewer{ } QWidget{ color: #fff; background-color: rgba(45,45,48,30); alternate-background-color: rgba(38,38,38,30); } QWidget:disabled{ } QAbstractItemView{ color: #dcdcdc; background-color: rgb(45,45,48,30); alternate-background-color: rgb(38,38,38,50); border: 1px solid #3F3F46; /* Grey - Blue*/ border-style:outset; border-radius: 3px; selection-color: #3F3F46; /* Grey - Blue*/ outline: none; padding-left:0; } #problemsWidget,#TransferSavesDialog,#SettingsDialog,#ProblemsDialog,#ModInfoDialog,#TextViewer,#PyCfgDialog,#InstallDialog { border-image:url(./Transparent-Style/Vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #toolBarArea{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #EditExecutablesDialog{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #SimpleInstallDialog,#LockedDialog,QMenu{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #nameLabel{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:rgba(154,154,0,0.3); color:#fff; } #ProfilesDialog,#FomodInstallerDialog{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #filesView{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } /* ******************************************** */ /* Main Navigation Button Bar at top of window */ /* ******************************************** */ QToolBar{ /*border: none;*/ } QToolBar::separator{ /*image: url(./Transparent-Style/Vault-101-vault-logo.png);*/ } QToolButton{ border:2px solid None; border-radius: 6px; margin: 3px; padding-left: 8px; padding-right: 8px; padding-top: 0px; padding-bottom: 2px; } QToolButton:hover{ border-radius: 3px; border: 0px; padding: 0px; background: trANSPARENT; border: 0px ridge #9A9A00; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #9A9A00, stop: 1 #484F53); } QToolButton:pressed{ background: black; } /* **************************************** */ /* Tabs on top of Treeview */ /* **************************************** */ QTabWidget::pane{ border-color:#3F3F46; /* Grey - Blue*/ /* border-top-color:#9A9A00; */ top: 0px; border-style:hidden; border-width:1px; } QTabWidget::tab-bar{ alignment: center; } QTabBar{ color: #cccccc; font-family: Segoe UI; font-size: 16px; text-transform: none; min-height:auto; padding-bottom: 5px; } QTabBar::tab{ border: none; border-bottom-style: none; background-color: rgba(154,154,0,0.3); padding-left: 9px; padding-right: 9px; padding-top: 3px; padding-bottom: 3px; margin-right: 2px; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; } QTabBar::tab:disabled{ color: #404040; background-color: #000; margin-top: 0px; } QTabBar::tab:selected{ color: #dcdcdc; background-color: #9A9A00; /*Yellow opaque*/ } QTabBar::tab:!selected:hover{ border-radius: 3px; border-top:3px double #9A9A00; border-bottom:3px double #9A9A00; padding: 1px; text-align: center; background-color: #9A9A00; color:#fff; Height: 20%; } /* QTabBar::tab:!selected{ color: #808080; background-color: #404040; margin-top: 0px; } */ #deactivateESP,#activateESP{ border:#b30000; background:#333337; } /* **************************************** */ /* on top of Treeview */ /* **************************************** */ QTabBar QToolButton{ border-radius: 0px; border: 0px; padding: 0px; background: transparent; } QToolButton::menu-indicator:pressed,QToolButton::menu-indicator:open{ image: url(./Transparent-Style/arrow-up.png); } QToolButton::menu-indicator:!pressed,QToolButton::menu-indicator:!open{ image: url(./Transparent-Style/arrow-down.png); } QTabBar QToolButton::right-arrow{ image: url(./Transparent-Style/narrow-arrow-right.png); } QTabBar QToolButton::left-arrow{ image: url(./Transparent-Style/narrow-arrow-left.png); } /* **************************************** */ /* Column names of TreeView */ /* **************************************** */ QTableView{ gridline-color:#3F3F46; selection-background-color:#9A9A00; selection-color:#F1F1F1; /* White */ text-align: center; min-height:24px; } QHeaderView{ min-height:24px; /*Top Bar on menus*/ padding: 3px; } QHeaderView::section{ /* color: #cc3333; */ background-color: #252526; border-radius: 0px; padding: 0px; padding-left: 5px; } QHeaderView::section:hover{ background: rgba(154,154,0,0.3); border: 1px solid #9a9a00; padding: 0px; padding-left: 5px; } QHeaderView::up-arrow,QHeaderView::down-arrow{ min-height:Auto; min-width:Auto; } QHeaderView::up-arrow{ image: url(./Transparent-Style/arrow-up.png); min-height:25px; min-width:15px; padding-right: 0px; }QHeaderView::down-arrow{ image: url(./Transparent-Style/arrow-down.png); } /* **************************************** */ /* Hover */ /* **************************************** */ QMenu:item:hover,QTreeView#espList::item:hover,QTreeWidget#categoriesList::item:hover,QListView::item:hover,QTreeWidget::item:hover{ color:#fff; border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:rgba(154,154,0,0.3); } QAbstractItemView::item:hover{ border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:rgba(154,154,0,0.3); padding: 3px; } /*#DownloadListWidget{ }*/ #DownloadListWidget{ outline: 1px solid #e8e8e8; border: 0px solid #9a9a00; } #deactivateESP:hover,#activateESP:hover,QTableView:hover,QHeaderView::selected:hover{ border: 1px solid #9a9a00; padding: 1px; text-align: center; color:#fff; } /* **************************************** */ /* Context menus, toolbar dropdowns #QMenu */ /* **************************************** */ QMenu{ border-width: 3px; border-style: solid; }QMenu::item{ padding: 6px 20px; }QMenu::item:selected{ color:#fff; background-color: #3F3F46; /* Grey - Blue*/ border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QMenu::right-arrow{ image: url(./Transparent-Style/arrow-right.png); /* right click main menu top option*/ subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; border:0px outset #3F3F46; /* Grey - Blue*/ } /* ********************* */ /* LCD Counter */ /* ********************* */ QLCDNumber{ color: #9A9A00; Background: #000; border-color: #3F3F46; /* Grey - Blue*/ border-style: solid; border-width: 1px; border-radius: 5px; } /* **************************************** */ /* Launch application Drop down Box */ /* **************************************** */ QComboBox{ background-color: #333337; padding: 3px 5px 5px 5px; height: 26px; border-radius: 5px; border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QComboBox:on{ padding: 3px 5px 5px 5px; color: white; background-color: #3F3F46; /* Grey - Blue*/ } QComboBox:hover{ border-radius: 3px; border-top:3px double #9A9A00; border-bottom:3px double #9A9A00; /* padding: 0px; */ } QComboBox::drop-down{ /* Down arrow in combo box */ outline: none; border: none; border-width: 1px; } QComboBox::menu-indicator{ image: url(./Transparent-Style/arrow-down.png); subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; border:1px outset #3F3F46; /* Grey - Blue*/ } /* ************************* */ /* QComboBox::down-arrow:on, */ /* ************************* */ QComboBox::down-arrow:hover,QComboBox::down-arrow:focus { image: url(./Transparent-Style/arrow-down-hover.png); } QComboBox::up-arrow:hover,QComboBox::up-arrow:focus { image: url(./Transparent-Style/arrow-up-hover.png); } QComboBox::down-arrow{ image:url(./Transparent-Style/arrow-down.png); subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; } QComboBox::down-arrow:on{ image:url(./Transparent-Style/arrow-up.png); } QComboBox:editable { background: black; /* color: pink; */ } QComboBox::item{ } QComboBox QAbstractItemView{ selection-color:#fff; outline: 1px solid #9a9a00; selection-background-color:#3F3F46; /* Grey - Blue*/ border-top:1px solid #9a9a00; border-bottom:1px solid #9a9a00; padding: 5px; } /* **************************************************************** */ /* Action Buttons */ /* */ /* Open list, Restore Backup, Create Backup, RUN, Shortcut Button */ /* Sort, Load order backup, Load Order Restore */ /* **************************************************************** */ QPushButton{ background-color: #333337; min-height:26px; padding:1px 5%; text-align: center; border-radius: 5px; border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QPushButton::menu-indicator{ image: url(./Transparent-Style/arrow-down.png); subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; border:1px hidden #3F3F46; /* Grey - Blue*/ } QPushButton::menu-indicator:Hover{ image: url(./Transparent-Style/arrow-down-hover.png); } QPushButton:focus{ } QPushButton:on,QPushButton:checked,QPushButton:pressed,QPushButton:checked:hover{ background: #3F3F46; /* Grey - Blue*/ border-radius: 1px; border: 1px solid #9a9a00; } QPushButton:hover{ border-radius: 3px; border-top:3px double #9A9A00; border-bottom:3px double #9A9A00; } QDialog QPushButton,QSlider::handle:horizontal,QSlider::handle:vertical{ color:#fff; border-color:#707070; border-style:solid; border-width:1px; } QPushButton:disabled{ } QPushButton:open{ } QPushButton:!enabled{ } /* **************************************** */ /* Filter Button */ /* **************************************** */ QPushButton#displayCategoriesBtn{ min-width: 20px; background:transparent; color: transparent; border: 1px hidden #9a9a00; } QPushButton#displayCategoriesBtn:checked{ image: url(./Transparent-Style/arrow-left.png); } QPushButton#displayCategoriesBtn:!checked{ image: url(./Transparent-Style/arrow-right.png); } QPushButton#displayCategoriesBtn:checked:hover{ image: url(./Transparent-Style/arrow-left-hover.png); } QPushButton#displayCategoriesBtn:!checked:hover{ image: url(./Transparent-Style/arrow-right-hover.png); } /* **************************************** */ /* Scroll bar */ /* **************************************** */ QScrollBar{ height:20px; width:20px; background-color:transparent; } QScrollBar::handle{ border: 1px solid #aaa; border-radius: 4px; } QScrollBar::handle:vertical{ margin: 1px 4px; min-height:20px; min-width: 10px; background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #9A9A00, stop:1 #000000); } QScrollBar::handle:vertical:hover{ background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #000000, stop:1 #9A9A00); } QScrollBar::handle:horizontal{ min-height:10px;min-width:20px;margin:4px 1px; background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #9A9A00, stop:1 #000000); } QScrollBar::handle:horizontal:hover{ background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #000000, stop:1 #9A9A00); } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical,QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal{ background-color: transparent; } QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical,QScrollBar::add-line:horizontal,QScrollBar::sub-line:horizontal{ height:0; width:0; } /* ******************************************************** */ /* Main Tree View Inherited from QAbstractItemView */ /* ******************************************************** */ QTreeView { } QTreeView:item{ padding: 3px; font-size: 16px; height: 18px; /*Bottom text Box*/ } QTreeView:item:selected{ color:#fff; padding: 0px; background-color: #3F3F46; /* Grey - Blue*/ border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QTreeView:item QLineEdit{ outline: none; border: none; padding: 0px; margin: 0px; } QTreeView::branch:has-siblings:!adjoins-item{ background:url(Transparent-Style/vault-101-item-line-v.png) center center no-repeat; } QTreeView::branch:has-siblings{ background:url(Transparent-Style/vault-101-itemB-line.png) center center no-repeat; } QTreeView::branch:!has-children:!has-siblings:adjoins-item{ background:url(Transparent-Style/vault-101-itemB-end.png) center center no-repeat; } QTreeView::branch:closed:has-children:has-siblings{ background:url(Transparent-Style/vault-101-itemB-close.png) center center no-repeat; } QTreeView::branch:closed:has-children:!has-siblings{ background:url(Transparent-Style/vault-101-itemB-close-last.png) center center no-repeat; } QTreeView::branch:open:has-children:has-siblings{ background:url(Transparent-Style/vault-101-itemB-open.png) center center no-repeat; } QTreeView::branch:open:has-children:!has-siblings{ background:url(Transparent-Style/vault-101-itemB-open-last.png) center center no-repeat; } /* **************************************************************** */ /* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */ /* common */ /* **************************************************************** */ QTreeView::indicator,QCheckBox::indicator{ outline: none; border: none; padding: 0px; width: 24px; height: 24px; } QGroupBox::indicator:checked,QGroupBox::indicator:indeterminate,QTreeView::indicator:checked,QTreeView::indicator:indeterminate,QCheckBox::indicator:checked,QCheckBox::indicator:indeterminate{ image: url(./Transparent-Style/checkbox-checked.png); } QGroupBox::indicator:checked:hover,QGroupBox::indicator:indeterminate:hover,QTreeView::indicator:checked:hover,QTreeView::indicator:indeterminate:hover,QCheckBox::indicator:checked:hover,QCheckBox::indicator:indeterminate:hover{ image: url(./Transparent-Style/checkbox-hover.png); } QGroupBox::indicator:checked:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:checked:disabled,QTreeView::indicator:indeterminate:disabled,QCheckBox::indicator:checked:disabled,QCheckBox::indicator:indeterminate:disabled{ image: url(./Transparent-Style/checkbox-checked-disabled.png); } QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QCheckBox::indicator:unchecked{ image: url(./Transparent-Style/checkbox.png); } QGroupBox::indicator:unchecked:hover,QTreeView::indicator:unchecked:hover,QCheckBox::indicator:unchecked:hover{ image: url(./Transparent-Style/checkbox-checked-hover.png); } QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QCheckBox::indicator:unchecked:disabled{ image: url(./Transparent-Style/checkbox-checked-disabled.png); } QTreeWidget#categoriesList::item:has-children{ background-image: url(./Transparent-Style/arrow-right-small.png); } QTreeWidget#categoriesList::item:has-children:open{ background-image: url(./Transparent-Style/branch-opened.png); } /* ******************************** */ /* Special styles */ /* increase categories tab width */ /* ******************************** */ QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ padding: .3em 0em; } QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ padding: .3em 0em; } QTreeWidget#categoriesGroup,QTreeWidget#categoriesList{ min-width: 200px; } QTreeWidget#categoriesList::item { background-position: center left; background-repeat: no-repeat; padding: 0.35em 4px; } /* ********************* */ /* Display Radio Button */ /* ********************* */ QRadioButton::indicator{ width: 16px; height: 16px; } QRadioButton::indicator::checked{ image: url(./Transparent-Style/radio-BOS-checked.png); } QRadioButton::indicator::unchecked{ image: url(./Transparent-Style/radio-BOS.png); } QRadioButton::indicator::unchecked:hover{ image: url(./Transparent-Style/radio-BOS-hover.png); } /* **************************************** */ /* Spinners #QSpinBox, #QDoubleSpinBox */ /* **************************************** */ QAbstractSpinBox{ padding: 5px; background-color: transparent; color: #eff0f1; border-radius: 2px; min-width: Auto; } QAbstractSpinBox::up-button,QAbstractSpinBox::down-button{ border-style: solid; border-width: 1px; subcontrol-origin: border; } QAbstractSpinBox::up-button:hover,QAbstractSpinBox::down-button:hover{ background-color: transparent; } QAbstractSpinBox::up-button{ background-color: transparent; subcontrol-origin: border; subcontrol-position: center right; padding-right: 5px; /* subcontrol-position: top right; */ } QAbstractSpinBox::down-button{ background-color: transparent; subcontrol-origin: border; subcontrol-position: center left; padding-left: 5px; /* subcontrol-position: bottom right; */ } QAbstractSpinBox::up-arrow{ border-image: url(./Transparent-Style/narrow-arrow-right.png); }QAbstractSpinBox::down-arrow{ border-image: url(./Transparent-Style/narrow-arrow-left.png); }/* QTextEdit,QWebView,QLineEdit,QComboBox{ border-color:#3F3F46; /* Grey - Blue*/ /*}*/ /* **************************************** */ /* Tooltips #QToolTip, #SaveGameInfoWidget */ /* **************************************** */ QToolTip{ background-color:#121212; color:#C0C0C0; padding:0px; border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } SaveGameInfoWidget{ font-size: 16px; background-color:#121212; color:#C0C0C0; padding:0px; border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } /* **************************** */ /* Handles Web, Nexus info tab */ /* **************************** */ QWebView{ background-color: Black; } QLineEdit{ font-family: Segoe UI; min-height: 20px; font-size: 16px; background-color:#3F3F46; selection-background-color: rgba(154,154,0,0.6);border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } QTextEdit{ color: #cccccc; font-family: Segoe UI; font-size: 16px; /* background-color:#000; */ selection-background-color: rgba(154,154,0,0.6); border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } /* Font size */ QLabel,QMenu,QPushButton,QAbstractSpinBox,QGroupBox,QCheckBox,QRadioButton{ color: #cccccc; font-family: Segoe UI; font-size: 16px; /* was 14px Profile font size */ } QAbstractItemView{ color: #cccccc; font-family: Segoe UI; /* font-family: Source Sans Pro; */ font-size: 16px; /* Body font size*/ } QRadioButton{ color: #cccccc; font-family: Segoe UI; /* font-family: Source Sans Pro; */ font-size: 16px; } QWebView,QComboBox,QComboBox:editable{ font-family:Segoe UI; font-style: normal; font-weight: normal; font-size: 16px; background-color:#000; /* border-color:#3F3F46; /* Grey - Blue*/ color: #cccccc; font-family: Arial; font-size: 16px; /* Filter Name*/ } QHeaderView::section{ /* font-family: Source Sans Pro; */ font-family: Segoe UI; font-size: 16px; /*Top Bar on menus font*/ } QListView::item{ color:#F1F1F1; /* White */ } QGroupBox { } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top center; } QGroupBox::indicator { subcontrol-origin: margin; subcontrol-position: top center; } QLabel { color: #cccccc; /* color: #F1F1F1; /* White */ /* border: none;outline: none; border-radius: 5px; border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00;*/ } \ No newline at end of file +#centralWidget { + border-image: url(./Transparent-Style/Fallout-BOS.png) 0 0 0 0 stretch stretch; +} + QWidget#TextViewer{ +} + QWidget{ + color: #fff; + background-color: rgba(45,45,48,30); + alternate-background-color: rgba(38,38,38,30); +} + QWidget:disabled{ +} + QAbstractItemView{ + color: #dcdcdc; + background-color: rgb(45,45,48,30); + alternate-background-color: rgb(38,38,38,50); + border: 1px solid #3F3F46; + /* Grey - Blue*/ + border-style:outset; + border-radius: 3px; + selection-color: #3F3F46; + /* Grey - Blue*/ + outline: none; + padding-left:0; +} + #problemsWidget,#TransferSavesDialog,#SettingsDialog,#ProblemsDialog,#ModInfoDialog,#TextViewer,#PyCfgDialog,#InstallDialog { + border-image:url(./Transparent-Style/Vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #toolBarArea{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #EditExecutablesDialog{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #SimpleInstallDialog,#LockedDialog,QMenu{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #nameLabel{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:rgba(154,154,0,0.3); + color:#fff; +} + #ProfilesDialog,#FomodInstallerDialog{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #filesView{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} +/* ******************************************** */ +/* Main Navigation Button Bar at top of window */ +/* ******************************************** */ + QToolBar{ + /*border: none; + */ +} + QToolBar::separator{ + /*image: url(./Transparent-Style/Vault-101-vault-logo.png); + */ +} + QToolButton{ + border:2px solid None; + border-radius: 6px; + margin: 3px; + padding-left: 8px; + padding-right: 8px; + padding-top: 0px; + padding-bottom: 2px; +} + QToolButton:hover{ + border-radius: 3px; + border: 0px; + padding: 0px; + background: trANSPARENT; + border: 0px ridge #9A9A00; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #9A9A00, stop: 1 #484F53); +} + QToolButton:pressed{ + background: black; +} +/* **************************************** */ +/* Tabs on top of Treeview */ +/* **************************************** */ + QTabWidget::pane{ + border-color:#3F3F46; + /* Grey - Blue*/ + /* border-top-color:#9A9A00; + */ + top: 0px; + border-style:hidden; + border-width:1px; +} + QTabWidget::tab-bar{ + alignment: center; +} + QTabBar{ + color: #cccccc; + font-family: Segoe UI; + font-size: 16px; + text-transform: none; + min-height:auto; + padding-bottom: 5px; +} + QTabBar::tab{ + border: none; + border-bottom-style: none; + background-color: rgba(154,154,0,0.3); + padding-left: 9px; + padding-right: 9px; + padding-top: 3px; + padding-bottom: 3px; + margin-right: 2px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + QTabBar::tab:disabled{ + color: #404040; + background-color: #000; + margin-top: 0px; +} + QTabBar::tab:selected{ + color: #dcdcdc; + background-color: #9A9A00; + /*Yellow opaque*/ +} + QTabBar::tab:!selected:hover{ + border-radius: 3px; + border-top:3px double #9A9A00; + border-bottom:3px double #9A9A00; + padding: 1px; + text-align: center; + background-color: #9A9A00; + color:#fff; + Height: 20%; +} +/* QTabBar::tab:!selected{ + color: #808080; + background-color: #404040; + margin-top: 0px; +} + */ + #deactivateESP,#activateESP{ + border:#b30000; + background:#333337; +} +/* **************************************** */ +/* on top of Treeview */ +/* **************************************** */ + QTabBar QToolButton{ + border-radius: 0px; + border: 0px; + padding: 0px; + background: transparent; +} + QToolButton::menu-indicator:pressed,QToolButton::menu-indicator:open{ + image: url(./Transparent-Style/arrow-up.png); +} + QToolButton::menu-indicator:!pressed,QToolButton::menu-indicator:!open{ + image: url(./Transparent-Style/arrow-down.png); +} + QTabBar QToolButton::right-arrow{ + image: url(./Transparent-Style/narrow-arrow-right.png); +} + QTabBar QToolButton::left-arrow{ + image: url(./Transparent-Style/narrow-arrow-left.png); +} +/* **************************************** */ +/* Column names of TreeView */ +/* **************************************** */ + QTableView{ + gridline-color:#3F3F46; + selection-background-color:#9A9A00; + selection-color:#F1F1F1; + /* White */ + text-align: center; + min-height:24px; +} + QHeaderView{ + min-height:24px; + /*Top Bar on menus*/ + padding: 3px; +} + QHeaderView::section{ + /* color: #cc3333; + */ + background-color: #252526; + border-radius: 0px; + padding: 0px; + padding-left: 5px; +} + QHeaderView::section:hover{ + background: rgba(154,154,0,0.3); + border: 1px solid #9a9a00; + padding: 0px; + padding-left: 5px; +} + QHeaderView::up-arrow,QHeaderView::down-arrow{ + min-height:Auto; + min-width:Auto; +} + QHeaderView::up-arrow{ + image: url(./Transparent-Style/arrow-up.png); + min-height:25px; + min-width:15px; + padding-right: 0px; +} +QHeaderView::down-arrow{ + image: url(./Transparent-Style/arrow-down.png); +} +/* **************************************** */ +/* Hover */ +/* **************************************** */ + QMenu:item:hover,QMenuBar::item:selected,QTreeView#espList::item:hover,QTreeWidget#categoriesList::item:hover,QListView::item:hover,QTreeWidget::item:hover{ + color:#fff; + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:rgba(154,154,0,0.3); +} + QAbstractItemView::item:hover{ + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:rgba(154,154,0,0.3); + padding: 3px; +} +/*#DownloadListWidget{ +} +*/ + #DownloadListWidget{ + outline: 1px solid #e8e8e8; + border: 0px solid #9a9a00; +} + #deactivateESP:hover,#activateESP:hover,QTableView:hover,QHeaderView::selected:hover{ + border: 1px solid #9a9a00; + padding: 1px; + text-align: center; + color:#fff; +} +/* **************************************** */ +/* Context menus, toolbar dropdowns #QMenu */ +/* **************************************** */ + QMenu{ + border-width: 3px; + border-style: solid; +} +QMenu::item{ + padding: 6px 20px; +} +QMenu::item:selected{ + color:#fff; + background-color: #3F3F46; + /* Grey - Blue*/ + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QMenu::right-arrow{ + image: url(./Transparent-Style/arrow-right.png); + /* right click main menu top option*/ + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border:0px outset #3F3F46; + /* Grey - Blue*/ +} +/* ********************* */ +/* LCD Counter */ +/* ********************* */ + QLCDNumber{ + color: #9A9A00; + Background: #000; + border-color: #3F3F46; + /* Grey - Blue*/ + border-style: solid; + border-width: 1px; + border-radius: 5px; +} +/* **************************************** */ +/* Launch application Drop down Box */ +/* **************************************** */ + QComboBox{ + background-color: #333337; + padding: 3px 5px 5px 5px; + height: 26px; + border-radius: 5px; + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QComboBox:on{ + padding: 3px 5px 5px 5px; + color: white; + background-color: #3F3F46; + /* Grey - Blue*/ +} + QComboBox:hover{ + border-radius: 3px; + border-top:3px double #9A9A00; + border-bottom:3px double #9A9A00; + /* padding: 0px; + */ +} + QComboBox::drop-down{ + /* Down arrow in combo box */ + outline: none; + border: none; + border-width: 1px; +} + QComboBox::menu-indicator{ + image: url(./Transparent-Style/arrow-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border:1px outset #3F3F46; + /* Grey - Blue*/ +} +/* ************************* */ +/* QComboBox::down-arrow:on, */ +/* ************************* */ + QComboBox::down-arrow:hover,QComboBox::down-arrow:focus { + image: url(./Transparent-Style/arrow-down-hover.png); +} + QComboBox::up-arrow:hover,QComboBox::up-arrow:focus { + image: url(./Transparent-Style/arrow-up-hover.png); +} + QComboBox::down-arrow{ + image:url(./Transparent-Style/arrow-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; +} + QComboBox::down-arrow:on{ + image:url(./Transparent-Style/arrow-up.png); +} + QComboBox:editable { + background: black; + /* color: pink; + */ +} + QComboBox::item{ +} + QComboBox QAbstractItemView{ + selection-color:#fff; + outline: 1px solid #9a9a00; + selection-background-color:#3F3F46; + /* Grey - Blue*/ + border-top:1px solid #9a9a00; + border-bottom:1px solid #9a9a00; + padding: 5px; +} +/* **************************************************************** */ +/* Action Buttons */ +/* */ +/* Open list, Restore Backup, Create Backup, RUN, Shortcut Button */ +/* Sort, Load order backup, Load Order Restore */ +/* **************************************************************** */ + QPushButton{ + background-color: #333337; + min-height:26px; + padding:1px 5%; + text-align: center; + border-radius: 5px; + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QPushButton::menu-indicator{ + image: url(./Transparent-Style/arrow-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border:1px hidden #3F3F46; + /* Grey - Blue*/ +} + QPushButton::menu-indicator:Hover{ + image: url(./Transparent-Style/arrow-down-hover.png); +} + QPushButton:focus{ +} + QPushButton:on,QPushButton:checked,QPushButton:pressed,QPushButton:checked:hover{ + background: #3F3F46; + /* Grey - Blue*/ + border-radius: 1px; + border: 1px solid #9a9a00; +} + QPushButton:hover{ + border-radius: 3px; + border-top:3px double #9A9A00; + border-bottom:3px double #9A9A00; +} + QDialog QPushButton,QSlider::handle:horizontal,QSlider::handle:vertical{ + color:#fff; + border-color:#707070; + border-style:solid; + border-width:1px; +} + QPushButton:disabled{ +} + QPushButton:open{ +} + QPushButton:!enabled{ +} +/* **************************************** */ +/* Filter Button */ +/* **************************************** */ + QPushButton#displayCategoriesBtn{ + min-width: 20px; + background:transparent; + color: transparent; + border: 1px hidden #9a9a00; +} + QPushButton#displayCategoriesBtn:checked{ + image: url(./Transparent-Style/arrow-left.png); +} + QPushButton#displayCategoriesBtn:!checked{ + image: url(./Transparent-Style/arrow-right.png); +} + QPushButton#displayCategoriesBtn:checked:hover{ + image: url(./Transparent-Style/arrow-left-hover.png); +} + QPushButton#displayCategoriesBtn:!checked:hover{ + image: url(./Transparent-Style/arrow-right-hover.png); +} +/* **************************************** */ +/* Scroll bar */ +/* **************************************** */ + QScrollBar{ + height:20px; + width:20px; + background-color:transparent; +} + QScrollBar::handle{ + border: 1px solid #aaa; + border-radius: 4px; +} + QScrollBar::handle:vertical{ + margin: 1px 4px; + min-height:20px; + min-width: 10px; + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #9A9A00, stop:1 #000000); +} + QScrollBar::handle:vertical:hover{ + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #000000, stop:1 #9A9A00); +} + QScrollBar::handle:horizontal{ + min-height:10px; + min-width:20px; + margin:4px 1px; + background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #9A9A00, stop:1 #000000); +} + QScrollBar::handle:horizontal:hover{ + background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #000000, stop:1 #9A9A00); +} + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical,QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal{ + background-color: transparent; +} + QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical,QScrollBar::add-line:horizontal,QScrollBar::sub-line:horizontal{ + height:0; + width:0; +} +/* ******************************************************** */ +/* Main Tree View Inherited from QAbstractItemView */ +/* ******************************************************** */ + QTreeView { +} + QTreeView:item{ + padding: 3px; + font-size: 16px; + height: 18px; + /*Bottom text Box*/ +} + QTreeView:item:selected{ + color:#fff; + padding: 0px; + background-color: #3F3F46; + /* Grey - Blue*/ + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QTreeView:item QLineEdit{ + outline: none; + border: none; + padding: 0px; + margin: 0px; +} + QTreeView::branch:has-siblings:!adjoins-item{ + background:url(Transparent-Style/vault-101-item-line-v.png) center center no-repeat; +} + QTreeView::branch:has-siblings{ + background:url(Transparent-Style/vault-101-itemB-line.png) center center no-repeat; +} + QTreeView::branch:!has-children:!has-siblings:adjoins-item{ + background:url(Transparent-Style/vault-101-itemB-end.png) center center no-repeat; +} + QTreeView::branch:closed:has-children:has-siblings{ + background:url(Transparent-Style/vault-101-itemB-close.png) center center no-repeat; +} + QTreeView::branch:closed:has-children:!has-siblings{ + background:url(Transparent-Style/vault-101-itemB-close-last.png) center center no-repeat; +} + QTreeView::branch:open:has-children:has-siblings{ + background:url(Transparent-Style/vault-101-itemB-open.png) center center no-repeat; +} + QTreeView::branch:open:has-children:!has-siblings{ + background:url(Transparent-Style/vault-101-itemB-open-last.png) center center no-repeat; +} +/* **************************************************************** */ +/* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */ +/* common */ +/* **************************************************************** */ + QTreeView::indicator,QCheckBox::indicator{ + outline: none; + border: none; + padding: 0px; + width: 24px; + height: 24px; +} + QGroupBox::indicator:checked,QGroupBox::indicator:indeterminate,QTreeView::indicator:checked,QTreeView::indicator:indeterminate,QCheckBox::indicator:checked,QCheckBox::indicator:indeterminate{ + image: url(./Transparent-Style/checkbox-checked.png); +} + QGroupBox::indicator:checked:hover,QGroupBox::indicator:indeterminate:hover,QTreeView::indicator:checked:hover,QTreeView::indicator:indeterminate:hover,QCheckBox::indicator:checked:hover,QCheckBox::indicator:indeterminate:hover{ + image: url(./Transparent-Style/checkbox-hover.png); +} + QGroupBox::indicator:checked:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:checked:disabled,QTreeView::indicator:indeterminate:disabled,QCheckBox::indicator:checked:disabled,QCheckBox::indicator:indeterminate:disabled{ + image: url(./Transparent-Style/checkbox-checked-disabled.png); +} + QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QCheckBox::indicator:unchecked{ + image: url(./Transparent-Style/checkbox.png); +} + QGroupBox::indicator:unchecked:hover,QTreeView::indicator:unchecked:hover,QCheckBox::indicator:unchecked:hover{ + image: url(./Transparent-Style/checkbox-checked-hover.png); +} + QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QCheckBox::indicator:unchecked:disabled{ + image: url(./Transparent-Style/checkbox-checked-disabled.png); +} + QTreeWidget#categoriesList::item:has-children{ + background-image: url(./Transparent-Style/arrow-right-small.png); +} + QTreeWidget#categoriesList::item:has-children:open{ + background-image: url(./Transparent-Style/branch-opened.png); +} +/* ******************************** */ +/* Special styles */ +/* increase categories tab width */ +/* ******************************** */ + QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ + padding: .3em 0em; +} + QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ + padding: .3em 0em; +} + QTreeWidget#categoriesGroup,QTreeWidget#categoriesList{ + min-width: 200px; +} + QTreeWidget#categoriesList::item { + background-position: center left; + background-repeat: no-repeat; + padding: 0.35em 4px; +} +/* ********************* */ +/* Display Radio Button */ +/* ********************* */ + QRadioButton::indicator{ + width: 16px; + height: 16px; +} + QRadioButton::indicator::checked{ + image: url(./Transparent-Style/radio-BOS-checked.png); +} + QRadioButton::indicator::unchecked{ + image: url(./Transparent-Style/radio-BOS.png); +} + QRadioButton::indicator::unchecked:hover{ + image: url(./Transparent-Style/radio-BOS-hover.png); +} +/* **************************************** */ +/* Spinners #QSpinBox, #QDoubleSpinBox */ +/* **************************************** */ + QAbstractSpinBox{ + padding: 5px; + background-color: transparent; + color: #eff0f1; + border-radius: 2px; + min-width: Auto; +} + QAbstractSpinBox::up-button,QAbstractSpinBox::down-button{ + border-style: solid; + border-width: 1px; + subcontrol-origin: border; +} + QAbstractSpinBox::up-button:hover,QAbstractSpinBox::down-button:hover{ + background-color: transparent; +} + QAbstractSpinBox::up-button{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center right; + padding-right: 5px; + /* subcontrol-position: top right; + */ +} + QAbstractSpinBox::down-button{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center left; + padding-left: 5px; + /* subcontrol-position: bottom right; + */ +} + QAbstractSpinBox::up-arrow{ + border-image: url(./Transparent-Style/narrow-arrow-right.png); +} +QAbstractSpinBox::down-arrow{ + border-image: url(./Transparent-Style/narrow-arrow-left.png); +} +/* QTextEdit,QWebView,QLineEdit,QComboBox{ + border-color:#3F3F46; + /* Grey - Blue*/ + /* +} +*/ +/* **************************************** */ +/* Tooltips #QToolTip, #SaveGameInfoWidget */ +/* **************************************** */ + QToolTip{ + background-color:#121212; + color:#C0C0C0; + padding:0px; + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} + SaveGameInfoWidget{ + font-size: 16px; + background-color:#121212; + color:#C0C0C0; + padding:0px; + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} +/* **************************** */ +/* Handles Web, Nexus info tab */ +/* **************************** */ + QWebView{ + background-color: Black; +} + QLineEdit{ + font-family: Segoe UI; + min-height: 20px; + font-size: 16px; + background-color:#3F3F46; + selection-background-color: rgba(154,154,0,0.6); + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} + QTextEdit{ + color: #cccccc; + font-family: Segoe UI; + font-size: 16px; + /* background-color:#000; + */ + selection-background-color: rgba(154,154,0,0.6); + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} +/* Font size */ + QLabel,QMenu,QPushButton,QAbstractSpinBox,QGroupBox,QCheckBox,QRadioButton{ + color: #cccccc; + font-family: Segoe UI; + font-size: 16px; + /* was 14px Profile font size */ +} + QAbstractItemView{ + color: #cccccc; + font-family: Segoe UI; + /* font-family: Source Sans Pro; + */ + font-size: 16px; + /* Body font size*/ +} + QRadioButton{ + color: #cccccc; + font-family: Segoe UI; + /* font-family: Source Sans Pro; + */ + font-size: 16px; +} + QWebView,QComboBox,QComboBox:editable{ + font-family:Segoe UI; + font-style: normal; + font-weight: normal; + font-size: 16px; + background-color:#000; + /* border-color:#3F3F46; + /* Grey - Blue*/ + color: #cccccc; + font-family: Arial; + font-size: 16px; + /* Filter Name*/ +} + QHeaderView::section{ + /* font-family: Source Sans Pro; + */ + font-family: Segoe UI; + font-size: 16px; + /*Top Bar on menus font*/ +} + QListView::item{ + color:#F1F1F1; + /* White */ +} + QGroupBox { +} + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; +} + QGroupBox::indicator { + subcontrol-origin: margin; + subcontrol-position: top center; +} + QLabel { + color: #cccccc; + /* color: #F1F1F1; + /* White */ + /* border: none; + outline: none; + border-radius: 5px; + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; + */ +} diff --git a/src/stylesheets/Transparent-Style-Skyrim.qss b/src/stylesheets/Transparent-Style-Skyrim.qss index b5fd7473..e95dcfc5 100644 --- a/src/stylesheets/Transparent-Style-Skyrim.qss +++ b/src/stylesheets/Transparent-Style-Skyrim.qss @@ -1 +1,742 @@ -#centralWidget { border-image: url(./Transparent-Style/Background-skyrim.png) 0 0 0 0 stretch stretch; } QWidget#TextViewer{ } QWidget{ color: #fff; background-color: rgba(45,45,48,30); alternate-background-color: rgba(38,38,38,30); } QWidget:disabled{ } QAbstractItemView{ color: #dcdcdc; background-color: rgb(45,45,48,30); alternate-background-color: rgb(38,38,38,50); border: 1px solid #3F3F46; /* Grey - Blue*/ border-style:outset; border-radius: 3px; selection-color: #3F3F46; /* Grey - Blue*/ outline: none; padding-left:0; } #problemsWidget,#TransferSavesDialog,#SettingsDialog,#ProblemsDialog,#ModInfoDialog,#TextViewer,#PyCfgDialog,#InstallDialog { border-image:url(./Transparent-Style/Vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #toolBarArea{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #EditExecutablesDialog{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #SimpleInstallDialog,#LockedDialog,QMenu{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #nameLabel{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:rgba(154,154,0,0.3); color:#fff; } #ProfilesDialog,#FomodInstallerDialog{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } #filesView{ border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; } /* ******************************************** */ /* Main Navigation Button Bar at top of window */ /* ******************************************** */ QToolBar{ /*border: none;*/ } QToolBar::separator{ /*image: url(./Transparent-Style/Vault-101-vault-logo.png);*/ } QToolButton{ border:2px solid None; border-radius: 6px; margin: 3px; padding-left: 8px; padding-right: 8px; padding-top: 0px; padding-bottom: 2px; } QToolButton:hover{ border-radius: 3px; border: 0px; padding: 0px; background: trANSPARENT; border: 0px ridge #9A9A00; background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #9A9A00, stop: 1 #484F53); } QToolButton:pressed{ background: black; } /* **************************************** */ /* Tabs on top of Treeview */ /* **************************************** */ QTabWidget::pane{ border-color:#3F3F46; /* Grey - Blue*/ /* border-top-color:#9A9A00; */ top: 0px; border-style:hidden; border-width:1px; } QTabWidget::tab-bar{ alignment: center; } QTabBar{ color: #cccccc; font-family: Segoe UI; font-size: 16px; text-transform: none; min-height:auto; padding-bottom: 5px; } QTabBar::tab{ border: none; border-bottom-style: none; background-color: rgba(154,154,0,0.3); padding-left: 9px; padding-right: 9px; padding-top: 3px; padding-bottom: 3px; margin-right: 2px; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; } QTabBar::tab:disabled{ color: #404040; background-color: #000; margin-top: 0px; } QTabBar::tab:selected{ color: #dcdcdc; background-color: #9A9A00; /*Yellow opaque*/ } QTabBar::tab:!selected:hover{ border-radius: 3px; border-top:3px double #9A9A00; border-bottom:3px double #9A9A00; padding: 1px; text-align: center; background-color: #9A9A00; color:#fff; Height: 20%; } /* QTabBar::tab:!selected{ color: #808080; background-color: #404040; margin-top: 0px; } */ #deactivateESP,#activateESP{ border:#b30000; background:#333337; } /* **************************************** */ /* on top of Treeview */ /* **************************************** */ QTabBar QToolButton{ border-radius: 0px; border: 0px; padding: 0px; background: transparent; } QToolButton::menu-indicator:pressed,QToolButton::menu-indicator:open{ image: url(./Transparent-Style/arrow-up.png); } QToolButton::menu-indicator:!pressed,QToolButton::menu-indicator:!open{ image: url(./Transparent-Style/arrow-down.png); } QTabBar QToolButton::right-arrow{ image: url(./Transparent-Style/narrow-arrow-right.png); } QTabBar QToolButton::left-arrow{ image: url(./Transparent-Style/narrow-arrow-left.png); } /* **************************************** */ /* Column names of TreeView */ /* **************************************** */ QTableView{ gridline-color:#3F3F46; selection-background-color:#9A9A00; selection-color:#F1F1F1; /* White */ text-align: center; min-height:24px; } QHeaderView{ min-height:24px; /*Top Bar on menus*/ padding: 3px; } QHeaderView::section{ /* color: #cc3333; */ background-color: #252526; border-radius: 0px; padding: 0px; padding-left: 5px; } QHeaderView::section:hover{ background: rgba(154,154,0,0.3); border: 1px solid #9a9a00; padding: 0px; padding-left: 5px; } QHeaderView::up-arrow,QHeaderView::down-arrow{ min-height:Auto; min-width:Auto; } QHeaderView::up-arrow{ image: url(./Transparent-Style/arrow-up.png); min-height:25px; min-width:15px; padding-right: 0px; }QHeaderView::down-arrow{ image: url(./Transparent-Style/arrow-down.png); } /* **************************************** */ /* Hover */ /* **************************************** */ QMenu:item:hover,QTreeView#espList::item:hover,QTreeWidget#categoriesList::item:hover,QListView::item:hover,QTreeWidget::item:hover{ color:#fff; border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:rgba(154,154,0,0.3); } QAbstractItemView::item:hover{ border-top:1px solid #b9d7fc; border-bottom:1px solid #b9d7fc; background-color:rgba(154,154,0,0.3); padding: 3px; } /*#DownloadListWidget{ }*/ #DownloadListWidget{ outline: 1px solid #e8e8e8; border: 0px solid #9a9a00; } #deactivateESP:hover,#activateESP:hover,QTableView:hover,QHeaderView::selected:hover{ border: 1px solid #9a9a00; padding: 1px; text-align: center; color:#fff; } /* **************************************** */ /* Context menus, toolbar dropdowns #QMenu */ /* **************************************** */ QMenu{ border-width: 3px; border-style: solid; }QMenu::item{ padding: 6px 20px; }QMenu::item:selected{ color:#fff; background-color: #3F3F46; /* Grey - Blue*/ border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QMenu::right-arrow{ image: url(./Transparent-Style/arrow-right.png); /* right click main menu top option*/ subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; border:0px outset #3F3F46; /* Grey - Blue*/ } /* ********************* */ /* LCD Counter */ /* ********************* */ QLCDNumber{ color: #9A9A00; Background: #000; border-color: #3F3F46; /* Grey - Blue*/ border-style: solid; border-width: 1px; border-radius: 5px; } /* **************************************** */ /* Launch application Drop down Box */ /* **************************************** */ QComboBox{ background-color: #333337; padding: 3px 5px 5px 5px; height: 26px; border-radius: 5px; border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QComboBox:on{ padding: 3px 5px 5px 5px; color: white; background-color: #3F3F46; /* Grey - Blue*/ } QComboBox:hover{ border-radius: 3px; border-top:3px double #9A9A00; border-bottom:3px double #9A9A00; /* padding: 0px; */ } QComboBox::drop-down{ /* Down arrow in combo box */ outline: none; border: none; border-width: 1px; } QComboBox::menu-indicator{ image: url(./Transparent-Style/arrow-down.png); subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; border:1px outset #3F3F46; /* Grey - Blue*/ } /* ************************* */ /* QComboBox::down-arrow:on, */ /* ************************* */ QComboBox::down-arrow:hover,QComboBox::down-arrow:focus { image: url(./Transparent-Style/arrow-down-hover.png); } QComboBox::up-arrow:hover,QComboBox::up-arrow:focus { image: url(./Transparent-Style/arrow-up-hover.png); } QComboBox::down-arrow{ image:url(./Transparent-Style/arrow-down.png); subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; } QComboBox::down-arrow:on{ image:url(./Transparent-Style/arrow-up.png); } QComboBox:editable { background: black; /* color: pink; */ } QComboBox::item{ } QComboBox QAbstractItemView{ selection-color:#fff; outline: 1px solid #9a9a00; selection-background-color:#3F3F46; /* Grey - Blue*/ border-top:1px solid #9a9a00; border-bottom:1px solid #9a9a00; padding: 5px; } /* **************************************************************** */ /* Action Buttons */ /* */ /* Open list, Restore Backup, Create Backup, RUN, Shortcut Button */ /* Sort, Load order backup, Load Order Restore */ /* **************************************************************** */ QPushButton{ background-color: #333337; min-height:26px; padding:1px 5%; text-align: center; border-radius: 5px; border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QPushButton::menu-indicator{ image: url(./Transparent-Style/arrow-down.png); subcontrol-origin: padding; subcontrol-position: center right; width: 18px; height: 18px; border:1px hidden #3F3F46; /* Grey - Blue*/ } QPushButton::menu-indicator:Hover{ image: url(./Transparent-Style/arrow-down-hover.png); } QPushButton:focus{ } QPushButton:on,QPushButton:checked,QPushButton:pressed,QPushButton:checked:hover{ background: #3F3F46; /* Grey - Blue*/ border-radius: 1px; border: 1px solid #9a9a00; } QPushButton:hover{ border-radius: 3px; border-top:3px double #9A9A00; border-bottom:3px double #9A9A00; } QDialog QPushButton,QSlider::handle:horizontal,QSlider::handle:vertical{ color:#fff; border-color:#707070; border-style:solid; border-width:1px; } QPushButton:disabled{ } QPushButton:open{ } QPushButton:!enabled{ } /* **************************************** */ /* Filter Button */ /* **************************************** */ QPushButton#displayCategoriesBtn{ min-width: 20px; background:transparent; color: transparent; border: 1px hidden #9a9a00; } QPushButton#displayCategoriesBtn:checked{ image: url(./Transparent-Style/arrow-left.png); } QPushButton#displayCategoriesBtn:!checked{ image: url(./Transparent-Style/arrow-right.png); } QPushButton#displayCategoriesBtn:checked:hover{ image: url(./Transparent-Style/arrow-left-hover.png); } QPushButton#displayCategoriesBtn:!checked:hover{ image: url(./Transparent-Style/arrow-right-hover.png); } /* **************************************** */ /* Scroll bar */ /* **************************************** */ QScrollBar{ height:20px; width:20px; background-color:transparent; } QScrollBar::handle{ border: 1px solid #aaa; border-radius: 4px; } QScrollBar::handle:vertical{ margin: 1px 4px; min-height:20px; min-width: 10px; background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #9A9A00, stop:1 #000000); } QScrollBar::handle:vertical:hover{ background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #000000, stop:1 #9A9A00); } QScrollBar::handle:horizontal{ min-height:10px;min-width:20px;margin:4px 1px; background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #9A9A00, stop:1 #000000); } QScrollBar::handle:horizontal:hover{ background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #000000, stop:1 #9A9A00); } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical,QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal{ background-color: transparent; } QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical,QScrollBar::add-line:horizontal,QScrollBar::sub-line:horizontal{ height:0; width:0; } /* ******************************************************** */ /* Main Tree View Inherited from QAbstractItemView */ /* ******************************************************** */ QTreeView { } QTreeView:item{ padding: 3px; font-size: 16px; height: 18px; /*Bottom text Box*/ } QTreeView:item:selected{ color:#fff; padding: 0px; background-color: #3F3F46; /* Grey - Blue*/ border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00; } QTreeView:item QLineEdit{ outline: none; border: none; padding: 0px; margin: 0px; } QTreeView::branch:has-siblings:!adjoins-item{ background:url(Transparent-Style/vault-101-item-line-v.png) center center no-repeat; } QTreeView::branch:has-siblings{ background:url(Transparent-Style/vault-101-itemB-line.png) center center no-repeat; } QTreeView::branch:!has-children:!has-siblings:adjoins-item{ background:url(Transparent-Style/vault-101-itemB-end.png) center center no-repeat; } QTreeView::branch:closed:has-children:has-siblings{ background:url(Transparent-Style/vault-101-itemB-close.png) center center no-repeat; } QTreeView::branch:closed:has-children:!has-siblings{ background:url(Transparent-Style/vault-101-itemB-close-last.png) center center no-repeat; } QTreeView::branch:open:has-children:has-siblings{ background:url(Transparent-Style/vault-101-itemB-open.png) center center no-repeat; } QTreeView::branch:open:has-children:!has-siblings{ background:url(Transparent-Style/vault-101-itemB-open-last.png) center center no-repeat; } /* **************************************************************** */ /* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */ /* common */ /* **************************************************************** */ QTreeView::indicator,QCheckBox::indicator{ outline: none; border: none; padding: 0px; width: 24px; height: 24px; } QGroupBox::indicator:checked,QGroupBox::indicator:indeterminate,QTreeView::indicator:checked,QTreeView::indicator:indeterminate,QCheckBox::indicator:checked,QCheckBox::indicator:indeterminate{ image: url(./Transparent-Style/checkbox-checked.png); } QGroupBox::indicator:checked:hover,QGroupBox::indicator:indeterminate:hover,QTreeView::indicator:checked:hover,QTreeView::indicator:indeterminate:hover,QCheckBox::indicator:checked:hover,QCheckBox::indicator:indeterminate:hover{ image: url(./Transparent-Style/checkbox-hover.png); } QGroupBox::indicator:checked:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:checked:disabled,QTreeView::indicator:indeterminate:disabled,QCheckBox::indicator:checked:disabled,QCheckBox::indicator:indeterminate:disabled{ image: url(./Transparent-Style/checkbox-checked-disabled.png); } QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QCheckBox::indicator:unchecked{ image: url(./Transparent-Style/checkbox.png); } QGroupBox::indicator:unchecked:hover,QTreeView::indicator:unchecked:hover,QCheckBox::indicator:unchecked:hover{ image: url(./Transparent-Style/checkbox-checked-hover.png); } QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QCheckBox::indicator:unchecked:disabled{ image: url(./Transparent-Style/checkbox-checked-disabled.png); } QTreeWidget#categoriesList::item:has-children{ background-image: url(./Transparent-Style/arrow-right-small.png); } QTreeWidget#categoriesList::item:has-children:open{ background-image: url(./Transparent-Style/branch-opened.png); } /* ******************************** */ /* Special styles */ /* increase categories tab width */ /* ******************************** */ QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ padding: .3em 0em; } QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ padding: .3em 0em; } QTreeWidget#categoriesGroup,QTreeWidget#categoriesList{ min-width: 200px; } QTreeWidget#categoriesList::item { background-position: center left; background-repeat: no-repeat; padding: 0.35em 4px; } /* ********************* */ /* Display Radio Button */ /* ********************* */ QRadioButton::indicator{ width: 16px; height: 16px; } QRadioButton::indicator::checked{ image: url(./Transparent-Style/radio-BOS-checked.png); } QRadioButton::indicator::unchecked{ image: url(./Transparent-Style/radio-BOS.png); } QRadioButton::indicator::unchecked:hover{ image: url(./Transparent-Style/radio-BOS-hover.png); } /* **************************************** */ /* Spinners #QSpinBox, #QDoubleSpinBox */ /* **************************************** */ QAbstractSpinBox{ padding: 5px; background-color: transparent; color: #eff0f1; border-radius: 2px; min-width: Auto; } QAbstractSpinBox::up-button,QAbstractSpinBox::down-button{ border-style: solid; border-width: 1px; subcontrol-origin: border; } QAbstractSpinBox::up-button:hover,QAbstractSpinBox::down-button:hover{ background-color: transparent; } QAbstractSpinBox::up-button{ background-color: transparent; subcontrol-origin: border; subcontrol-position: center right; padding-right: 5px; /* subcontrol-position: top right; */ } QAbstractSpinBox::down-button{ background-color: transparent; subcontrol-origin: border; subcontrol-position: center left; padding-left: 5px; /* subcontrol-position: bottom right; */ } QAbstractSpinBox::up-arrow{ border-image: url(./Transparent-Style/narrow-arrow-right.png); }QAbstractSpinBox::down-arrow{ border-image: url(./Transparent-Style/narrow-arrow-left.png); }/* QTextEdit,QWebView,QLineEdit,QComboBox{ border-color:#3F3F46; /* Grey - Blue*/ /*}*/ /* **************************************** */ /* Tooltips #QToolTip, #SaveGameInfoWidget */ /* **************************************** */ QToolTip{ background-color:#121212; color:#C0C0C0; padding:0px; border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } SaveGameInfoWidget{ font-size: 16px; background-color:#121212; color:#C0C0C0; padding:0px; border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } /* **************************** */ /* Handles Web, Nexus info tab */ /* **************************** */ QWebView{ background-color: Black; } QLineEdit{ font-family: Segoe UI; min-height: 20px; font-size: 16px; background-color:#3F3F46; selection-background-color: rgba(154,154,0,0.6);border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } QTextEdit{ color: #cccccc; font-family: Segoe UI; font-size: 16px; /* background-color:#000; */ selection-background-color: rgba(154,154,0,0.6); border-color:#9A9A00; border-radius: 3px; border-width:1px; border-style:solid; } /* Font size */ QLabel,QMenu,QPushButton,QAbstractSpinBox,QGroupBox,QCheckBox,QRadioButton{ color: #cccccc; font-family: Segoe UI; font-size: 16px; /* was 14px Profile font size */ } QAbstractItemView{ color: #cccccc; font-family: Segoe UI; /* font-family: Source Sans Pro; */ font-size: 16px; /* Body font size*/ } QRadioButton{ color: #cccccc; font-family: Segoe UI; /* font-family: Source Sans Pro; */ font-size: 16px; } QWebView,QComboBox,QComboBox:editable{ font-family:Segoe UI; font-style: normal; font-weight: normal; font-size: 16px; background-color:#000; /* border-color:#3F3F46; /* Grey - Blue*/ color: #cccccc; font-family: Arial; font-size: 16px; /* Filter Name*/ } QHeaderView::section{ /* font-family: Source Sans Pro; */ font-family: Segoe UI; font-size: 16px; /*Top Bar on menus font*/ } QListView::item{ color:#F1F1F1; /* White */ } QGroupBox { } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top center; } QGroupBox::indicator { subcontrol-origin: margin; subcontrol-position: top center; } QLabel { color: #cccccc; /* color: #F1F1F1; /* White */ /* border: none;outline: none; border-radius: 5px; border-top:1px solid #9A9A00; border-bottom:1px solid #9A9A00;*/ } \ No newline at end of file +#centralWidget { + border-image: url(./Transparent-Style/Background-skyrim.png) 0 0 0 0 stretch stretch; +} + QWidget#TextViewer{ +} + QWidget{ + color: #fff; + background-color: rgba(45,45,48,30); + alternate-background-color: rgba(38,38,38,30); +} + QWidget:disabled{ +} + QAbstractItemView{ + color: #dcdcdc; + background-color: rgb(45,45,48,30); + alternate-background-color: rgb(38,38,38,50); + border: 1px solid #3F3F46; + /* Grey - Blue*/ + border-style:outset; + border-radius: 3px; + selection-color: #3F3F46; + /* Grey - Blue*/ + outline: none; + padding-left:0; +} + #problemsWidget,#TransferSavesDialog,#SettingsDialog,#ProblemsDialog,#ModInfoDialog,#TextViewer,#PyCfgDialog,#InstallDialog { + border-image:url(./Transparent-Style/Vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #toolBarArea{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #EditExecutablesDialog{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #SimpleInstallDialog,#LockedDialog,QMenu{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #nameLabel{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:rgba(154,154,0,0.3); + color:#fff; +} + #ProfilesDialog,#FomodInstallerDialog{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} + #filesView{ + border-image: url(./Transparent-Style/vault-BOS-Main.png) 0 0 0 0 stretch stretch; +} +/* ******************************************** */ +/* Main Navigation Button Bar at top of window */ +/* ******************************************** */ + QToolBar{ + /*border: none; + */ +} + QToolBar::separator{ + /*image: url(./Transparent-Style/Vault-101-vault-logo.png); + */ +} + QToolButton{ + border:2px solid None; + border-radius: 6px; + margin: 3px; + padding-left: 8px; + padding-right: 8px; + padding-top: 0px; + padding-bottom: 2px; +} + QToolButton:hover{ + border-radius: 3px; + border: 0px; + padding: 0px; + background: trANSPARENT; + border: 0px ridge #9A9A00; + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #9A9A00, stop: 1 #484F53); +} + QToolButton:pressed{ + background: black; +} +/* **************************************** */ +/* Tabs on top of Treeview */ +/* **************************************** */ + QTabWidget::pane{ + border-color:#3F3F46; + /* Grey - Blue*/ + /* border-top-color:#9A9A00; + */ + top: 0px; + border-style:hidden; + border-width:1px; +} + QTabWidget::tab-bar{ + alignment: center; +} + QTabBar{ + color: #cccccc; + font-family: Segoe UI; + font-size: 16px; + text-transform: none; + min-height:auto; + padding-bottom: 5px; +} + QTabBar::tab{ + border: none; + border-bottom-style: none; + background-color: rgba(154,154,0,0.3); + padding-left: 9px; + padding-right: 9px; + padding-top: 3px; + padding-bottom: 3px; + margin-right: 2px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + QTabBar::tab:disabled{ + color: #404040; + background-color: #000; + margin-top: 0px; +} + QTabBar::tab:selected{ + color: #dcdcdc; + background-color: #9A9A00; + /*Yellow opaque*/ +} + QTabBar::tab:!selected:hover{ + border-radius: 3px; + border-top:3px double #9A9A00; + border-bottom:3px double #9A9A00; + padding: 1px; + text-align: center; + background-color: #9A9A00; + color:#fff; + Height: 20%; +} +/* QTabBar::tab:!selected{ + color: #808080; + background-color: #404040; + margin-top: 0px; +} + */ + #deactivateESP,#activateESP{ + border:#b30000; + background:#333337; +} +/* **************************************** */ +/* on top of Treeview */ +/* **************************************** */ + QTabBar QToolButton{ + border-radius: 0px; + border: 0px; + padding: 0px; + background: transparent; +} + QToolButton::menu-indicator:pressed,QToolButton::menu-indicator:open{ + image: url(./Transparent-Style/arrow-up.png); +} + QToolButton::menu-indicator:!pressed,QToolButton::menu-indicator:!open{ + image: url(./Transparent-Style/arrow-down.png); +} + QTabBar QToolButton::right-arrow{ + image: url(./Transparent-Style/narrow-arrow-right.png); +} + QTabBar QToolButton::left-arrow{ + image: url(./Transparent-Style/narrow-arrow-left.png); +} +/* **************************************** */ +/* Column names of TreeView */ +/* **************************************** */ + QTableView{ + gridline-color:#3F3F46; + selection-background-color:#9A9A00; + selection-color:#F1F1F1; + /* White */ + text-align: center; + min-height:24px; +} + QHeaderView{ + min-height:24px; + /*Top Bar on menus*/ + padding: 3px; +} + QHeaderView::section{ + /* color: #cc3333; + */ + background-color: #252526; + border-radius: 0px; + padding: 0px; + padding-left: 5px; +} + QHeaderView::section:hover{ + background: rgba(154,154,0,0.3); + border: 1px solid #9a9a00; + padding: 0px; + padding-left: 5px; +} + QHeaderView::up-arrow,QHeaderView::down-arrow{ + min-height:Auto; + min-width:Auto; +} + QHeaderView::up-arrow{ + image: url(./Transparent-Style/arrow-up.png); + min-height:25px; + min-width:15px; + padding-right: 0px; +} +QHeaderView::down-arrow{ + image: url(./Transparent-Style/arrow-down.png); +} +/* **************************************** */ +/* Hover */ +/* **************************************** */ + QMenu:item:hover,QMenuBar::item:selected,QTreeView#espList::item:hover,QTreeWidget#categoriesList::item:hover,QListView::item:hover,QTreeWidget::item:hover{ + color:#fff; + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:rgba(154,154,0,0.3); +} + QAbstractItemView::item:hover{ + border-top:1px solid #b9d7fc; + border-bottom:1px solid #b9d7fc; + background-color:rgba(154,154,0,0.3); + padding: 3px; +} +/*#DownloadListWidget{ +} +*/ + #DownloadListWidget{ + outline: 1px solid #e8e8e8; + border: 0px solid #9a9a00; +} + #deactivateESP:hover,#activateESP:hover,QTableView:hover,QHeaderView::selected:hover{ + border: 1px solid #9a9a00; + padding: 1px; + text-align: center; + color:#fff; +} +/* **************************************** */ +/* Context menus, toolbar dropdowns #QMenu */ +/* **************************************** */ + QMenu{ + border-width: 3px; + border-style: solid; +} +QMenu::item{ + padding: 6px 20px; +} +QMenu::item:selected{ + color:#fff; + background-color: #3F3F46; + /* Grey - Blue*/ + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QMenu::right-arrow{ + image: url(./Transparent-Style/arrow-right.png); + /* right click main menu top option*/ + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border:0px outset #3F3F46; + /* Grey - Blue*/ +} +/* ********************* */ +/* LCD Counter */ +/* ********************* */ + QLCDNumber{ + color: #9A9A00; + Background: #000; + border-color: #3F3F46; + /* Grey - Blue*/ + border-style: solid; + border-width: 1px; + border-radius: 5px; +} +/* **************************************** */ +/* Launch application Drop down Box */ +/* **************************************** */ + QComboBox{ + background-color: #333337; + padding: 3px 5px 5px 5px; + height: 26px; + border-radius: 5px; + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QComboBox:on{ + padding: 3px 5px 5px 5px; + color: white; + background-color: #3F3F46; + /* Grey - Blue*/ +} + QComboBox:hover{ + border-radius: 3px; + border-top:3px double #9A9A00; + border-bottom:3px double #9A9A00; + /* padding: 0px; + */ +} + QComboBox::drop-down{ + /* Down arrow in combo box */ + outline: none; + border: none; + border-width: 1px; +} + QComboBox::menu-indicator{ + image: url(./Transparent-Style/arrow-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border:1px outset #3F3F46; + /* Grey - Blue*/ +} +/* ************************* */ +/* QComboBox::down-arrow:on, */ +/* ************************* */ + QComboBox::down-arrow:hover,QComboBox::down-arrow:focus { + image: url(./Transparent-Style/arrow-down-hover.png); +} + QComboBox::up-arrow:hover,QComboBox::up-arrow:focus { + image: url(./Transparent-Style/arrow-up-hover.png); +} + QComboBox::down-arrow{ + image:url(./Transparent-Style/arrow-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; +} + QComboBox::down-arrow:on{ + image:url(./Transparent-Style/arrow-up.png); +} + QComboBox:editable { + background: black; + /* color: pink; + */ +} + QComboBox::item{ +} + QComboBox QAbstractItemView{ + selection-color:#fff; + outline: 1px solid #9a9a00; + selection-background-color:#3F3F46; + /* Grey - Blue*/ + border-top:1px solid #9a9a00; + border-bottom:1px solid #9a9a00; + padding: 5px; +} +/* **************************************************************** */ +/* Action Buttons */ +/* */ +/* Open list, Restore Backup, Create Backup, RUN, Shortcut Button */ +/* Sort, Load order backup, Load Order Restore */ +/* **************************************************************** */ + QPushButton{ + background-color: #333337; + min-height:26px; + padding:1px 5%; + text-align: center; + border-radius: 5px; + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QPushButton::menu-indicator{ + image: url(./Transparent-Style/arrow-down.png); + subcontrol-origin: padding; + subcontrol-position: center right; + width: 18px; + height: 18px; + border:1px hidden #3F3F46; + /* Grey - Blue*/ +} + QPushButton::menu-indicator:Hover{ + image: url(./Transparent-Style/arrow-down-hover.png); +} + QPushButton:focus{ +} + QPushButton:on,QPushButton:checked,QPushButton:pressed,QPushButton:checked:hover{ + background: #3F3F46; + /* Grey - Blue*/ + border-radius: 1px; + border: 1px solid #9a9a00; +} + QPushButton:hover{ + border-radius: 3px; + border-top:3px double #9A9A00; + border-bottom:3px double #9A9A00; +} + QDialog QPushButton,QSlider::handle:horizontal,QSlider::handle:vertical{ + color:#fff; + border-color:#707070; + border-style:solid; + border-width:1px; +} + QPushButton:disabled{ +} + QPushButton:open{ +} + QPushButton:!enabled{ +} +/* **************************************** */ +/* Filter Button */ +/* **************************************** */ + QPushButton#displayCategoriesBtn{ + min-width: 20px; + background:transparent; + color: transparent; + border: 1px hidden #9a9a00; +} + QPushButton#displayCategoriesBtn:checked{ + image: url(./Transparent-Style/arrow-left.png); +} + QPushButton#displayCategoriesBtn:!checked{ + image: url(./Transparent-Style/arrow-right.png); +} + QPushButton#displayCategoriesBtn:checked:hover{ + image: url(./Transparent-Style/arrow-left-hover.png); +} + QPushButton#displayCategoriesBtn:!checked:hover{ + image: url(./Transparent-Style/arrow-right-hover.png); +} +/* **************************************** */ +/* Scroll bar */ +/* **************************************** */ + QScrollBar{ + height:20px; + width:20px; + background-color:transparent; +} + QScrollBar::handle{ + border: 1px solid #aaa; + border-radius: 4px; +} + QScrollBar::handle:vertical{ + margin: 1px 4px; + min-height:20px; + min-width: 10px; + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #9A9A00, stop:1 #000000); +} + QScrollBar::handle:vertical:hover{ + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #000000, stop:1 #9A9A00); +} + QScrollBar::handle:horizontal{ + min-height:10px; + min-width:20px; + margin:4px 1px; + background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #9A9A00, stop:1 #000000); +} + QScrollBar::handle:horizontal:hover{ + background-color:qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop:0 #000000, stop:1 #9A9A00); +} + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical,QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal{ + background-color: transparent; +} + QScrollBar::add-line:vertical,QScrollBar::sub-line:vertical,QScrollBar::add-line:horizontal,QScrollBar::sub-line:horizontal{ + height:0; + width:0; +} +/* ******************************************************** */ +/* Main Tree View Inherited from QAbstractItemView */ +/* ******************************************************** */ + QTreeView { +} + QTreeView:item{ + padding: 3px; + font-size: 16px; + height: 18px; + /*Bottom text Box*/ +} + QTreeView:item:selected{ + color:#fff; + padding: 0px; + background-color: #3F3F46; + /* Grey - Blue*/ + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; +} + QTreeView:item QLineEdit{ + outline: none; + border: none; + padding: 0px; + margin: 0px; +} + QTreeView::branch:has-siblings:!adjoins-item{ + background:url(Transparent-Style/vault-101-item-line-v.png) center center no-repeat; +} + QTreeView::branch:has-siblings{ + background:url(Transparent-Style/vault-101-itemB-line.png) center center no-repeat; +} + QTreeView::branch:!has-children:!has-siblings:adjoins-item{ + background:url(Transparent-Style/vault-101-itemB-end.png) center center no-repeat; +} + QTreeView::branch:closed:has-children:has-siblings{ + background:url(Transparent-Style/vault-101-itemB-close.png) center center no-repeat; +} + QTreeView::branch:closed:has-children:!has-siblings{ + background:url(Transparent-Style/vault-101-itemB-close-last.png) center center no-repeat; +} + QTreeView::branch:open:has-children:has-siblings{ + background:url(Transparent-Style/vault-101-itemB-open.png) center center no-repeat; +} + QTreeView::branch:open:has-children:!has-siblings{ + background:url(Transparent-Style/vault-101-itemB-open-last.png) center center no-repeat; +} +/* **************************************************************** */ +/* Checkboxes and Radio buttons common #QCheckBox, #QRadioButton */ +/* common */ +/* **************************************************************** */ + QTreeView::indicator,QCheckBox::indicator{ + outline: none; + border: none; + padding: 0px; + width: 24px; + height: 24px; +} + QGroupBox::indicator:checked,QGroupBox::indicator:indeterminate,QTreeView::indicator:checked,QTreeView::indicator:indeterminate,QCheckBox::indicator:checked,QCheckBox::indicator:indeterminate{ + image: url(./Transparent-Style/checkbox-checked.png); +} + QGroupBox::indicator:checked:hover,QGroupBox::indicator:indeterminate:hover,QTreeView::indicator:checked:hover,QTreeView::indicator:indeterminate:hover,QCheckBox::indicator:checked:hover,QCheckBox::indicator:indeterminate:hover{ + image: url(./Transparent-Style/checkbox-hover.png); +} + QGroupBox::indicator:checked:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:checked:disabled,QTreeView::indicator:indeterminate:disabled,QCheckBox::indicator:checked:disabled,QCheckBox::indicator:indeterminate:disabled{ + image: url(./Transparent-Style/checkbox-checked-disabled.png); +} + QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QCheckBox::indicator:unchecked{ + image: url(./Transparent-Style/checkbox.png); +} + QGroupBox::indicator:unchecked:hover,QTreeView::indicator:unchecked:hover,QCheckBox::indicator:unchecked:hover{ + image: url(./Transparent-Style/checkbox-checked-hover.png); +} + QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QCheckBox::indicator:unchecked:disabled{ + image: url(./Transparent-Style/checkbox-checked-disabled.png); +} + QTreeWidget#categoriesList::item:has-children{ + background-image: url(./Transparent-Style/arrow-right-small.png); +} + QTreeWidget#categoriesList::item:has-children:open{ + background-image: url(./Transparent-Style/branch-opened.png); +} +/* ******************************** */ +/* Special styles */ +/* increase categories tab width */ +/* ******************************** */ + QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ + padding: .3em 0em; +} + QTreeView#modList::item,QTreeWidget#categoriesTree::item,#tabConflicts QTreeWidget::item,QListView::item,QTreeView#espList::item,QTreeView#treeContent::item{ + padding: .3em 0em; +} + QTreeWidget#categoriesGroup,QTreeWidget#categoriesList{ + min-width: 200px; +} + QTreeWidget#categoriesList::item { + background-position: center left; + background-repeat: no-repeat; + padding: 0.35em 4px; +} +/* ********************* */ +/* Display Radio Button */ +/* ********************* */ + QRadioButton::indicator{ + width: 16px; + height: 16px; +} + QRadioButton::indicator::checked{ + image: url(./Transparent-Style/radio-BOS-checked.png); +} + QRadioButton::indicator::unchecked{ + image: url(./Transparent-Style/radio-BOS.png); +} + QRadioButton::indicator::unchecked:hover{ + image: url(./Transparent-Style/radio-BOS-hover.png); +} +/* **************************************** */ +/* Spinners #QSpinBox, #QDoubleSpinBox */ +/* **************************************** */ + QAbstractSpinBox{ + padding: 5px; + background-color: transparent; + color: #eff0f1; + border-radius: 2px; + min-width: Auto; +} + QAbstractSpinBox::up-button,QAbstractSpinBox::down-button{ + border-style: solid; + border-width: 1px; + subcontrol-origin: border; +} + QAbstractSpinBox::up-button:hover,QAbstractSpinBox::down-button:hover{ + background-color: transparent; +} + QAbstractSpinBox::up-button{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center right; + padding-right: 5px; + /* subcontrol-position: top right; + */ +} + QAbstractSpinBox::down-button{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center left; + padding-left: 5px; + /* subcontrol-position: bottom right; + */ +} + QAbstractSpinBox::up-arrow{ + border-image: url(./Transparent-Style/narrow-arrow-right.png); +} +QAbstractSpinBox::down-arrow{ + border-image: url(./Transparent-Style/narrow-arrow-left.png); +} +/* QTextEdit,QWebView,QLineEdit,QComboBox{ + border-color:#3F3F46; + /* Grey - Blue*/ + /* +} +*/ +/* **************************************** */ +/* Tooltips #QToolTip, #SaveGameInfoWidget */ +/* **************************************** */ + QToolTip{ + background-color:#121212; + color:#C0C0C0; + padding:0px; + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} + SaveGameInfoWidget{ + font-size: 16px; + background-color:#121212; + color:#C0C0C0; + padding:0px; + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} +/* **************************** */ +/* Handles Web, Nexus info tab */ +/* **************************** */ + QWebView{ + background-color: Black; +} + QLineEdit{ + font-family: Segoe UI; + min-height: 20px; + font-size: 16px; + background-color:#3F3F46; + selection-background-color: rgba(154,154,0,0.6); + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} + QTextEdit{ + color: #cccccc; + font-family: Segoe UI; + font-size: 16px; + /* background-color:#000; + */ + selection-background-color: rgba(154,154,0,0.6); + border-color:#9A9A00; + border-radius: 3px; + border-width:1px; + border-style:solid; +} +/* Font size */ + QLabel,QMenu,QPushButton,QAbstractSpinBox,QGroupBox,QCheckBox,QRadioButton{ + color: #cccccc; + font-family: Segoe UI; + font-size: 16px; + /* was 14px Profile font size */ +} + QAbstractItemView{ + color: #cccccc; + font-family: Segoe UI; + /* font-family: Source Sans Pro; + */ + font-size: 16px; + /* Body font size*/ +} + QRadioButton{ + color: #cccccc; + font-family: Segoe UI; + /* font-family: Source Sans Pro; + */ + font-size: 16px; +} + QWebView,QComboBox,QComboBox:editable{ + font-family:Segoe UI; + font-style: normal; + font-weight: normal; + font-size: 16px; + background-color:#000; + /* border-color:#3F3F46; + /* Grey - Blue*/ + color: #cccccc; + font-family: Arial; + font-size: 16px; + /* Filter Name*/ +} + QHeaderView::section{ + /* font-family: Source Sans Pro; + */ + font-family: Segoe UI; + font-size: 16px; + /*Top Bar on menus font*/ +} + QListView::item{ + color:#F1F1F1; + /* White */ +} + QGroupBox { +} + QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; +} + QGroupBox::indicator { + subcontrol-origin: margin; + subcontrol-position: top center; +} + QLabel { + color: #cccccc; + /* color: #F1F1F1; + /* White */ + /* border: none; + outline: none; + border-radius: 5px; + border-top:1px solid #9A9A00; + border-bottom:1px solid #9A9A00; + */ +} diff --git a/src/stylesheets/dark.qss b/src/stylesheets/dark.qss index 5bbb0f0c..1ad534a1 100644 --- a/src/stylesheets/dark.qss +++ b/src/stylesheets/dark.qss @@ -247,6 +247,11 @@ QMenu::item:selected border-color: #3EA0CA; } +QMenuBar::item:selected { + background-color: #3c4b54; + border-color: #3EA0CA; +} + QProgressBar { border: 2px solid grey; diff --git a/src/stylesheets/skyrim.qss b/src/stylesheets/skyrim.qss index d36eac57..d69fa173 100644 --- a/src/stylesheets/skyrim.qss +++ b/src/stylesheets/skyrim.qss @@ -488,6 +488,14 @@ QHeaderView { image: url(./skyrim/arrow-down.png); } /* Context menus, toolbar drop-downs #QMenu */ +QMenuBar { + background-color: #000; +} + +QMenuBar::item:selected { + background-color: #121212; +} + QMenu { background-color: transparent; } QMenu::item, diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 10f923cb..1fa9dcef 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -435,13 +435,13 @@ QScrollBar::sub-line { background-color: #3E3E42; border: none; } -/*QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +/*QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { }*/ QScrollBar::handle:hover { background: #9E9E9E; } @@ -551,7 +551,7 @@ QMenu::item { background: transparent; padding: 4px 20px; } -QMenu::item:selected { +QMenu::item:selected, QMenuBar::item:selected { background-color: #333334; } QMenu::item:disabled { diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index bbde1f82..e02d895d 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -436,13 +436,13 @@ QScrollBar::sub-line { background-color: #3E3E42; border: none; } -/*QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +/*QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { }*/ QScrollBar::handle:hover { background: #9E9E9E; } @@ -552,7 +552,7 @@ QMenu::item { background: transparent; padding: 4px 20px; } -QMenu::item:selected { +QMenu::item:selected, QMenuBar::item:selected { background-color: #333334; } QMenu::item:disabled { diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index faad7297..24ad5c35 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -436,13 +436,13 @@ QScrollBar::sub-line { background-color: #3E3E42; border: none; } -/*QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +/*QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { }*/ QScrollBar::handle:hover { background: #9E9E9E; } @@ -552,7 +552,7 @@ QMenu::item { background: transparent; padding: 4px 20px; } -QMenu::item:selected { +QMenu::item:selected, QMenuBar::item:selected { background-color: #333334; } QMenu::item:disabled { diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 2ffcff68..9f286335 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -436,13 +436,13 @@ QScrollBar::sub-line { background-color: #3E3E42; border: none; } -/*QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +/*QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { }*/ QScrollBar::handle:hover { background: #9E9E9E; } @@ -552,7 +552,7 @@ QMenu::item { background: transparent; padding: 4px 20px; } -QMenu::item:selected { +QMenu::item:selected, QMenuBar::item:selected { background-color: #333334; } QMenu::item:disabled { diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index 24afe005..fc9143ea 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -436,13 +436,13 @@ QScrollBar::sub-line { background-color: #3E3E42; border: none; } -/*QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +/*QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { }*/ QScrollBar::handle:hover { background: #9E9E9E; } @@ -552,7 +552,7 @@ QMenu::item { background: transparent; padding: 4px 20px; } -QMenu::item:selected { +QMenu::item:selected, QMenuBar::item:selected { background-color: #333334; } QMenu::item:disabled { diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index 6a551775..202c74d8 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -435,13 +435,13 @@ QScrollBar::sub-line { background-color: #3E3E42; border: none; } -/*QScrollBar::add-line:horizontal:hover, -QScrollBar::sub-line:horizontal:hover, -QScrollBar::add-line:vertical:hover, +/*QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover, -QScrollBar::add-line:horizontal:pressed, -QScrollBar::sub-line:horizontal:pressed, -QScrollBar::add-line:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, QScrollBar::sub-line:vertical:pressed { }*/ QScrollBar::handle:hover { background: #9E9E9E; } @@ -551,7 +551,7 @@ QMenu::item { background: transparent; padding: 4px 20px; } -QMenu::item:selected { +QMenu::item:selected, QMenuBar::item:selected { background-color: #333334; } QMenu::item:disabled { -- cgit v1.3.1 From 471d76832a7ec42b148b4eab3e466432a7042e93 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 22:01:40 -0400 Subject: reduced the padding of toolbar icons in the vs15 themes --- src/stylesheets/vs15 Dark-Green.qss | 3 +-- src/stylesheets/vs15 Dark-Orange.qss | 3 +-- src/stylesheets/vs15 Dark-Purple.qss | 3 +-- src/stylesheets/vs15 Dark-Red.qss | 3 +-- src/stylesheets/vs15 Dark-Yellow.qss | 3 +-- src/stylesheets/vs15 Dark.qss | 3 +-- 6 files changed, 6 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 1fa9dcef..5edcab9c 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -201,8 +201,7 @@ QToolBar::separator { width: 0; } QToolButton { - margin: 0 4px 0 4px; - padding: 5px; } + padding: 4px; } QToolButton:hover, QToolButton:focus { background-color: #3E3E40; } QToolButton:pressed { diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index e02d895d..5d3bcd94 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -202,8 +202,7 @@ QToolBar::separator { width: 0; } QToolButton { - margin: 0 4px 0 4px; - padding: 5px; } + padding: 4px; } QToolButton:hover, QToolButton:focus { background-color: #3E3E40; } QToolButton:pressed { diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index 24ad5c35..0ace4f23 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -202,8 +202,7 @@ QToolBar::separator { width: 0; } QToolButton { - margin: 0 4px 0 4px; - padding: 5px; } + padding: 4px; } QToolButton:hover, QToolButton:focus { background-color: #3E3E40; } QToolButton:pressed { diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 9f286335..3a0a645c 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -202,8 +202,7 @@ QToolBar::separator { width: 0; } QToolButton { - margin: 0 4px 0 4px; - padding: 5px; } + padding: 4px; } QToolButton:hover, QToolButton:focus { background-color: #3E3E40; } QToolButton:pressed { diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index fc9143ea..b833c37e 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -202,8 +202,7 @@ QToolBar::separator { width: 0; } QToolButton { - margin: 0 4px 0 4px; - padding: 5px; } + padding: 4px; } QToolButton:hover, QToolButton:focus { background-color: #3E3E40; } QToolButton:pressed { diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index 202c74d8..9a571ab9 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -201,8 +201,7 @@ QToolBar::separator { width: 0; } QToolButton { - margin: 0 4px 0 4px; - padding: 5px; } + padding: 4px; } QToolButton:hover, QToolButton:focus { background-color: #3E3E40; } QToolButton:pressed { -- cgit v1.3.1 From 1fcd8f0e09363a0103df116e137ea9eb16bbf0db Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 2 Jun 2019 22:37:36 -0400 Subject: missed some mnemonics in the main menu --- src/mainwindow.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 05d64bdf..9656cbf2 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1426,11 +1426,11 @@ p, li { white-space: pre-wrap; } - View + &View - Toolbars + &Toolbars @@ -1622,7 +1622,7 @@ p, li { white-space: pre-wrap; } :/MO/gui/warning:/MO/gui/warning - Notifications... + &Notifications... Open the notifications dialog -- cgit v1.3.1 From 24aee49792c149f080eee37171ba049d0a618961 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 3 Jun 2019 14:51:12 -0400 Subject: remember the monitor number use it to display the splash screen on the same monitor as the main window instead of where the mouse cursor is --- src/main.cpp | 11 +++++++++++ src/mainwindow.cpp | 2 ++ 2 files changed, 13 insertions(+) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index c012b037..6f304944 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -597,6 +597,17 @@ int runApplication(MOApplication &application, SingleInstance &instance, QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); + + if (settings.contains("window_monitor")) { + const int monitor = settings.value("window_monitor").toInt(); + + if (monitor != -1) { + QDesktopWidget* desktop = QApplication::desktop(); + const QPoint center = desktop->availableGeometry(monitor).center(); + splash.move(center - splash.rect().center()); + } + } + splash.show(); splash.activateWindow(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0ef9bc80..428c77dc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1930,6 +1930,7 @@ void MainWindow::storeSettings(QSettings &settings) { if (settings.value("reset_geometry", false).toBool()) { settings.remove("window_geometry"); settings.remove("window_split"); + settings.remove("window_monitor"); settings.remove("log_split"); settings.remove("filters_visible"); settings.remove("browser_geometry"); @@ -1938,6 +1939,7 @@ void MainWindow::storeSettings(QSettings &settings) { } else { settings.setValue("window_geometry", saveGeometry()); settings.setValue("window_split", ui->splitter->saveState()); + settings.setValue("window_monitor", QApplication::desktop()->screenNumber(this)); settings.setValue("log_split", ui->topLevelSplitter->saveState()); settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); -- cgit v1.3.1 From 933f460ee61a89ff71038c478de92d6c3760cc9a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 3 Jun 2019 17:20:31 -0400 Subject: added new dialog to enter api key manually added button in nexus settings to open the dialog added a force flag to apiCheck() to do a check with the new key even if the login was successful before --- src/nexusmanualkey.ui | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ src/nxmaccessmanager.cpp | 6 +++- src/nxmaccessmanager.h | 2 +- src/settingsdialog.cpp | 43 +++++++++++++++++++++++ src/settingsdialog.h | 2 ++ src/settingsdialog.ui | 16 +++++++++ 6 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/nexusmanualkey.ui (limited to 'src') diff --git a/src/nexusmanualkey.ui b/src/nexusmanualkey.ui new file mode 100644 index 00000000..191ca218 --- /dev/null +++ b/src/nexusmanualkey.ui @@ -0,0 +1,89 @@ + + + NexusManualKeyDialog + + + + 0 + 0 + 452 + 302 + + + + Dialog + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Enter API key here + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + NexusManualKeyDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NexusManualKeyDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 00bb2a91..684a53fa 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -176,12 +176,16 @@ bool NXMAccessManager::validateWaiting() const } -void NXMAccessManager::apiCheck(const QString &apiKey) +void NXMAccessManager::apiCheck(const QString &apiKey, bool force) { if (m_ValidateReply != nullptr) { return; } + if (force) { + m_ValidateState = VALIDATE_NOT_CHECKED; + } + if (m_ValidateState == VALIDATE_VALID) { emit validateSuccessful(false); return; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 54ae14fb..e3608f8d 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -46,7 +46,7 @@ public: bool validateAttempted() const; bool validateWaiting() const; - void apiCheck(const QString &apiKey); + void apiCheck(const QString &apiKey, bool force=false); void showCookies() const; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 7f53a9bb..44d7fde5 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "settingsdialog.h" #include "ui_settingsdialog.h" +#include "ui_nexusmanualkey.h" #include "categoriesdialog.h" #include "helper.h" #include "noeditdelegate.h" @@ -27,6 +28,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "instancemanager.h" #include "nexusinterface.h" +#include "nxmaccessmanager.h" #include "plugincontainer.h" #include @@ -47,6 +49,33 @@ along with Mod Organizer. If not, see . using namespace MOBase; + +class NexusManualKeyDialog : public QDialog +{ +public: + NexusManualKeyDialog(QWidget* parent) + : QDialog(parent), ui(new Ui::NexusManualKeyDialog) + { + ui->setupUi(this); + } + + void accept() override + { + m_key = ui->key->toPlainText(); + QDialog::accept(); + } + + const QString& key() const + { + return m_key; + } + +private: + std::unique_ptr ui; + QString m_key; +}; + + SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) @@ -344,6 +373,20 @@ void SettingsDialog::on_nexusConnect_clicked() m_nexusLogin->open(url); } +void SettingsDialog::on_nexusManualKey_clicked() +{ + NexusManualKeyDialog dialog(this); + + if (dialog.exec() != QDialog::Accepted) { + return; + } + + const auto key = dialog.key(); + emit processApiKey(key); + ui->nexusConnect->setText("Nexus API Key Stored"); + NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(key, true); +} + void SettingsDialog::dispatchLogin() { m_KeyReceived = false; diff --git a/src/settingsdialog.h b/src/settingsdialog.h index afe988bd..a844b391 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -156,6 +156,8 @@ private slots: void on_nexusConnect_clicked(); + void on_nexusManualKey_clicked(); + void dispatchLogin(); void loginPing(); diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index fa8893b9..088b7a0d 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -494,6 +494,22 @@ p, li { white-space: pre-wrap; }
    + + + + + 0 + 0 + + + + Manually enter the API key and try to login + + + Enter API Key Manually + + + -- 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') 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') 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 ea3f8826f13832e97fde8ae0c50c3533221df34e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 3 Jun 2019 19:09:10 -0400 Subject: more comments --- src/mainwindow.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 62971f00..f8c09b20 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -738,6 +738,10 @@ void MainWindow::setToolbarButtonStyle(Qt::ToolButtonStyle s) void MainWindow::on_centralWidget_customContextMenuRequested(const QPoint &pos) { + // this allows for getting the context menu even if both the menubar and all + // the toolbars are hidden; an alternative is the Alt key handled in + // keyPressEvent() below + // 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. -- cgit v1.3.1 From ae021586a5291255a5f9faf8068d87367f497002 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 3 Jun 2019 19:18:09 -0400 Subject: reset m_closing to false when the user cancels closing --- src/mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 01bb4fc1..1f5d5bdc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1049,6 +1049,7 @@ bool MainWindow::exit() if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { + m_closing = false; return false; } else { m_OrganizerCore.downloadManager()->pauseAll(); -- 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') 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') 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') 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 2db29cffda538c6a9be24dff705e1d032bd42008 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 20:58:30 -0400 Subject: renamed "revoke nexus authorization" to "disconnect from nexus" because "revoking" is ambiguous reworked manual key dialog to look more like MO1 moved all the UI stuff from callbacks in Settings back to SettingsDialog in a central updateNexusButtons(), which also enables/disables buttons as needed --- src/nexusmanualkey.ui | 156 ++++++++++++++++++++++++++++++++++++++++++++++--- src/settings.cpp | 78 +++++++++---------------- src/settings.h | 30 ++++++---- src/settingsdialog.cpp | 129 ++++++++++++++++++++++++++++++++++------ src/settingsdialog.h | 101 +++++++++++--------------------- src/settingsdialog.ui | 4 +- 6 files changed, 341 insertions(+), 157 deletions(-) (limited to 'src') diff --git a/src/nexusmanualkey.ui b/src/nexusmanualkey.ui index 191ca218..1a842dc3 100644 --- a/src/nexusmanualkey.ui +++ b/src/nexusmanualkey.ui @@ -7,16 +7,16 @@ 0 0 452 - 302 + 236 Dialog - + - + 0 @@ -30,10 +30,152 @@ 0 - - - Enter API key here - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1. Get your personal API key + + + + + + + Open Browser + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 2. Enter your personal API key + + + + + + + Qt::Horizontal + + + + 115 + 20 + + + + + + + + Paste + + + + + + + Clear + + + + + + + + + + + 0 + 0 + + + + + 1 + 1 + + + + Enter API key here + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <b>Sharing this key with anyone could get your Nexus account banned. You can always revoke this key from your account on the Nexus website.</b> + + + true + + + + diff --git a/src/settings.cpp b/src/settings.cpp index a844fdc9..53dd32dc 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -363,6 +363,32 @@ bool Settings::getNexusApiKey(QString &apiKey) const return true; } +bool Settings::setNexusApiKey(const QString& apiKey) +{ + if (!obfuscate("APIKEY", apiKey)) { + wchar_t buffer[256]; + + FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); + + qCritical() << "Storing API key failed:" << buffer; + return false; + } + + return true; +} + +bool Settings::clearNexusApiKey() +{ + return setNexusApiKey(""); +} + +bool Settings::hasNexusApiKey() const +{ + return !deObfuscate("APIKEY").isEmpty(); +} + bool Settings::getSteamLogin(QString &username, QString &password) const { if (m_Settings.contains("Settings/steam_username")) { @@ -452,17 +478,6 @@ QString Settings::executablesBlacklist() const ).toString(); } -void Settings::setNexusApiKey(QString apiKey) -{ - if (!obfuscate("APIKEY", apiKey)) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Storing API key failed:" << buffer; - } -} - void Settings::setSteamLogin(QString username, QString password) { if (username == "") { @@ -706,43 +721,10 @@ void Settings::resetDialogs() QuestionBoxMemory::resetDialogs(); } -void Settings::processApiKey(const QString &apiKey) -{ - if (!obfuscate("APIKEY", apiKey)) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Storing or deleting API key failed:" << buffer; - } -} - -void Settings::clearApiKey(QPushButton *nexusButton) -{ - obfuscate("APIKEY", ""); - nexusButton->setEnabled(true); - nexusButton->setText("Connect to Nexus"); -} - -void Settings::checkApiKey(QPushButton *nexusButton) -{ - if (deObfuscate("APIKEY").isEmpty()) { - nexusButton->setEnabled(true); - nexusButton->setText("Connect to Nexus"); - QMessageBox::warning(qApp->activeWindow(), tr("Error"), - tr("Failed to retrieve a Nexus API key! Please try again. " - "A browser window should open asking you to authorize.")); - } -} - void Settings::query(PluginContainer *pluginContainer, QWidget *parent) { - SettingsDialog dialog(pluginContainer, parent); - + SettingsDialog dialog(pluginContainer, this, parent); connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); - connect(&dialog, SIGNAL(processApiKey(const QString &)), this, SLOT(processApiKey(const QString &))); - connect(&dialog, SIGNAL(closeApiConnection(QPushButton *)), this, SLOT(checkApiKey(QPushButton *))); - connect(&dialog, SIGNAL(revokeApiKey(QPushButton *)), this, SLOT(clearApiKey(QPushButton *))); std::vector> tabs; @@ -1043,7 +1025,6 @@ void Settings::DiagnosticsTab::update() Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) : Settings::SettingsTab(parent, dialog) - , m_nexusConnect(dialog.findChild("nexusConnect")) , m_offlineBox(dialog.findChild("offlineBox")) , m_proxyBox(dialog.findChild("proxyBox")) , m_knownServersList(dialog.findChild("knownServersList")) @@ -1052,11 +1033,6 @@ Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) , m_endorsementBox(dialog.findChild("endorsementBox")) , m_hideAPICounterBox(dialog.findChild("hideAPICounterBox")) { - if (!deObfuscate("APIKEY").isEmpty()) { - m_nexusConnect->setText("Nexus API Key Stored"); - m_nexusConnect->setDisabled(true); - } - m_offlineBox->setChecked(parent->offlineMode()); m_proxyBox->setChecked(parent->useProxy()); m_endorsementBox->setChecked(parent->endorsementIntegration()); diff --git a/src/settings.h b/src/settings.h index 04a0646e..bccd1e81 100644 --- a/src/settings.h +++ b/src/settings.h @@ -187,6 +187,24 @@ public: **/ bool getNexusApiKey(QString &apiKey) const; + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + bool setNexusApiKey(const QString& apiKey); + + /** + * @brief clears the nexus login information + */ + bool clearNexusApiKey(); + + /** + * @brief returns whether an API key is currently stored + */ + bool hasNexusApiKey() const; + /** * @brief retrieve the login information for steam * @@ -240,14 +258,6 @@ public: QString executablesBlacklist() const; - /** - * @brief set the nexus login information - * - * @param username username - * @param password password - */ - void setNexusApiKey(QString apiKey); - /** * @brief set the steam login information * @@ -481,7 +491,6 @@ private: void update(); private: - QPushButton *m_nexusConnect; QCheckBox *m_offlineBox; QCheckBox *m_proxyBox; QListWidget *m_knownServersList; @@ -538,9 +547,6 @@ private: private slots: void resetDialogs(); - void processApiKey(const QString &); - void clearApiKey(QPushButton *nexusButton); - void checkApiKey(QPushButton *nexusButton); signals: diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 44d7fde5..0373cef6 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -57,6 +57,10 @@ public: : QDialog(parent), ui(new Ui::NexusManualKeyDialog) { ui->setupUi(this); + + connect(ui->openBrowser, &QPushButton::clicked, [&]{ openBrowser(); }); + connect(ui->paste, &QPushButton::clicked, [&]{ paste(); }); + connect(ui->clear, &QPushButton::clicked, [&]{ clear(); }); } void accept() override @@ -70,15 +74,34 @@ public: return m_key; } + void openBrowser() + { + shell::OpenLink(QUrl("https://www.nexusmods.com/users/myaccount?tab=api")); + } + + void paste() + { + const auto text = QApplication::clipboard()->text(); + if (!text.isEmpty()) { + ui->key->setPlainText(text); + } + } + + void clear() + { + ui->key->clear(); + } + private: std::unique_ptr ui; QString m_key; }; -SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, QWidget *parent) +SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent) : TutorableDialog("SettingsDialog", parent) , ui(new Ui::SettingsDialog) + , m_settings(settings) , m_PluginContainer(pluginContainer) , m_nexusLogin(new QWebSocket) , m_KeyReceived(false) @@ -95,8 +118,9 @@ SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, QWidget *parent connect(m_nexusLogin, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(authError(QAbstractSocket::SocketError))); connect(m_nexusLogin, SIGNAL(textMessageReceived(const QString &)), this, SLOT(receiveApiKey(const QString &))); connect(m_nexusLogin, SIGNAL(disconnected()), this, SLOT(completeApiConnection())); - connect(this, SIGNAL(retryApiConnection()), this, SLOT(on_nexusConnect_clicked())); m_loginTimer.callOnTimeout(this, &SettingsDialog::loginPing); + + updateNexusButtons(); } SettingsDialog::~SettingsDialog() @@ -367,10 +391,7 @@ void SettingsDialog::on_resetDialogsButton_clicked() void SettingsDialog::on_nexusConnect_clicked() { - ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 30 minutes."); - ui->nexusConnect->setDisabled(true); - QUrl url = QUrl("wss://sso.nexusmods.com"); - m_nexusLogin->open(url); + fetchNexusApiKey(); } void SettingsDialog::on_nexusManualKey_clicked() @@ -382,9 +403,21 @@ void SettingsDialog::on_nexusManualKey_clicked() } const auto key = dialog.key(); - emit processApiKey(key); - ui->nexusConnect->setText("Nexus API Key Stored"); - NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(key, true); + + if (key.isEmpty()) { + clearKey(); + } else { + if (setKey(key)) { + testApiKey(); + } + } +} + +void SettingsDialog::fetchNexusApiKey() +{ + QUrl url = QUrl("wss://sso.nexusmods.com"); + m_nexusLogin->open(url); + updateNexusButtons(); } void SettingsDialog::dispatchLogin() @@ -434,12 +467,17 @@ void SettingsDialog::receiveApiKey(const QString &response) if (data.contains("connection_token")) { m_AuthToken = data["connection_token"].toString(); } else { - m_KeyReceived = true; - emit processApiKey(data["api_key"].toString()); + const auto key = data["api_key"].toString(); + m_nexusLogin->close(); - ui->nexusConnect->setText("Nexus API Key Stored"); m_loginTimer.stop(); m_totalPings = 0; + + if (key.isEmpty()) { + clearKey(); + } else { + setKey(key); + } } } else { QString error("There was a problem with SSO initialization: %1"); @@ -450,10 +488,64 @@ void SettingsDialog::receiveApiKey(const QString &response) void SettingsDialog::completeApiConnection() { - if (m_KeyReceived == true || !m_loginTimer.isActive()) - emit closeApiConnection(ui->nexusConnect); - else - emit retryApiConnection(); + if (!m_KeyReceived && !m_loginTimer.isActive()) { + QMessageBox::warning(qApp->activeWindow(), tr("Error"), + tr("Failed to retrieve a Nexus API key! Please try again. " + "A browser window should open asking you to authorize.")); + + // try again + fetchNexusApiKey(); + } +} + +bool SettingsDialog::setKey(const QString& key) +{ + m_KeyReceived = true; + const bool ret = m_settings->setNexusApiKey(key); + updateNexusButtons(); + return ret; +} + +bool SettingsDialog::clearKey() +{ + m_KeyCleared = true; + const auto ret = m_settings->clearNexusApiKey(); + updateNexusButtons(); + return ret; +} + +void SettingsDialog::testApiKey() +{ + QString key; + if (!m_settings->getNexusApiKey(key)) { + qWarning().nospace() << "can't test API key, nothing stored"; + return; + } + + auto* am = NexusInterface::instance(m_PluginContainer)->getAccessManager(); + am->apiCheck(key, true); +} + +void SettingsDialog::updateNexusButtons() +{ + if (m_nexusLogin->state() != QAbstractSocket::UnconnectedState) { + // api key is in the process of being retrieved + ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 30 minutes."); + ui->nexusConnect->setEnabled(false); + } + else if (m_settings->hasNexusApiKey()) { + // api key is present + ui->nexusConnect->setText("Nexus API Key Stored"); + ui->nexusConnect->setEnabled(false); + ui->nexusDisconnect->setEnabled(true); + ui->nexusManualKey->setEnabled(false); + } else { + // api key not present + ui->nexusConnect->setText("Connect to Nexus"); + ui->nexusConnect->setEnabled(true); + ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setEnabled(true); + } } void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) @@ -523,10 +615,9 @@ void SettingsDialog::on_clearCacheButton_clicked() NexusInterface::instance(m_PluginContainer)->clearCache(); } -void SettingsDialog::on_revokeNexusAuthButton_clicked() +void SettingsDialog::on_nexusDisconnect_clicked() { - emit revokeApiKey(ui->nexusConnect); - m_KeyCleared = true; + clearKey(); } void SettingsDialog::normalizePath(QLineEdit *lineEdit) diff --git a/src/settingsdialog.h b/src/settingsdialog.h index a844b391..858a36d4 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include class PluginContainer; +class Settings; namespace Ui { class SettingsDialog; @@ -44,7 +45,9 @@ class SettingsDialog : public MOBase::TutorableDialog Q_OBJECT public: - explicit SettingsDialog(PluginContainer *pluginContainer, QWidget *parent = 0); + explicit SettingsDialog( + PluginContainer *pluginContainer, Settings* settings, QWidget *parent = 0); + ~SettingsDialog(); /** @@ -62,9 +65,6 @@ public slots: signals: void resetDialogs(); - void processApiKey(const QString &); - void closeApiConnection(QPushButton *); - void revokeApiKey(QPushButton *); void retryApiConnection(); private: @@ -94,105 +94,74 @@ public: private slots: - //void on_loginCheckBox_toggled(bool checked); - void on_categoriesBtn_clicked(); - void on_execBlacklistBtn_clicked(); - void on_bsaDateBtn_clicked(); - void on_browseDownloadDirBtn_clicked(); - void on_browseModDirBtn_clicked(); - void on_browseCacheDirBtn_clicked(); - void on_resetDialogsButton_clicked(); - void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void deleteBlacklistItem(); - void on_associateButton_clicked(); - void on_clearCacheButton_clicked(); - - void on_revokeNexusAuthButton_clicked(); - + void on_nexusDisconnect_clicked(); void on_browseBaseDirBtn_clicked(); - void on_browseOverwriteDirBtn_clicked(); - void on_browseProfilesDirBtn_clicked(); - void on_browseGameDirBtn_clicked(); - void on_overwritingBtn_clicked(); - void on_overwrittenBtn_clicked(); - void on_overwritingArchiveBtn_clicked(); - void on_overwrittenArchiveBtn_clicked(); - void on_containsBtn_clicked(); - void on_containedBtn_clicked(); - void on_resetColorsBtn_clicked(); - void on_baseDirEdit_editingFinished(); - void on_downloadDirEdit_editingFinished(); - void on_modDirEdit_editingFinished(); - void on_cacheDirEdit_editingFinished(); - void on_profilesDirEdit_editingFinished(); - void on_overwriteDirEdit_editingFinished(); - void on_nexusConnect_clicked(); - void on_nexusManualKey_clicked(); + void on_resetGeometryBtn_clicked(); + void deleteBlacklistItem(); void dispatchLogin(); - void loginPing(); - void authError(QAbstractSocket::SocketError error); - void receiveApiKey(const QString &apiKey); - void completeApiConnection(); - void on_resetGeometryBtn_clicked(); - private: - Ui::SettingsDialog *ui; - PluginContainer *m_PluginContainer; - - QColor m_OverwritingColor; - QColor m_OverwrittenColor; - QColor m_OverwritingArchiveColor; - QColor m_OverwrittenArchiveColor; - QColor m_ContainsColor; - QColor m_ContainedColor; - - bool m_KeyReceived; - bool m_KeyCleared; - bool m_GeometriesReset; - QString m_UUID; - QString m_AuthToken; - - QString m_ExecutableBlacklist; - QWebSocket *m_nexusLogin; - QTimer m_loginTimer; - int m_totalPings = 0; + Ui::SettingsDialog *ui; + Settings* m_settings; + PluginContainer *m_PluginContainer; + + QColor m_OverwritingColor; + QColor m_OverwrittenColor; + QColor m_OverwritingArchiveColor; + QColor m_OverwrittenArchiveColor; + QColor m_ContainsColor; + QColor m_ContainedColor; + + bool m_KeyReceived; + bool m_KeyCleared; + bool m_GeometriesReset; + QString m_UUID; + QString m_AuthToken; + + QString m_ExecutableBlacklist; + QWebSocket *m_nexusLogin; + QTimer m_loginTimer; + int m_totalPings = 0; + + bool setKey(const QString& key); + bool clearKey(); + void updateNexusButtons(); + + void fetchNexusApiKey(); + void testApiKey(); }; - - #endif // SETTINGSDIALOG_H diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 088b7a0d..08c8d9f2 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -511,12 +511,12 @@ p, li { white-space: pre-wrap; } - + Clear the stored Nexus API key and force reauthorization. - Revoke Nexus Authorization + Disconnect from Nexus -- cgit v1.3.1 From c31303c6af77a47e61efd94022302f860ed5ebfc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 21:15:54 -0400 Subject: fixed title on the manual key dialog disabled disconnect and manual key buttons while fetching the api key --- src/nexusmanualkey.ui | 2 +- src/settingsdialog.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/nexusmanualkey.ui b/src/nexusmanualkey.ui index 1a842dc3..847bdd61 100644 --- a/src/nexusmanualkey.ui +++ b/src/nexusmanualkey.ui @@ -11,7 +11,7 @@ - Dialog + Manual Nexus API Key diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 0373cef6..b3e8c8a7 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -532,6 +532,8 @@ void SettingsDialog::updateNexusButtons() // api key is in the process of being retrieved ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 30 minutes."); ui->nexusConnect->setEnabled(false); + ui->nexusDisconnect->setEnabled(false); + ui->nexusManualKey->setEnabled(false); } else if (m_settings->hasNexusApiKey()) { // api key is present -- 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') 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 aedddb10f820dc61cc9aeb164b3318871ac5a15d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 23:31:29 -0400 Subject: refactored conflict list items: - use new ConflictItem class derived from QTreeWidgetItem - that class has all the various conflict*() member functions that were previously in the dialog - moved some of the can*() member functions out to namespace level so they can be used by ConflictItem, no real need to have them as members anyway --- src/modinfodialog.cpp | 352 +++++++++++++++++++++++++------------------------- src/modinfodialog.h | 21 --- 2 files changed, 178 insertions(+), 195 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 5d2dd811..ce15f5db 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -57,12 +57,6 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -const auto FILENAME_USERROLE = Qt::UserRole + 1; -const auto ALT_ORIGIN_USERROLE = Qt::UserRole + 2; -const auto ARCHIVE_USERROLE = Qt::UserRole + 3; -const auto INDEX_USERROLE = Qt::UserRole + 4; -const auto HAS_ALTS_USERROLE = Qt::UserRole + 5; - class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); @@ -339,6 +333,132 @@ protected: }; +bool canPreviewFile( + PluginContainer* pluginContainer, bool isArchive, const QString& filename) +{ + if (isArchive) { + return false; + } + + const auto ext = QFileInfo(filename).suffix(); + return pluginContainer->previewGenerator().previewSupported(ext); +} + +bool canOpenFile(bool isArchive, const QString&) +{ + // can open anything as long as it's not in an archive + return !isArchive; +} + +bool canHideFile(bool isArchive, const QString& filename) +{ + if (isArchive) { + // can't hide files from archives + return false; + } + + if (filename.endsWith(ModInfo::s_HiddenExt)) { + // already hidden + return false; + } + + return true; +} + +bool canUnhideFile(bool isArchive, const QString& filename) +{ + if (isArchive) { + // can't unhide files from archives + return false; + } + + if (!filename.endsWith(ModInfo::s_HiddenExt)) { + // already visible + return false; + } + + return true; +} + + +class ConflictItem : public QTreeWidgetItem +{ +public: + static const int FILENAME_USERROLE = Qt::UserRole + 1; + static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2; + static const int ARCHIVE_USERROLE = Qt::UserRole + 3; + static const int INDEX_USERROLE = Qt::UserRole + 4; + static const int HAS_ALTS_USERROLE = Qt::UserRole + 5; + + ConflictItem( + QStringList columns, FileEntry::Index index, const QString& fileName, + bool hasAltOrigins, const QString& altOrigin, bool archive) + : QTreeWidgetItem(columns) + { + setData(0, FILENAME_USERROLE, fileName); + setData(0, ALT_ORIGIN_USERROLE, altOrigin); + setData(0, ARCHIVE_USERROLE, archive); + setData(0, INDEX_USERROLE, index); + setData(0, HAS_ALTS_USERROLE, hasAltOrigins); + + if (archive) { + QFont f = font(0); + f.setItalic(true); + + for (int i=0; i); + return data(0, INDEX_USERROLE).toUInt(); + } + + bool canHide() const + { + return canHideFile(isArchive(), fileName()); + } + + bool canUnhide() const + { + return canUnhideFile(isArchive(), fileName()); + } + + bool canOpen() const + { + return canOpenFile(isArchive(), fileName()); + } + + bool canPreview(PluginContainer* pluginContainer) const + { + return canPreviewFile(pluginContainer, isArchive(), fileName()); + } +}; + + ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), @@ -793,20 +913,14 @@ QTreeWidgetItem* ModInfoDialog::createOverwriteItem( const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()); - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - setConflictItem(item, index, fileName, true, origin, archive); - - return item; + return new ConflictItem(fields, index, fileName, true, origin, archive); } QTreeWidgetItem* ModInfoDialog::createNoConflictItem( FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName) { - QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); - setConflictItem(item, index, fileName, false, "", archive); - - return item; + return new ConflictItem({relativeName}, index, fileName, false, "", archive); } QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( @@ -818,10 +932,8 @@ QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( QStringList fields(relativeName); fields.append(ToQString(realOrigin.getName())); - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - setConflictItem(item, index, fileName, true, ToQString(realOrigin.getName()), archive); - - return item; + return new ConflictItem( + fields, index, fileName, true, ToQString(realOrigin.getName()), archive); } QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( @@ -930,61 +1042,8 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( return nullptr; } - QTreeWidgetItem* item = new QTreeWidgetItem; - item->setText(0, before); - item->setText(1, relativeName); - item->setText(2, after); - - setConflictItem(item, index, fileName, hasAlts, "", archive); - - return item; -} - -void ModInfoDialog::setConflictItem( - QTreeWidgetItem* item, FileEntry::Index index, - const QString& fileName, bool hasAltOrigins, const QString& altOrigin, - bool archive) const -{ - item->setData(0, FILENAME_USERROLE, fileName); - item->setData(0, ALT_ORIGIN_USERROLE, altOrigin); - item->setData(0, ARCHIVE_USERROLE, archive); - item->setData(0, INDEX_USERROLE, index); - item->setData(0, HAS_ALTS_USERROLE, hasAltOrigins); - - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - - for (int i=0; icolumnCount(); ++i) { - item->setFont(i, font); - } - } -} - -QString ModInfoDialog::conflictFileName(const QTreeWidgetItem* conflictItem) const -{ - return conflictItem->data(0, FILENAME_USERROLE).toString(); -} - -QString ModInfoDialog::conflictAltOrigin(const QTreeWidgetItem* conflictItem) const -{ - return conflictItem->data(0, ALT_ORIGIN_USERROLE).toString(); -} - -bool ModInfoDialog::conflictHasAlts(const QTreeWidgetItem* conflictItem) const -{ - return conflictItem->data(0, HAS_ALTS_USERROLE).toBool(); -} - -bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const -{ - return conflictItem->data(0, ARCHIVE_USERROLE).toBool(); -} - -FileEntry::Index ModInfoDialog::conflictFileIndex(const QTreeWidgetItem* conflictItem) const -{ - static_assert(std::is_same_v); - return conflictItem->data(0, INDEX_USERROLE).toUInt(); + return new ConflictItem( + {before, relativeName, after}, index, fileName, hasAlts, "", archive); } void ModInfoDialog::refreshFiles() @@ -1814,7 +1873,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (!canPreviewFile(false, fileName)) { + if (!canPreviewFile(m_PluginContainer, false, fileName)) { enablePreview = false; } @@ -1936,11 +1995,13 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - const auto origin = conflictAltOrigin(item); + if (auto* ci=dynamic_cast(item)) { + const auto origin = ci->altOrigin(); - if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } } } @@ -1980,21 +2041,26 @@ void ModInfoDialog::changeConflictItemsVisibility( break; } + const auto* ci = dynamic_cast(item); + if (!ci) { + continue; + } + auto result = FileRenamer::RESULT_CANCEL; if (visible) { - if (!canUnhideConflictItem(item)) { + if (!ci->canUnhide()) { qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; continue; } - result = unhideFile(renamer, conflictFileName(item)); + result = unhideFile(renamer, ci->fileName()); } else { - if (!canHideConflictItem(item)) { + if (!ci->canHide()) { qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; continue; } - result = hideFile(renamer, conflictFileName(item)); + result = hideFile(renamer, ci->fileName()); } switch (result) { @@ -2033,8 +2099,8 @@ void ModInfoDialog::openConflictItems(const QList& items) // the menu item is only shown for a single selection, but handle all of them // in case this changes for (auto* item : items) { - if (item) { - m_OrganizerCore->executeFileVirtualized(this, conflictFileName(item)); + if (auto* ci=dynamic_cast(item)) { + m_OrganizerCore->executeFileVirtualized(this, ci->fileName()); } } } @@ -2044,78 +2110,12 @@ void ModInfoDialog::previewConflictItems(const QList& items) // the menu item is only shown for a single selection, but handle all of them // in case this changes for (auto* item : items) { - if (item) { - m_OrganizerCore->previewFileWithAlternatives(this, conflictFileName(item)); + if (auto* ci=dynamic_cast(item)) { + m_OrganizerCore->previewFileWithAlternatives(this, ci->fileName()); } } } -bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) const -{ - if (isArchive) { - return false; - } - - const auto ext = QFileInfo(filename).suffix(); - return m_PluginContainer->previewGenerator().previewSupported(ext); -} - -bool ModInfoDialog::canOpenFile(bool isArchive, const QString&) const -{ - // can open anything as long as it's not in an archive - return !isArchive; -} - -bool ModInfoDialog::canHideFile(bool isArchive, const QString& filename) const -{ - if (isArchive) { - // can't hide files from archives - return false; - } - - if (filename.endsWith(ModInfo::s_HiddenExt)) { - // already hidden - return false; - } - - return true; -} - -bool ModInfoDialog::canUnhideFile(bool isArchive, const QString& filename) const -{ - if (isArchive) { - // can't unhide files from archives - return false; - } - - if (!filename.endsWith(ModInfo::s_HiddenExt)) { - // already visible - return false; - } - - return true; -} - -bool ModInfoDialog::canHideConflictItem(const QTreeWidgetItem* item) const -{ - return canHideFile(conflictIsArchive(item), conflictFileName(item)); -} - -bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const -{ - return canUnhideFile(conflictIsArchive(item), conflictFileName(item)); -} - -bool ModInfoDialog::canOpenConflictItem(const QTreeWidgetItem* item) const -{ - return canOpenFile(conflictIsArchive(item), conflictFileName(item)); -} - -bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const -{ - return canPreviewFile(conflictIsArchive(item), conflictFileName(item)); -} - void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) { showConflictMenu(pos, ui->overwriteTree); @@ -2212,16 +2212,16 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( if (selection.size() == 1) { // this is a single selection - const auto* item = selection[0]; - if (!item) { + const auto* ci = dynamic_cast(selection[0]); + if (!ci) { return {}; } - enableHide = canHideConflictItem(item); - enableUnhide = canUnhideConflictItem(item); - enableOpen = canOpenConflictItem(item); - enablePreview = canPreviewConflictItem(item); - enableGoto = conflictHasAlts(item); + enableHide = ci->canHide(); + enableUnhide = ci->canUnhide(); + enableOpen = ci->canOpen(); + enablePreview = ci->canPreview(m_PluginContainer); + enableGoto = ci->hasAlts(); } else { // this is a multiple selection, don't show open/preview so users don't open @@ -2239,17 +2239,19 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( enableUnhide = false; for (const auto* item : selection) { - if (canHideConflictItem(item)) { - enableHide = true; - } + if (const auto* ci=dynamic_cast(item)) { + if (ci->canHide()) { + enableHide = true; + } - if (canUnhideConflictItem(item)) { - enableUnhide = true; - } + if (ci->canUnhide()) { + enableUnhide = true; + } - if (enableHide && enableUnhide && enableGoto) { - // found all, no need to check more - break; + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more + break; + } } } } @@ -2287,12 +2289,12 @@ std::vector ModInfoDialog::createGotoActions(const QList(selection[0]); if (!item) { return {}; } - auto file = m_Origin->findFile(conflictFileIndex(item)); + auto file = m_Origin->findFile(item->fileIndex()); if (!file) { return {}; } @@ -2329,11 +2331,13 @@ std::vector ModInfoDialog::createGotoActions(const QList(item)) { + const auto origin = ci->altOrigin(); - if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } } } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 8510c96d..c4f65d8a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -460,28 +460,12 @@ private: const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); - void setConflictItem( - QTreeWidgetItem* item, FileEntry::Index index, - const QString& fileName, bool hasAltOrigins, const QString& altOrigin, - bool archive) const; - - QString conflictFileName(const QTreeWidgetItem* conflictItem) const; - QString conflictAltOrigin(const QTreeWidgetItem* conflictItem) const; - bool conflictHasAlts(const QTreeWidgetItem* conflictItem) const; - bool conflictIsArchive(const QTreeWidgetItem* conflictItem) const; - FileEntry::Index conflictFileIndex(const QTreeWidgetItem* conflictItem) const; - void restoreTabState(const QByteArray &state); void restoreConflictsState(const QByteArray &state); QByteArray saveTabState() const; QByteArray saveConflictsState() const; - bool canHideConflictItem(const QTreeWidgetItem* item) const; - bool canUnhideConflictItem(const QTreeWidgetItem* item) const; - bool canOpenConflictItem(const QTreeWidgetItem* item) const; - bool canPreviewConflictItem(const QTreeWidgetItem* item) const; - void changeFiletreeVisibility(bool visible); void openConflictItems(const QList& items); @@ -489,11 +473,6 @@ private: void changeConflictItemsVisibility( const QList& items, bool visible); - bool canPreviewFile(bool isArchive, const QString& filename) const; - bool canOpenFile(bool isArchive, const QString& filename) const; - bool canHideFile(bool isArchive, const QString& filename) const; - bool canUnhideFile(bool isArchive, const QString& filename) const; - void showConflictMenu(const QPoint &pos, QTreeWidget* tree); ConflictActions createConflictMenuActions( -- cgit v1.3.1 From 75f59b68a5d82139b97dae9a7ab762f563a55676 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 4 Jun 2019 23:42:20 -0400 Subject: natural sort for conflict items --- src/modinfodialog.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ce15f5db..2be8e481 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -381,6 +381,19 @@ bool canUnhideFile(bool isArchive, const QString& filename) } +int naturalCompare(const QString& a, const QString& b) +{ + static QCollator c = []{ + QCollator c; + c.setNumericMode(true); + c.setCaseSensitivity(Qt::CaseInsensitive); + return c; + }(); + + return c.compare(a, b); +} + + class ConflictItem : public QTreeWidgetItem { public: @@ -456,6 +469,19 @@ public: { return canPreviewFile(pluginContainer, isArchive(), fileName()); } + + bool operator<(const QTreeWidgetItem& other) const + { + const int column = treeWidget()->sortColumn(); + + if (column >= columnCount() || column >= other.columnCount()) { + // shouldn't happen + qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count"; + return false; + } + + return (naturalCompare(text(column), other.text(column)) < 0); + } }; -- 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') 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') 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 89aa616a61d41d65698d9abcd914e5e9acfc3131 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 09:59:16 -0400 Subject: clarified some comments --- src/mainwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a20f49f8..911d0ff1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -849,7 +849,7 @@ void MainWindow::updateProblemsButton() const std::size_t numProblems = checkForProblems(); - // starting icon + // original icon without a count painted on it const QIcon original = m_originalNotificationIcon.isNull() ? QIcon(DefaultIconName) : m_originalNotificationIcon; @@ -860,8 +860,8 @@ void MainWindow::updateProblemsButton() ui->actionNotifications->setToolTip(tr("There are notifications to read")); // 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 + // makes sure the pixmap is exactly 64x64 by requesting the icon that's + // as close to 64x64 as possible, and then scaling it up if it's too small QPixmap merged = original.pixmap(64, 64).scaled(64, 64); { -- 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') 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') 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 dc8256bae1f62cf3fad3b7dd60f4befc60edca03 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 12 Jun 2019 15:41:15 -0400 Subject: added notifications label, unused --- src/statusbar.cpp | 12 +++++++----- src/statusbar.h | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 6af10c56..f2af0272 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -2,15 +2,17 @@ #include "nexusinterface.h" #include "settings.h" -StatusBar::StatusBar(QStatusBar* bar) - : m_bar(bar), m_api(new QLabel), m_progress(new QProgressBar) +StatusBar::StatusBar(QStatusBar* bar) : + m_bar(bar), m_notifications(new QLabel), m_progress(new QProgressBar), + m_api(new QLabel) { + m_bar->addPermanentWidget(m_notifications); + m_bar->addPermanentWidget(m_progress); + m_bar->addPermanentWidget(m_api); + m_progress->setTextVisible(true); m_progress->setRange(0, 100); - 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; }"); diff --git a/src/statusbar.h b/src/statusbar.h index 47a0e56d..99de8f16 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -19,8 +19,9 @@ public: private: QStatusBar* m_bar; - QLabel* m_api; + QLabel* m_notifications; QProgressBar* m_progress; + QLabel* m_api; }; #endif // MO_STATUSBAR_H -- cgit v1.3.1 From a418d028b209ab8e6b75b77b405e9babe48a19dd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 12 Jun 2019 16:16:17 -0400 Subject: max width for progress bar notification icon in status bar --- src/mainwindow.cpp | 7 ++++++- src/statusbar.cpp | 15 ++++++++++++--- src/statusbar.h | 5 +++-- 3 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6b3ee11f..c39ac749 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -243,7 +243,7 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->logList, SLOT(scrollToBottom())); - m_statusBar.reset(new StatusBar(statusBar())); + m_statusBar.reset(new StatusBar(statusBar(), ui->actionNotifications)); updateProblemsButton(); @@ -899,6 +899,11 @@ void MainWindow::updateProblemsButton() button->setIcon(final); } } + + // updating the status bar, may be null very early when MO is starting + if (m_statusBar) { + m_statusBar->setHasNotifications(numProblems > 0); + } } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index f2af0272..a34500b0 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -2,16 +2,20 @@ #include "nexusinterface.h" #include "settings.h" -StatusBar::StatusBar(QStatusBar* bar) : - m_bar(bar), m_notifications(new QLabel), m_progress(new QProgressBar), +StatusBar::StatusBar(QStatusBar* bar, QAction* notifications) : + m_bar(bar), m_progress(new QProgressBar), m_notifications(new QToolButton), m_api(new QLabel) { - m_bar->addPermanentWidget(m_notifications); m_bar->addPermanentWidget(m_progress); + m_bar->addPermanentWidget(m_notifications); m_bar->addPermanentWidget(m_api); m_progress->setTextVisible(true); m_progress->setRange(0, 100); + m_progress->setMaximumWidth(100); + + m_notifications->setDefaultAction(notifications); + m_notifications->setAutoRaise(true); m_api->setObjectName("apistats"); m_api->setStyleSheet("QLabel{ padding-left: 0.1em; padding-right: 0.1em; }"); @@ -31,6 +35,11 @@ void StatusBar::setProgress(int percent) } } +void StatusBar::setHasNotifications(bool b) +{ + m_notifications->setVisible(b); +} + void StatusBar::updateAPI(const APIStats& stats, const APIUserAccount& user) { m_api->setText( diff --git a/src/statusbar.h b/src/statusbar.h index 99de8f16..bf14ba57 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -11,16 +11,17 @@ class Settings; class StatusBar { public: - StatusBar(QStatusBar* bar); + StatusBar(QStatusBar* bar, QAction* notifications); void setProgress(int percent); + void setHasNotifications(bool b); void updateAPI(const APIStats& stats, const APIUserAccount& user); void checkSettings(const Settings& settings); private: QStatusBar* m_bar; - QLabel* m_notifications; QProgressBar* m_progress; + QToolButton* m_notifications; QLabel* m_api; }; -- cgit v1.3.1 From dc08c12fe8cde84c7cf507954d090d572ac865d5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 12 Jun 2019 17:08:09 -0400 Subject: fixed progress bar not updating --- src/statusbar.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/statusbar.cpp b/src/statusbar.cpp index a34500b0..1c62b38e 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -29,7 +29,8 @@ void StatusBar::setProgress(int percent) { if (percent < 0 || percent >= 100) { m_progress->setVisible(false); - } else if (!m_progress->isVisible()) { + } else { + m_bar->showMessage(QObject::tr("Loading...")); m_progress->setVisible(true); m_progress->setValue(percent); } -- cgit v1.3.1 From 16d6a4c528fd226ac5bf9a2ff54911c567d5bca0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 12 Jun 2019 18:04:42 -0400 Subject: added a StatusBarNotifications class to handle icon, text and double click --- src/mainwindow.cpp | 2 +- src/statusbar.cpp | 46 ++++++++++++++++++++++++++++++++++++---------- src/statusbar.h | 24 +++++++++++++++++++++--- 3 files changed, 58 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c39ac749..6d65d64c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -902,7 +902,7 @@ void MainWindow::updateProblemsButton() // updating the status bar, may be null very early when MO is starting if (m_statusBar) { - m_statusBar->setHasNotifications(numProblems > 0); + m_statusBar->updateNotifications(numProblems > 0); } } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 1c62b38e..9ff1c476 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -2,9 +2,9 @@ #include "nexusinterface.h" #include "settings.h" -StatusBar::StatusBar(QStatusBar* bar, QAction* notifications) : - m_bar(bar), m_progress(new QProgressBar), m_notifications(new QToolButton), - m_api(new QLabel) +StatusBar::StatusBar(QStatusBar* bar, QAction* actionNotifications) : + m_bar(bar), m_notifications(new StatusBarNotifications(actionNotifications)), + m_progress(new QProgressBar), m_api(new QLabel) { m_bar->addPermanentWidget(m_progress); m_bar->addPermanentWidget(m_notifications); @@ -12,13 +12,11 @@ StatusBar::StatusBar(QStatusBar* bar, QAction* notifications) : m_progress->setTextVisible(true); m_progress->setRange(0, 100); - m_progress->setMaximumWidth(100); - - m_notifications->setDefaultAction(notifications); - m_notifications->setAutoRaise(true); + m_progress->setMaximumWidth(150); + m_progress->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); m_api->setObjectName("apistats"); - m_api->setStyleSheet("QLabel{ padding-left: 0.1em; padding-right: 0.1em; }"); + m_api->setStyleSheet("QLabel{ padding: 0.1em 0 0.1em 0; }"); m_bar->clearMessage(); setProgress(-1); @@ -28,6 +26,7 @@ StatusBar::StatusBar(QStatusBar* bar, QAction* notifications) : void StatusBar::setProgress(int percent) { if (percent < 0 || percent >= 100) { + m_bar->clearMessage(); m_progress->setVisible(false); } else { m_bar->showMessage(QObject::tr("Loading...")); @@ -36,9 +35,9 @@ void StatusBar::setProgress(int percent) } } -void StatusBar::setHasNotifications(bool b) +void StatusBar::updateNotifications(bool hasNotifications) { - m_notifications->setVisible(b); + m_notifications->update(hasNotifications); } void StatusBar::updateAPI(const APIStats& stats, const APIUserAccount& user) @@ -78,3 +77,30 @@ void StatusBar::checkSettings(const Settings& settings) { m_api->setVisible(!settings.hideAPICounter()); } + + +StatusBarNotifications::StatusBarNotifications(QAction* action) + : m_action(action), m_icon(new QLabel), m_text(new QLabel) +{ + setLayout(new QHBoxLayout); + layout()->setContentsMargins(0, 0, 0, 0); + layout()->addWidget(m_icon); + layout()->addWidget(m_text); +} + +void StatusBarNotifications::update(bool hasNotifications) +{ + if (hasNotifications) { + m_icon->setPixmap(m_action->icon().pixmap(16, 16)); + m_text->setText(QObject::tr("Notifications")); + } + + setVisible(hasNotifications); +} + +void StatusBarNotifications::mouseDoubleClickEvent(QMouseEvent* e) +{ + if (m_action->isEnabled()) { + m_action->trigger(); + } +} diff --git a/src/statusbar.h b/src/statusbar.h index bf14ba57..8edfd037 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -8,20 +8,38 @@ struct APIStats; class APIUserAccount; class Settings; + +class StatusBarNotifications : public QWidget +{ +public: + StatusBarNotifications(QAction* action); + + void update(bool hasNotifications); + +protected: + void mouseDoubleClickEvent(QMouseEvent* e) override; + +private: + QAction* m_action; + QLabel* m_icon; + QLabel* m_text; +}; + + class StatusBar { public: - StatusBar(QStatusBar* bar, QAction* notifications); + StatusBar(QStatusBar* bar, QAction* actionNotifications); void setProgress(int percent); - void setHasNotifications(bool b); + void updateNotifications(bool hasNotifications); void updateAPI(const APIStats& stats, const APIUserAccount& user); void checkSettings(const Settings& settings); private: QStatusBar* m_bar; + StatusBarNotifications* m_notifications; QProgressBar* m_progress; - QToolButton* m_notifications; QLabel* m_api; }; -- cgit v1.3.1 From 4a7a77480bfd44c7780e9e37bceadac7e367c062 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 12 Jun 2019 19:12:34 -0400 Subject: changed StatusBarNotifications to a generic StatusBarAction added update notification to statusbar --- src/mainwindow.cpp | 16 ++++++---------- src/statusbar.cpp | 52 +++++++++++++++++++++++++++++++++++++++------------- src/statusbar.h | 19 ++++++++++++------- 3 files changed, 57 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6d65d64c..2497b05d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -243,7 +243,7 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->logList, SLOT(scrollToBottom())); - m_statusBar.reset(new StatusBar(statusBar(), ui->actionNotifications)); + m_statusBar.reset(new StatusBar(statusBar(), ui)); updateProblemsButton(); @@ -571,7 +571,7 @@ void MainWindow::updateWindowTitle(const APIUserAccount& user) void MainWindow::onRequestsChanged(const APIStats& stats, const APIUserAccount& user) { - m_statusBar->updateAPI(stats, user); + m_statusBar->setAPI(stats, user); } @@ -902,7 +902,7 @@ void MainWindow::updateProblemsButton() // updating the status bar, may be null very early when MO is starting if (m_statusBar) { - m_statusBar->updateNotifications(numProblems > 0); + m_statusBar->setNotifications(numProblems > 0); } } @@ -5593,13 +5593,9 @@ void MainWindow::openDataOriginExplorer_clicked() void MainWindow::updateAvailable() { - for (QAction *action : ui->toolBar->actions()) { - if (action->text() == tr("Update")) { - action->setEnabled(true); - action->setToolTip(tr("Update available")); - break; - } - } + ui->actionUpdate->setEnabled(true); + ui->actionUpdate->setToolTip(tr("Update available")); + m_statusBar->setUpdateAvailable(true); } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 9ff1c476..68c0642a 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -1,13 +1,17 @@ #include "statusbar.h" #include "nexusinterface.h" #include "settings.h" +#include "ui_mainwindow.h" -StatusBar::StatusBar(QStatusBar* bar, QAction* actionNotifications) : - m_bar(bar), m_notifications(new StatusBarNotifications(actionNotifications)), - m_progress(new QProgressBar), m_api(new QLabel) +StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : + m_bar(bar), m_progress(new QProgressBar), + m_notifications(new StatusBarAction(ui->actionNotifications)), + m_update(new StatusBarAction(ui->actionUpdate)), + m_api(new QLabel) { m_bar->addPermanentWidget(m_progress); m_bar->addPermanentWidget(m_notifications); + m_bar->addPermanentWidget(m_update); m_bar->addPermanentWidget(m_api); m_progress->setTextVisible(true); @@ -15,12 +19,15 @@ StatusBar::StatusBar(QStatusBar* bar, QAction* actionNotifications) : m_progress->setMaximumWidth(150); m_progress->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + m_update->set(false); + m_notifications->set(false); + m_api->setObjectName("apistats"); m_api->setStyleSheet("QLabel{ padding: 0.1em 0 0.1em 0; }"); m_bar->clearMessage(); setProgress(-1); - updateAPI({}, {}); + setAPI({}, {}); } void StatusBar::setProgress(int percent) @@ -35,12 +42,12 @@ void StatusBar::setProgress(int percent) } } -void StatusBar::updateNotifications(bool hasNotifications) +void StatusBar::setNotifications(bool hasNotifications) { - m_notifications->update(hasNotifications); + m_notifications->set(hasNotifications); } -void StatusBar::updateAPI(const APIStats& stats, const APIUserAccount& user) +void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) { m_api->setText( QString("API: Q: %1 | D: %2 | H: %3") @@ -73,13 +80,18 @@ void StatusBar::updateAPI(const APIStats& stats, const APIUserAccount& user) m_api->setAutoFillBackground(true); } +void StatusBar::setUpdateAvailable(bool b) +{ + m_update->set(b); +} + void StatusBar::checkSettings(const Settings& settings) { m_api->setVisible(!settings.hideAPICounter()); } -StatusBarNotifications::StatusBarNotifications(QAction* action) +StatusBarAction::StatusBarAction(QAction* action) : m_action(action), m_icon(new QLabel), m_text(new QLabel) { setLayout(new QHBoxLayout); @@ -88,19 +100,33 @@ StatusBarNotifications::StatusBarNotifications(QAction* action) layout()->addWidget(m_text); } -void StatusBarNotifications::update(bool hasNotifications) +void StatusBarAction::set(bool visible) { - if (hasNotifications) { + if (visible) { m_icon->setPixmap(m_action->icon().pixmap(16, 16)); - m_text->setText(QObject::tr("Notifications")); + m_text->setText(cleanupActionText(m_action->text())); } - setVisible(hasNotifications); + setVisible(visible); } -void StatusBarNotifications::mouseDoubleClickEvent(QMouseEvent* e) +void StatusBarAction::mouseDoubleClickEvent(QMouseEvent* e) { if (m_action->isEnabled()) { m_action->trigger(); } } + +QString StatusBarAction::cleanupActionText(const QString& original) const +{ + QString s = original; + + s.replace(QRegExp("\\&([^&])"), "\\1"); // &Item -> Item + s.replace("&&", "&"); // &&Item -> &Item + + if (s.endsWith("...")) { + s = s.left(s.size() - 3); + } + + return s; +} diff --git a/src/statusbar.h b/src/statusbar.h index 8edfd037..2baf12ee 100644 --- a/src/statusbar.h +++ b/src/statusbar.h @@ -7,14 +7,15 @@ struct APIStats; class APIUserAccount; class Settings; +namespace Ui { class MainWindow; } -class StatusBarNotifications : public QWidget +class StatusBarAction : public QWidget { public: - StatusBarNotifications(QAction* action); + StatusBarAction(QAction* action); - void update(bool hasNotifications); + void set(bool visible); protected: void mouseDoubleClickEvent(QMouseEvent* e) override; @@ -23,23 +24,27 @@ private: QAction* m_action; QLabel* m_icon; QLabel* m_text; + + QString cleanupActionText(const QString& s) const; }; class StatusBar { public: - StatusBar(QStatusBar* bar, QAction* actionNotifications); + StatusBar(QStatusBar* bar, Ui::MainWindow* ui); void setProgress(int percent); - void updateNotifications(bool hasNotifications); - void updateAPI(const APIStats& stats, const APIUserAccount& user); + void setNotifications(bool hasNotifications); + void setAPI(const APIStats& stats, const APIUserAccount& user); + void setUpdateAvailable(bool b); void checkSettings(const Settings& settings); private: QStatusBar* m_bar; - StatusBarNotifications* m_notifications; QProgressBar* m_progress; + StatusBarAction* m_notifications; + StatusBarAction* m_update; QLabel* m_api; }; -- cgit v1.3.1 From ba0b2c2a29d32d290a16d88ff71475dbd61a8ffd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 13 Jun 2019 23:26:46 -0400 Subject: switched to stylesheet for api colors, palette doesn't seem to survive hiding/unhiding very well --- src/statusbar.cpp | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 68c0642a..4effa6a3 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -23,7 +23,6 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : m_notifications->set(false); m_api->setObjectName("apistats"); - m_api->setStyleSheet("QLabel{ padding: 0.1em 0 0.1em 0; }"); m_bar->clearMessage(); setProgress(-1); @@ -55,28 +54,36 @@ void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) .arg(user.limits().remainingDailyRequests) .arg(user.limits().remainingHourlyRequests)); - QColor textColor; - QColor backgroundColor; + QString textColor; + QString backgroundColor; if (user.type() == APIUserAccountTypes::None) { - backgroundColor = Qt::transparent; + backgroundColor = "transparent"; } else if (user.remainingRequests() > 300) { textColor = "white"; - backgroundColor = Qt::darkGreen; + backgroundColor = "darkgreen"; } else if (user.remainingRequests() < 150) { textColor = "white"; - backgroundColor = Qt::darkRed; + backgroundColor = "darkred"; } else { textColor = "black"; - backgroundColor = Qt::darkYellow; + backgroundColor = "rgb(226, 192, 0)"; // yellow } - QPalette palette = m_api->palette(); + m_api->setStyleSheet(QString(R"( + QLabel + { + padding-left: 0.1em; + padding-right: 0.1em; + padding-top: 0; + padding-bottom: 0; + color: %1; + background-color: %2; + } + )") + .arg(textColor) + .arg(backgroundColor)); - palette.setColor(QPalette::WindowText, textColor); - palette.setColor(QPalette::Background, backgroundColor); - - m_api->setPalette(palette); m_api->setAutoFillBackground(true); } -- 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') 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 616925ebbb8e916119f8dbee0c1f0cb97b14a68b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 01:38:07 -0400 Subject: moved api user account classes to their own files api label in status bar: - now shows when not logged in - changed some of the colour thresholds to correspond to real throttling numbers make sure the api key is also cleared from the access manager when clearing from the settings removed unused bool m_ValidateAttempted in NXMAccessManager update the window title and status bar api label with correct values on startup, which fixes incorrect values when "restarting" MO after changing nexus settings --- src/CMakeLists.txt | 3 ++ src/apiuseraccount.cpp | 67 ++++++++++++++++++++++++ src/apiuseraccount.h | 129 ++++++++++++++++++++++++++++++++++++++++++++++ src/mainwindow.cpp | 13 +++-- src/nexusinterface.cpp | 78 ++++------------------------ src/nexusinterface.h | 131 ++--------------------------------------------- src/nxmaccessmanager.cpp | 17 ++++-- src/nxmaccessmanager.h | 7 +-- src/settingsdialog.cpp | 6 ++- src/statusbar.cpp | 34 +++++++----- 10 files changed, 263 insertions(+), 222 deletions(-) create mode 100644 src/apiuseraccount.cpp create mode 100644 src/apiuseraccount.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 71f87a8a..5623a851 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -117,6 +117,7 @@ SET(organizer_SRCS forcedloaddialogwidget.cpp filterwidget.cpp statusbar.cpp + apiuseraccount.cpp shared/windows_error.cpp shared/error_report.cpp @@ -215,6 +216,7 @@ SET(organizer_HDRS forcedloaddialogwidget.h filterwidget.h statusbar.h + apiuseraccount.h shared/windows_error.h shared/error_report.h @@ -299,6 +301,7 @@ set(core nxmaccessmanager organizercore organizerproxy + apiuseraccount ) set(dialogs diff --git a/src/apiuseraccount.cpp b/src/apiuseraccount.cpp new file mode 100644 index 00000000..b901e41a --- /dev/null +++ b/src/apiuseraccount.cpp @@ -0,0 +1,67 @@ +#include "apiuseraccount.h" + +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 std::max( + m_limits.remainingDailyRequests, + m_limits.remainingHourlyRequests); +} + +bool APIUserAccount::shouldThrottle() const +{ + return (remainingRequests() < ThrottleThreshold); +} + +bool APIUserAccount::exhausted() const +{ + return (remainingRequests() <= 0); +} diff --git a/src/apiuseraccount.h b/src/apiuseraccount.h new file mode 100644 index 00000000..8a238d71 --- /dev/null +++ b/src/apiuseraccount.h @@ -0,0 +1,129 @@ +#ifndef APIUSERACCOUNT_H +#define APIUSERACCOUNT_H + +#include + +/** +* 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 = 200; + + 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; +}; + +#endif // APIUSERACCOUNT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c255759b..5018479d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -225,8 +225,17 @@ MainWindow::MainWindow(QSettings &initSettings QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory()); QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); + ui->setupUi(this); - updateWindowTitle({}); + + { + auto* ni = NexusInterface::instance(&m_PluginContainer); + + updateWindowTitle(ni->getAPIUserAccount()); + + m_statusBar.reset(new StatusBar(statusBar(), ui)); + m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); + } languageChange(m_OrganizerCore.settings().language()); @@ -244,8 +253,6 @@ MainWindow::MainWindow(QSettings &initSettings connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->logList, SLOT(scrollToBottom())); - m_statusBar.reset(new StatusBar(statusBar(), ui)); - updateProblemsButton(); setupToolbar(); diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 8362143a..ee9acf2c 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -49,71 +49,6 @@ void throttledWarning(const APIUserAccount& user) } -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) @@ -325,7 +260,7 @@ void NexusInterface::loginCompleted() void NexusInterface::setUserAccount(const APIUserAccount& user) { m_User = user; - emit requestsChanged(stats(), m_User); + emit requestsChanged(getAPIStats(), m_User); } void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) @@ -884,7 +819,7 @@ void NexusInterface::requestFinished(std::list::iterator iter) qWarning("All API requests have been consumed and are now being denied."); } - emit requestsChanged(stats(), m_User); + emit requestsChanged(getAPIStats(), m_User); qWarning("Error: %s", reply->errorString().toUtf8().constData()); } else { qWarning("request failed: %s", reply->errorString().toUtf8().constData()); @@ -960,7 +895,7 @@ void NexusInterface::requestFinished(std::list::iterator iter) } m_User.limits(parseLimits(reply)); - emit requestsChanged(stats(), m_User); + emit requestsChanged(getAPIStats(), 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")); } @@ -1017,7 +952,12 @@ void NexusInterface::requestTimeout() } } -APIStats NexusInterface::stats() const +APIUserAccount NexusInterface::getAPIUserAccount() const +{ + return m_User; +} + +APIStats NexusInterface::getAPIStats() const { APIStats stats; stats.requestsQueued = m_RequestQueue.size(); diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 6c4e8d11..6e768149 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -20,6 +20,8 @@ along with Mod Organizer. If not, see . #ifndef NEXUSINTERFACE_H #define NEXUSINTERFACE_H +#include "apiuseraccount.h" + #include #include #include @@ -40,130 +42,6 @@ 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. @@ -521,6 +399,9 @@ public: std::vector> getGameChoices(const MOBase::IPluginGame *game); + APIUserAccount getAPIUserAccount() const; + APIStats getAPIStats() const; + public: /** @@ -667,8 +548,6 @@ private: MOBase::VersionInfo m_MOVersion; PluginContainer *m_PluginContainer; APIUserAccount m_User; - - APIStats stats() const; }; #endif // NEXUSINTERFACE_H diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 5886d3ee..ee6c03f1 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -190,6 +190,7 @@ void NXMAccessManager::apiCheck(const QString &apiKey, bool force) emit validateSuccessful(false); return; } + m_ApiKey = apiKey; startValidationCheck(); } @@ -218,6 +219,13 @@ QString NXMAccessManager::apiKey() const return m_ApiKey; } +void NXMAccessManager::clearApiKey() +{ + m_ApiKey = ""; + m_ValidateState = VALIDATE_NOT_VALID; + + emit credentialsReceived(APIUserAccount()); +} void NXMAccessManager::validateTimeout() { @@ -233,7 +241,6 @@ void NXMAccessManager::validateTimeout() if (m_ValidateReply != nullptr) { m_ValidateReply->deleteLater(); m_ValidateReply = nullptr; - m_ValidateAttempted = false; // this usually means we might have success later } emit validateFailed(tr("There was a timeout during the request")); @@ -280,17 +287,21 @@ void NXMAccessManager::validateFinished() QString name = credentialsData.value("name").toString(); bool premium = credentialsData.value("is_premium").toBool(); - emit credentialsReceived(APIUserAccount() + const auto user = APIUserAccount() .id(QString("%1").arg(id)) .name(name) .type(premium ? APIUserAccountTypes::Premium : APIUserAccountTypes::Regular) - .limits(NexusInterface::parseLimits(m_ValidateReply))); + .limits(NexusInterface::parseLimits(m_ValidateReply)); + + + emit credentialsReceived(user); m_ValidateReply->deleteLater(); m_ValidateReply = nullptr; m_ValidateState = VALIDATE_VALID; emit validateSuccessful(true); + } else { m_ApiKey.clear(); m_ValidateState = VALIDATE_NOT_VALID; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 1d23faf9..1bdeae40 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #ifndef NXMACCESSMANAGER_H #define NXMACCESSMANAGER_H +#include "apiuseraccount.h" #include #include #include @@ -28,8 +29,6 @@ along with Mod Organizer. If not, see . namespace MOBase { class IPluginGame; } -class APIUserAccount; - /** * @brief access manager extended to handle nxm links **/ @@ -56,6 +55,7 @@ public: QString userAgent(const QString &subModule = QString()) const; QString apiKey() const; + void clearApiKey(); void startValidationCheck(); @@ -94,7 +94,6 @@ protected: QIODevice *device); private: - QTimer m_ValidateTimeout; QNetworkReply *m_ValidateReply; QProgressDialog *m_ProgressDialog { nullptr }; @@ -103,7 +102,6 @@ private: QString m_ApiKey; - bool m_ValidateAttempted; enum { VALIDATE_NOT_CHECKED, VALIDATE_CHECKING, @@ -112,7 +110,6 @@ private: VALIDATE_REFUSED, VALIDATE_VALID } m_ValidateState = VALIDATE_NOT_CHECKED; - }; #endif // NXMACCESSMANAGER_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index b3e8c8a7..95e4ceb0 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -511,6 +511,9 @@ bool SettingsDialog::clearKey() m_KeyCleared = true; const auto ret = m_settings->clearNexusApiKey(); updateNexusButtons(); + + NexusInterface::instance(m_PluginContainer)->getAccessManager()->clearApiKey(); + return ret; } @@ -522,8 +525,7 @@ void SettingsDialog::testApiKey() return; } - auto* am = NexusInterface::instance(m_PluginContainer)->getAccessManager(); - am->apiCheck(key, true); + NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(key, true); } void SettingsDialog::updateNexusButtons() diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 4effa6a3..712eb005 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -48,28 +48,34 @@ void StatusBar::setNotifications(bool hasNotifications) void StatusBar::setAPI(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)); - + QString text; QString textColor; QString backgroundColor; if (user.type() == APIUserAccountTypes::None) { - backgroundColor = "transparent"; - } else if (user.remainingRequests() > 300) { - textColor = "white"; - backgroundColor = "darkgreen"; - } else if (user.remainingRequests() < 150) { + text = "API: not logged in"; textColor = "white"; - backgroundColor = "darkred"; + backgroundColor = "transparent"; } else { - textColor = "black"; - backgroundColor = "rgb(226, 192, 0)"; // yellow + text = QString("API: Queued: %1 | Daily: %2 | Hourly: %3") + .arg(stats.requestsQueued) + .arg(user.limits().remainingDailyRequests) + .arg(user.limits().remainingHourlyRequests); + + if (user.remainingRequests() > 500) { + textColor = "white"; + backgroundColor = "darkgreen"; + } else if (user.remainingRequests() > 200) { + textColor = "black"; + backgroundColor = "rgb(226, 192, 0)"; // yellow + } else { + textColor = "white"; + backgroundColor = "darkred"; + } } + m_api->setText(text); + m_api->setStyleSheet(QString(R"( QLabel { -- cgit v1.3.1 From a7c48a05f857e9ff8c0bd25abc1bb595e580149d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 01:41:17 -0400 Subject: fixed text color of api label when not logged in --- src/statusbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 712eb005..8502802a 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -54,7 +54,7 @@ void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) if (user.type() == APIUserAccountTypes::None) { text = "API: not logged in"; - textColor = "white"; + textColor = "initial"; backgroundColor = "transparent"; } else { text = QString("API: Queued: %1 | Daily: %2 | Hourly: %3") -- cgit v1.3.1 From a75f54e5221b359d5a1e5c0d669c6ab2e3d297b2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 01:44:53 -0400 Subject: show executable path in status bar --- src/mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5018479d..1d51b42e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -727,6 +727,7 @@ void MainWindow::updatePinnedExecutables() iconForExecutable(iter->m_BinaryInfo.filePath()), iter->m_Title); exeAction->setObjectName(QString("custom__") + iter->m_Title); + exeAction->setStatusTip(iter->m_BinaryInfo.filePath()); if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { qDebug("failed to connect trigger?"); -- cgit v1.3.1 From d527d62b2b671b52e74a34f24a701c18d7632477 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 01:53:31 -0400 Subject: changed the notifications action back to being disabled when there aren't any, having a coloured and clickable icon was confusing --- src/mainwindow.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1d51b42e..250bc1d7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -934,6 +934,8 @@ void MainWindow::updateProblemsButton() final = original; } + ui->actionNotifications->setEnabled(numProblems > 0); + // setting the icon on the action (shown on the menu) ui->actionNotifications->setIcon(final); -- cgit v1.3.1 From eed0f1503d2b2bf03d1ad823c72f7d279a1b41f6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 02:05:06 -0400 Subject: re-added the api label tooltip comments --- src/mainwindow.cpp | 21 +++++++++++++++++++-- src/statusbar.cpp | 7 +++++++ 2 files changed, 26 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 250bc1d7..205ab7cc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -227,13 +227,30 @@ MainWindow::MainWindow(QSettings &initSettings QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); ui->setupUi(this); + m_statusBar.reset(new StatusBar(statusBar(), ui)); { auto* ni = NexusInterface::instance(&m_PluginContainer); + // there are two ways to get here: + // 1) the user just started MO, and + // 2) the user has changed some setting that required a restart + // + // "restarting" MO doesn't actually re-execute the binary, it just basically + // executes most of main() again, so a bunch of things are actually not + // reset + // + // one of these things is the api status, which will have fired its events + // long before the execution gets here because stuff is still cached and no + // real request to nexus is actually done + // + // therefore, when the user starts MO normally, the user account and stats + // will be empty (which is fine) and populated later on when the api key + // check has finished + // + // in the rare case where the user restarts MO through the settings, this + // will correctly pick up the previous values updateWindowTitle(ni->getAPIUserAccount()); - - m_statusBar.reset(new StatusBar(statusBar(), ui)); m_statusBar->setAPI(ni->getAPIStats(), ni->getAPIUserAccount()); } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 8502802a..01449202 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -23,6 +23,13 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : m_notifications->set(false); m_api->setObjectName("apistats"); + m_api->setToolTip(QObject::tr( + "This tracks the number of queued Nexus API requests, as well as the " + "remaining daily and hourly requests. The Nexus API limits you to a pool " + "of requests per day and requests per hour. It is dynamically updated " + "every time a request is completed. If you run out of requests, you will " + "be unable to queue downloads, check updates, parse mod info, or even log " + "in. Both pools must be consumed before this happens.")); m_bar->clearMessage(); setProgress(-1); -- cgit v1.3.1 From 2a2b7c0f101a66607c49b8dd103ffc0fecb623ab Mon Sep 17 00:00:00 2001 From: Matte A Date: Sat, 15 Jun 2019 16:09:13 +0200 Subject: Correcting minor spelling mistakes in the UI + add contributor When translating, I found some minor mistakes. Therefor I ran through the file in a proofreader and corrected some mistakes. There is also some mistakes corrected for consistency e.g. spaces and dashes --- src/organizer_en.ts | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 24a58c81..e4a134a5 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -96,7 +96,7 @@ - Jax (Swedish) + Jax, Nubbie (Swedish) @@ -111,7 +111,7 @@ - Other Supporters && Contributors + Other Supporters & Contributors @@ -1453,12 +1453,12 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOApplication - an error occured: %1 + an error occurred: %1 - an error occured + an error occurred
    @@ -1555,8 +1555,8 @@ This is likely due to a corrupted or incompatible download or unrecognized archi <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> @@ -3272,7 +3272,7 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - Errors occured + Errors occurred @@ -3309,7 +3309,7 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - Backup of modlist created + Backup of mod list created @@ -3508,7 +3508,7 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod whether the tweaks are really optional. @@ -3582,7 +3582,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. + These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. @@ -3658,7 +3658,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer2 on Nexus. Why not go there now and endorse us?</p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> @@ -3717,7 +3717,7 @@ p, li { white-space: pre-wrap; } - Web page URL (only used if invalid NexusID) : + Web page URL (only used if invalid Nexus ID) : @@ -3917,13 +3917,13 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? + Are you sure you want to delete "%1"? - Are sure you want to delete the selected files? + Are you sure you want to delete the selected files? @@ -4519,7 +4519,7 @@ p, li { white-space: pre-wrap; } - An error occured trying to update MO settings to %1: %2 + An error occurred trying to update MO settings to %1: %2 @@ -4539,7 +4539,7 @@ p, li { white-space: pre-wrap; } - An error occured trying to write back MO settings to %1: %2 + An error occurred trying to write back MO settings to %1: %2 @@ -4619,7 +4619,7 @@ p, li { white-space: pre-wrap; } - MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely neccessary. + MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely necessary. Restart MO as administrator? @@ -4802,13 +4802,13 @@ Continue? - Are sure you want to delete "%1"? + Are you sure you want to delete "%1"? - Are sure you want to delete the selected files? + Are you sure you want to delete the selected files? @@ -5528,7 +5528,7 @@ p, li { white-space: pre-wrap; } - Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched. + Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untouched. @@ -5907,7 +5907,7 @@ If the folder was still in use, restart MO and try again. This process requires elevation to run. -This is a potential security risk so I highly advice you to investigate if +This is a potential security risk so I highly advise you to investigate if "%1" can be installed to work without elevation. @@ -6377,7 +6377,7 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - Important: All directories have to be writeable! + Important: All directories have to be writable! @@ -6809,7 +6809,7 @@ programs you are intentionally running. Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. @@ -7191,7 +7191,7 @@ On Windows XP: - <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwrtten by another. You could as well disable it. + <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwritten by another. You could as well disable it. @@ -7397,7 +7397,7 @@ There IS a notification now but you may want to hold off on clearing it until af - A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the indiviual mod. To do so, right-click the mod and select "Information". + A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the individual mod. To do so, right-click the mod and select "Information". @@ -7431,7 +7431,7 @@ It's important you always start the game from inside MO, otherwise the mods - We may re-visit this screen in later tutorials. + We may revisit this screen in later tutorials.
    -- 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') 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 4f01b94f01180989abbdf0407cdf95483970dba8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 17:53:42 -0400 Subject: replaced ExecutablesList::getExecutables() by a standard container interface renamed ExecutablesList::init() to addFromPlugin() renamed ExecutablesList::find() to get() and added a find() that returns an iterator changed some calls from get() to find() so they can handle failure because they didn't seem to handle std::runtime_error at all --- src/editexecutablesdialog.cpp | 43 ++++++++++++++++---- src/executableslist.cpp | 84 +++++++++++++++++++-------------------- src/executableslist.h | 63 +++++++++++------------------ src/mainwindow.cpp | 92 ++++++++++++++++++++++++++----------------- src/organizercore.cpp | 16 ++++---- 5 files changed, 164 insertions(+), 134 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 67fbff31..9dbc6bae 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -59,20 +59,29 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const { ExecutablesList newList; for (int i = 0; i < ui->executablesListBox->count(); ++i) { - newList.addExecutable(m_ExecutablesList.find(ui->executablesListBox->item(i)->text())); + const auto& title = ui->executablesListBox->item(i)->text(); + auto itor = m_ExecutablesList.find(title); + + if (itor == m_ExecutablesList.end()) { + qWarning().nospace() + << "getExecutablesList(): executable '" << title << "' not found"; + + continue; + } + + newList.addExecutable(*itor); } + return newList; } void EditExecutablesDialog::refreshExecutablesWidget() { ui->executablesListBox->clear(); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - for(; current != end; ++current) { - QListWidgetItem *newItem = new QListWidgetItem(current->title()); - newItem->setTextColor(current->isCustom() ? QColor(Qt::black) : QColor(Qt::darkGray)); + for(const auto& exe : m_ExecutablesList) { + QListWidgetItem *newItem = new QListWidgetItem(exe.title()); + newItem->setTextColor(exe.isCustom() ? QColor(Qt::black) : QColor(Qt::darkGray)); ui->executablesListBox->addItem(newItem); } @@ -282,7 +291,17 @@ void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) bool EditExecutablesDialog::executableChanged() { if (m_CurrentItem != nullptr) { - Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); + const auto& title = m_CurrentItem->text(); + auto itor = m_ExecutablesList.find(title); + + if (itor == m_ExecutablesList.end()) { + qWarning().nospace() + << "executableChanged(): title '" << title << "' not found"; + + return false; + } + + const Executable& selectedExecutable = *itor; QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.title()).toString(); @@ -374,7 +393,15 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur m_CurrentItem = ui->executablesListBox->item(current.row()); - Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); + const auto& title = m_CurrentItem->text(); + auto itor = m_ExecutablesList.find(title); + + if (itor == m_ExecutablesList.end()) { + qWarning().nospace() << "selection: executable '" << title << "' not found"; + return; + } + + const Executable& selectedExecutable = *itor; ui->titleEdit->setText(selectedExecutable.title()); ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.binaryInfo().absoluteFilePath())); diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 79b17f5b..2ea9c3d9 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -33,19 +33,40 @@ along with Mod Organizer. If not, see . using namespace MOBase; +ExecutablesList::iterator ExecutablesList::begin() +{ + return m_Executables.begin(); +} + +ExecutablesList::const_iterator ExecutablesList::begin() const +{ + return m_Executables.begin(); +} + +ExecutablesList::iterator ExecutablesList::end() +{ + return m_Executables.end(); +} + +ExecutablesList::const_iterator ExecutablesList::end() const +{ + return m_Executables.end(); +} -ExecutablesList::ExecutablesList() +std::size_t ExecutablesList::size() const { + return m_Executables.size(); } -ExecutablesList::~ExecutablesList() +bool ExecutablesList::empty() const { + return m_Executables.empty(); } -void ExecutablesList::init(IPluginGame const *game) +void ExecutablesList::addFromPlugin(IPluginGame const *game) { Q_ASSERT(game != nullptr); - m_Executables.clear(); + for (const ExecutableInfo &info : game->executables()) { if (info.isValid()) { addExecutableInternal(info.title(), @@ -55,6 +76,7 @@ void ExecutablesList::init(IPluginGame const *game) info.steamAppID()); } } + ExecutableInfo explorerpp = ExecutableInfo("Explore Virtual Folder", QFileInfo(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe" )) .withArgument(QString("\"%1\"").arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath()))); @@ -65,45 +87,25 @@ void ExecutablesList::init(IPluginGame const *game) explorerpp.workingDirectory().absolutePath(), explorerpp.steamAppID()); } - } -void ExecutablesList::getExecutables(std::vector::iterator &begin, std::vector::iterator &end) +const Executable &ExecutablesList::get(const QString &title) const { - begin = m_Executables.begin(); - end = m_Executables.end(); -} - -void ExecutablesList::getExecutables(std::vector::const_iterator &begin, - std::vector::const_iterator &end) const -{ - begin = m_Executables.begin(); - end = m_Executables.end(); -} - -const Executable &ExecutablesList::find(const QString &title) const -{ - for (Executable const &exe : m_Executables) { + for (const auto& exe : m_Executables) { if (exe.title() == title) { return exe; } } - throw std::runtime_error(QString("invalid executable name: %1").arg(title).toLocal8Bit().constData()); -} + throw std::runtime_error(QString("executable not found: %1").arg(title).toLocal8Bit().constData()); +} -Executable &ExecutablesList::find(const QString &title) +Executable &ExecutablesList::get(const QString &title) { - for (Executable &exe : m_Executables) { - if (exe.title() == title) { - return exe; - } - } - throw std::runtime_error(QString("invalid executable name: %1").arg(title).toLocal8Bit().constData()); + return const_cast(std::as_const(*this).get(title)); } - -Executable &ExecutablesList::findByBinary(const QFileInfo &info) +Executable &ExecutablesList::getByBinary(const QFileInfo &info) { for (Executable &exe : m_Executables) { if (exe.binaryInfo() == info) { @@ -113,17 +115,15 @@ Executable &ExecutablesList::findByBinary(const QFileInfo &info) throw std::runtime_error("invalid info"); } - -std::vector::iterator ExecutablesList::findExe(const QString &title) +ExecutablesList::iterator ExecutablesList::find(const QString &title) { - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->title() == title) { - return iter; - } - } - return m_Executables.end(); + return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; }); } +ExecutablesList::const_iterator ExecutablesList::find(const QString &title) const +{ + return std::find_if(begin(), end(), [&](auto&& e) { return e.title() == title; }); +} bool ExecutablesList::titleExists(const QString &title) const { @@ -131,10 +131,9 @@ bool ExecutablesList::titleExists(const QString &title) const return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); } - void ExecutablesList::addExecutable(const Executable &executable) { - auto existingExe = findExe(executable.title()); + auto existingExe = find(executable.title()); if (existingExe != m_Executables.end()) { *existingExe = executable; } else { @@ -142,7 +141,6 @@ void ExecutablesList::addExecutable(const Executable &executable) } } - void ExecutablesList::updateExecutable(const QString &title, const QString &executableName, const QString &arguments, @@ -153,7 +151,7 @@ void ExecutablesList::updateExecutable(const QString &title, { QFileInfo file(executableName); QDir dir(workingDirectory); - auto existingExe = findExe(title); + auto existingExe = find(title); flags &= mask; if (existingExe != m_Executables.end()) { diff --git a/src/executableslist.h b/src/executableslist.h index 0e43b337..b8e4cf77 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -88,28 +88,33 @@ private: **/ class ExecutablesList { public: + using vector_type = std::vector; + using iterator = vector_type::iterator; + using const_iterator = vector_type::const_iterator; /** - * @brief constructor - * - **/ - ExecutablesList(); - - ~ExecutablesList(); + * standard container interface + */ + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + std::size_t size() const; + bool empty() const; /** - * @brief initialise the list with the executables preconfigured for this game + * @brief add the executables preconfigured for this game **/ - void init(MOBase::IPluginGame const *game); + void addFromPlugin(MOBase::IPluginGame const *game); /** - * @brief find an executable by its name + * @brief get an executable by name * * @param title the title of the executable to look up * @return the executable - * @exception runtime_error will throw an exception if the name is not correct + * @exception runtime_error will throw an exception if the executable is not found **/ - const Executable &find(const QString &title) const; + const Executable &get(const QString &title) const; /** * @brief find an executable by its name @@ -118,7 +123,7 @@ public: * @return the executable * @exception runtime_error will throw an exception if the name is not correct **/ - Executable &find(const QString &title); + Executable &get(const QString &title); /** * @brief find an executable by a fileinfo structure @@ -126,7 +131,13 @@ public: * @return the executable * @exception runtime_error will throw an exception if the name is not correct */ - Executable &findByBinary(const QFileInfo &info); + Executable &getByBinary(const QFileInfo &info); + + /** + * @brief returns an iterator for the given executable by title, or end() + */ + iterator find(const QString &title); + const_iterator find(const QString &title) const; /** * @brief determine if an executable exists @@ -183,33 +194,7 @@ public: **/ void remove(const QString &title); - /** - * @brief retrieve begin and end iterators of the configured executables - * - * @param begin iterator to the first executable - * @param end iterator one past the last executable - **/ - void getExecutables(std::vector::const_iterator &begin, std::vector::const_iterator &end) const; - - /** - * @brief retrieve begin and end iterators of the configured executables - * - * @param begin iterator to the first executable - * @param end iterator one past the last executable - **/ - void getExecutables(std::vector::iterator &begin, std::vector::iterator &end); - - /** - * @brief get the number of executables (custom or otherwise) - **/ - size_t size() const { - return m_Executables.size(); - } - private: - - std::vector::iterator findExe(const QString &title); - void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, const QString &steamAppID); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 38b2ed3f..2fbdbec7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -733,15 +733,12 @@ void MainWindow::updatePinnedExecutables() bool hasLinks = false; - std::vector::iterator begin, end; - m_OrganizerCore.executablesList()->getExecutables(begin, end); - - for (auto iter = begin; iter != end; ++iter) { - if (iter->isShownOnToolbar()) { + for (const auto& exe : *m_OrganizerCore.executablesList()) { + if (exe.isShownOnToolbar()) { hasLinks = true; QAction *exeAction = new QAction( - iconForExecutable(iter->binaryInfo().filePath()), iter->title()); + iconForExecutable(exe.binaryInfo().filePath()), exe.title()); exeAction->setObjectName(QString("custom__") + iter->m_Title); exeAction->setStatusTip(iter->m_BinaryInfo.filePath()); @@ -1504,24 +1501,42 @@ void MainWindow::registerModPage(IPluginModPage *modPage) 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.title()).toString(); - auto forcedLibraries = m_OrganizerCore.currentProfile()->determineForcedLibraries(selectedExecutable.title()); - if (!m_OrganizerCore.currentProfile()->forcedLibrariesEnabled(selectedExecutable.title())) { - forcedLibraries.clear(); - } - m_OrganizerCore.spawnBinary( - selectedExecutable.binaryInfo(), selectedExecutable.arguments(), - selectedExecutable.workingDirectory().length() != 0 - ? selectedExecutable.workingDirectory() - : selectedExecutable.binaryInfo().absolutePath(), - selectedExecutable.steamAppID(), - customOverwrite, - forcedLibraries); - } else { + + if (action == nullptr) { qCritical("not an action?"); + return; } + + const auto& list = *m_OrganizerCore.executablesList(); + + const auto title = action->text(); + auto itor = list.find(title); + + if (itor == list.end()) { + qWarning().nospace() + << "startExeAction(): executable '" << title << "' not found"; + + return; + } + + const Executable& exe = *itor; + auto& profile = *m_OrganizerCore.currentProfile(); + + QString customOverwrite = profile.setting("custom_overwrites", exe.title()).toString(); + auto forcedLibraries = profile.determineForcedLibraries(exe.title()); + + if (!profile.forcedLibrariesEnabled(exe.title())) { + forcedLibraries.clear(); + } + + m_OrganizerCore.spawnBinary( + exe.binaryInfo(), exe.arguments(), + exe.workingDirectory().length() != 0 + ? exe.workingDirectory() + : exe.binaryInfo().absolutePath(), + exe.steamAppID(), + customOverwrite, + forcedLibraries); } @@ -1789,12 +1804,12 @@ void MainWindow::refreshExecutablesList() QAbstractItemModel *model = executablesList->model(); - std::vector::const_iterator current, end; - m_OrganizerCore.executablesList()->getExecutables(current, end); - for(int i = 0; current != end; ++current, ++i) { - QIcon icon = iconForExecutable(current->binaryInfo().filePath()); - executablesList->addItem(icon, current->title()); + int i = 0; + for (const auto& exe : *m_OrganizerCore.executablesList()) { + QIcon icon = iconForExecutable(exe.binaryInfo().filePath()); + executablesList->addItem(icon, exe.title()); model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole); + ++i; } setExecutableIndex(1); @@ -6389,13 +6404,18 @@ void MainWindow::unlockESPIndex() void MainWindow::removeFromToolbar() { - try { - Executable &exe = m_OrganizerCore.executablesList()->find(m_ContextAction->text()); - exe.setShownOnToolbar(false); - } catch (const std::runtime_error&) { - qDebug("executable doesn't exist any more"); + const auto& title = m_ContextAction->text(); + auto& list = *m_OrganizerCore.executablesList(); + + auto itor = list.find(title); + if (itor == list.end()) { + qWarning().nospace() + << "removeFromToolbar(): executable '" << title << "' not found"; + + return; } + itor->setShownOnToolbar(false); updatePinnedExecutables(); } @@ -6520,14 +6540,14 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) const Executable &MainWindow::getSelectedExecutable() const { - QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->find(name); + const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); + return m_OrganizerCore.executablesList()->get(name); } Executable &MainWindow::getSelectedExecutable() { - QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->find(name); + const QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); + return m_OrganizerCore.executablesList()->get(name); } void MainWindow::on_linkButton_pressed() diff --git a/src/organizercore.cpp b/src/organizercore.cpp index cfbcad39..4d11a35f 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -375,11 +375,10 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName) settings.remove("customExecutables"); settings.beginWriteArray("customExecutables"); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); + int count = 0; - for (; current != end; ++current) { - const Executable &item = *current; + + for (const auto& item : m_ExecutablesList) { settings.setArrayIndex(count++); settings.setValue("title", item.title()); settings.setValue("custom", item.isCustom()); @@ -508,7 +507,7 @@ void OrganizerCore::updateExecutablesList(QSettings &settings) return; } - m_ExecutablesList.init(managedGame()); + m_ExecutablesList.addFromPlugin(managedGame()); qDebug("setting up configured executables"); @@ -1735,7 +1734,8 @@ HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) .arg(shortcut.instance(),shortcut.executable()) .toLocal8Bit().constData()); - Executable& exe = m_ExecutablesList.find(shortcut.executable()); + const Executable& exe = m_ExecutablesList.get(shortcut.executable()); + auto forcedLibaries = m_CurrentProfile->determineForcedLibraries(shortcut.executable()); if (!m_CurrentProfile->forcedLibrariesEnabled(shortcut.executable())) { forcedLibaries.clear(); @@ -1786,7 +1786,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, currentDirectory = binary.absolutePath(); } try { - const Executable &exe = m_ExecutablesList.findByBinary(binary); + const Executable &exe = m_ExecutablesList.getByBinary(binary); steamAppID = exe.steamAppID(); customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()) @@ -1800,7 +1800,7 @@ HANDLE OrganizerCore::startApplication(const QString &executable, } else { // only a file name, search executables list try { - const Executable &exe = m_ExecutablesList.find(executable); + const Executable &exe = m_ExecutablesList.get(executable); steamAppID = exe.steamAppID(); customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.title()) -- cgit v1.3.1 From 26786da96d37914ca91eff633de751acf1a3b9d8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 18:04:31 -0400 Subject: moved store/load to ExecutablesList --- src/executableslist.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ src/executableslist.h | 21 ++++++++++++------- src/organizercore.cpp | 47 ++++-------------------------------------- 3 files changed, 72 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 2ea9c3d9..43a30f02 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -63,6 +63,60 @@ bool ExecutablesList::empty() const return m_Executables.empty(); } +void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) +{ + addFromPlugin(game); + + qDebug("setting up configured executables"); + + int numCustomExecutables = settings.beginReadArray("customExecutables"); + for (int i = 0; i < numCustomExecutables; ++i) { + settings.setArrayIndex(i); + + Executable::Flags flags; + if (settings.value("custom", true).toBool()) + flags |= Executable::CustomExecutable; + if (settings.value("toolbar", false).toBool()) + flags |= Executable::ShowInToolbar; + if (settings.value("ownicon", false).toBool()) + flags |= Executable::UseApplicationIcon; + + addExecutable( + settings.value("title").toString(), settings.value("binary").toString(), + settings.value("arguments").toString(), + settings.value("workingDirectory", "").toString(), + settings.value("steamAppID", "").toString(), flags); + } + + settings.endArray(); +} + +void ExecutablesList::store(QSettings& settings) +{ + settings.remove("customExecutables"); + settings.beginWriteArray("customExecutables"); + + int count = 0; + + for (const auto& item : *this) { + settings.setArrayIndex(count++); + + 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.binaryInfo().absoluteFilePath()); + settings.setValue("arguments", item.arguments()); + settings.setValue("workingDirectory", item.workingDirectory()); + settings.setValue("steamAppID", item.steamAppID()); + } + } + + settings.endArray(); +} + void ExecutablesList::addFromPlugin(IPluginGame const *game) { Q_ASSERT(game != nullptr); diff --git a/src/executableslist.h b/src/executableslist.h index b8e4cf77..136f70bf 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -103,9 +103,14 @@ public: bool empty() const; /** - * @brief add the executables preconfigured for this game - **/ - void addFromPlugin(MOBase::IPluginGame const *game); + * @brief initializes the list from the settings and the given plugin + */ + void load(const MOBase::IPluginGame* game, QSettings& settings); + + /** + * @brief writes the current list to the settings + */ + void store(QSettings& settings); /** * @brief get an executable by name @@ -195,14 +200,16 @@ public: void remove(const QString &title); private: + std::vector m_Executables; + void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, const QString &steamAppID); -private: - - std::vector m_Executables; - + /** + * @brief add the executables preconfigured for this game + **/ + void addFromPlugin(MOBase::IPluginGame const *game); }; Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 4d11a35f..2172538e 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -365,33 +365,17 @@ QString OrganizerCore::commitSettings(const QString &iniFile) QSettings::Status OrganizerCore::storeSettings(const QString &fileName) { QSettings settings(fileName, QSettings::IniFormat); + if (m_UserInterface != nullptr) { m_UserInterface->storeSettings(settings); } + if (m_CurrentProfile != nullptr) { settings.setValue("selected_profile", m_CurrentProfile->name().toUtf8().constData()); } - settings.remove("customExecutables"); - settings.beginWriteArray("customExecutables"); - - int count = 0; - - for (const auto& item : m_ExecutablesList) { - settings.setArrayIndex(count++); - 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.binaryInfo().absoluteFilePath()); - settings.setValue("arguments", item.arguments()); - settings.setValue("workingDirectory", item.workingDirectory()); - settings.setValue("steamAppID", item.steamAppID()); - } - } - settings.endArray(); + m_ExecutablesList.store(settings); FileDialogMemory::save(settings); @@ -507,30 +491,7 @@ void OrganizerCore::updateExecutablesList(QSettings &settings) return; } - m_ExecutablesList.addFromPlugin(managedGame()); - - qDebug("setting up configured executables"); - - int numCustomExecutables = settings.beginReadArray("customExecutables"); - for (int i = 0; i < numCustomExecutables; ++i) { - settings.setArrayIndex(i); - - Executable::Flags flags; - if (settings.value("custom", true).toBool()) - flags |= Executable::CustomExecutable; - if (settings.value("toolbar", false).toBool()) - flags |= Executable::ShowInToolbar; - if (settings.value("ownicon", false).toBool()) - flags |= Executable::UseApplicationIcon; - - m_ExecutablesList.addExecutable( - settings.value("title").toString(), settings.value("binary").toString(), - settings.value("arguments").toString(), - settings.value("workingDirectory", "").toString(), - settings.value("steamAppID", "").toString(), flags); - } - - settings.endArray(); + m_ExecutablesList.load(managedGame(), settings); // TODO this has nothing to do with executables list move to an appropriate // function! -- cgit v1.3.1 From 617a6005451aba1d45f13228f13f1cc150a78bb4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 18:24:41 -0400 Subject: ExecutablesList: - merged addExecutable(), updateExecutable() and addExecutableInternal() into a new setExecutable() - setExecutable() adds the executable to the list if not found, or forwards to Executable::mergeFrom() - mergeFrom() handles merging from/to plugin executables --- src/editexecutablesdialog.cpp | 24 ++++---- src/executableslist.cpp | 125 ++++++++++++++++++------------------------ src/executableslist.h | 48 +++------------- src/mainwindow.cpp | 10 ++-- 4 files changed, 77 insertions(+), 130 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 9dbc6bae..83756ac8 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -69,7 +69,7 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const continue; } - newList.addExecutable(*itor); + newList.setExecutable(*itor); } return newList; @@ -138,17 +138,17 @@ void EditExecutablesDialog::resetInput() void EditExecutablesDialog::saveExecutable() { - m_ExecutablesList.updateExecutable( - ui->titleEdit->text(), - QDir::fromNativeSeparators(ui->binaryEdit->text()), - ui->argumentsEdit->text(), - QDir::fromNativeSeparators(ui->workingDirEdit->text()), - ui->overwriteAppIDBox->isChecked() ? - ui->appIDOverwriteEdit->text() : "", - Executable::UseApplicationIcon | Executable::CustomExecutable, - (ui->useAppIconCheckBox->isChecked() ? - Executable::UseApplicationIcon : Executable::Flags()) - | Executable::CustomExecutable); + Executable::Flags flags = Executable::CustomExecutable; + if (ui->useAppIconCheckBox->isChecked()) + flags |= Executable::UseApplicationIcon; + + m_ExecutablesList.setExecutable({ + ui->titleEdit->text(), + QDir::fromNativeSeparators(ui->binaryEdit->text()), + ui->argumentsEdit->text(), + QDir::fromNativeSeparators(ui->workingDirEdit->text()), + ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", + flags}); if (ui->newFilesModCheckBox->isChecked()) { m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(), diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 43a30f02..062ee28e 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -81,11 +81,13 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) if (settings.value("ownicon", false).toBool()) flags |= Executable::UseApplicationIcon; - addExecutable( - settings.value("title").toString(), settings.value("binary").toString(), + setExecutable({ + settings.value("title").toString(), + settings.value("binary").toString(), settings.value("arguments").toString(), settings.value("workingDirectory", "").toString(), - settings.value("steamAppID", "").toString(), flags); + settings.value("steamAppID", "").toString(), + flags}); } settings.endArray(); @@ -123,11 +125,13 @@ void ExecutablesList::addFromPlugin(IPluginGame const *game) for (const ExecutableInfo &info : game->executables()) { if (info.isValid()) { - addExecutableInternal(info.title(), - info.binary().absoluteFilePath(), - info.arguments().join(" "), - info.workingDirectory().absolutePath(), - info.steamAppID()); + setExecutable({ + info.title(), + info.binary().absoluteFilePath(), + info.arguments().join(" "), + info.workingDirectory().absolutePath(), + info.steamAppID(), + Executable::UseApplicationIcon}); } } @@ -135,11 +139,13 @@ void ExecutablesList::addFromPlugin(IPluginGame const *game) .withArgument(QString("\"%1\"").arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath()))); if (explorerpp.isValid()) { - addExecutableInternal(explorerpp.title(), + setExecutable({ + explorerpp.title(), explorerpp.binary().absoluteFilePath(), explorerpp.arguments().join(" "), explorerpp.workingDirectory().absolutePath(), - explorerpp.steamAppID()); + explorerpp.steamAppID(), + Executable::UseApplicationIcon}); } } @@ -185,60 +191,17 @@ bool ExecutablesList::titleExists(const QString &title) const return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); } -void ExecutablesList::addExecutable(const Executable &executable) +void ExecutablesList::setExecutable(const Executable &executable) { - auto existingExe = find(executable.title()); - if (existingExe != m_Executables.end()) { - *existingExe = executable; - } else { - m_Executables.push_back(executable); - } -} + auto itor = find(executable.title()); -void ExecutablesList::updateExecutable(const QString &title, - const QString &executableName, - const QString &arguments, - const QString &workingDirectory, - const QString &steamAppID, - Executable::Flags mask, - Executable::Flags flags) -{ - QFileInfo file(executableName); - QDir dir(workingDirectory); - auto existingExe = find(title); - flags &= mask; - - if (existingExe != m_Executables.end()) { - 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->setBinaryInfo(file); - } - - if (dir.exists()) { - // don't overwrite a valid working directory with an invalid one - existingExe->setWorkingDirectory(workingDirectory); - } - existingExe->setArguments(arguments); - existingExe->setSteamAppID(steamAppID); - } + if (itor == m_Executables.end()) { + m_Executables.push_back(executable); } else { - m_Executables.push_back({ - title, file, arguments, workingDirectory, steamAppID, - Executable::CustomExecutable | flags}); + itor->mergeFrom(executable); } } - void ExecutablesList::remove(const QString &title) { for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { @@ -250,19 +213,6 @@ void ExecutablesList::remove(const QString &title) } -void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName, - const QString &arguments, const QString &workingDirectory, - const QString &steamAppID) -{ - QFileInfo file(executableName); - if (file.exists()) { - m_Executables.push_back({ - title, file, arguments, steamAppID, - workingDirectory, Executable::UseApplicationIcon}); - } -} - - Executable::Executable( QString title, QFileInfo binaryInfo, QString arguments, QString steamAppID, QString workingDirectory, Flags flags) : @@ -358,3 +308,36 @@ bool Executable::usesOwnIcon() const { return m_flags.testFlag(UseApplicationIcon); } + +void Executable::mergeFrom(const Executable& other) +{ + if (!isCustom()) { + // this happens when the user is trying to modify a plugin executable + + // only change some of the flags + const auto allow = ShowInToolbar; + + m_flags |= (other.flags() & allow); + } else { + // this happens after executables are loaded from settings and plugin + // executables are being added, or when users are modifying executables + + m_title = other.title(); + m_arguments = other.arguments(); + m_steamAppID = other.steamAppID(); + m_workingDirectory = other.workingDirectory(); + + // don't overwrite a valid binary with an invalid one + if (other.binaryInfo().exists()) { + m_binaryInfo = other.binaryInfo(); + } + + if (!other.isCustom()) { + // overwriting a custom executable with a plugin, merge all the flags + m_flags |= other.flags(); + } else { + // overwriting a custom with another custom, just replace the flags + m_flags = other.flags(); + } + } +} diff --git a/src/executableslist.h b/src/executableslist.h index 136f70bf..4965dbf9 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -73,6 +73,8 @@ public: void setShownOnToolbar(bool state); bool usesOwnIcon() const; + void mergeFrom(const Executable& other); + private: QString m_title; QFileInfo m_binaryInfo; @@ -155,57 +157,21 @@ public: * @brief add a new executable to the list * @param executable */ - void addExecutable(const Executable &executable); - - /** - * @brief add a new executable to the list - * - * @param title name displayed in the UI - * @param executableName the actual filename to execute - * @param arguments arguments to pass to the executable - **/ - void addExecutable(const QString &title, - const QString &executableName, - const QString &arguments, - const QString &workingDirectory, - const QString &steamAppID, - Executable::Flags flags) - { - updateExecutable(title, executableName, arguments, workingDirectory, - steamAppID, Executable::AllFlags, flags); - } - - /** - * @brief Update an executable to the list - * - * @param title name displayed in the UI - * @param executableName the actual filename to execute - * @param arguments arguments to pass to the executable - * @param closeMO if true, MO will be closed when the binary is started - **/ - void updateExecutable(const QString &title, - const QString &executableName, - const QString &arguments, - const QString &workingDirectory, - const QString &steamAppID, - Executable::Flags mask, - Executable::Flags flags); + void setExecutable(const Executable &executable); /** - * @brief remove the executable with the specified file name. This needs to be an absolute file path + * @brief remove the executable with the specified file name. This needs to + * be an absolute file path * * @param title title of the executable to remove - * @note if the executable name is invalid, nothing happens. There is no way to determine if this was successful + * @note if the executable name is invalid, nothing happens. There is no way + * to determine if this was successful **/ void remove(const QString &title); private: std::vector m_Executables; - void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, - const QString &workingDirectory, - const QString &steamAppID); - /** * @brief add the executables preconfigured for this game **/ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2fbdbec7..926ba43c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5507,12 +5507,10 @@ void MainWindow::addAsExecutable() if (!name.isEmpty()) { //Note: If this already exists, you'll lose custom settings - m_OrganizerCore.executablesList()->addExecutable(name, - binaryInfo.absoluteFilePath(), - arguments, - targetInfo.absolutePath(), - QString(), - Executable::CustomExecutable); + m_OrganizerCore.executablesList()->setExecutable({ + name, binaryInfo, arguments, targetInfo.absolutePath(), QString(), + Executable::CustomExecutable}); + refreshExecutablesList(); } -- cgit v1.3.1 From 65ad374d9769524a62ac423b7d73661e42163265 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 18:29:20 -0400 Subject: removed temporary setters in Executable, unnecessary now that it has mergeFrom() --- src/executableslist.cpp | 30 ------------------------------ src/executableslist.h | 15 +-------------- 2 files changed, 1 insertion(+), 44 deletions(-) (limited to 'src') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 062ee28e..9c291708 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -230,61 +230,31 @@ 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); diff --git a/src/executableslist.h b/src/executableslist.h index 4965dbf9..e35cb550 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -39,9 +39,7 @@ public: { CustomExecutable = 0x01, ShowInToolbar = 0x02, - UseApplicationIcon = 0x04, - - AllFlags = 0xff //I know, I know + UseApplicationIcon = 0x04 }; Q_DECLARE_FLAGS(Flags, Flag); @@ -51,22 +49,11 @@ public: QString steamAppID, QString workingDirectory, Flags flags); const QString& title() const; - void setTitle(const QString& s); - const QFileInfo& binaryInfo() const; - void setBinaryInfo(const QFileInfo& fi); - const QString& arguments() const; - void setArguments(const QString& s); - const QString& steamAppID() const; - void setSteamAppID(const QString& s); - const QString& workingDirectory() const; - void setWorkingDirectory(const QString& s); - Flags flags() const; - void setFlags(Flags f); bool isCustom() const; bool isShownOnToolbar() const; -- cgit v1.3.1 From fafdb146004401355db5245cc1f7c241c00cf991 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 19:26:54 -0400 Subject: fixed EditExecutablesDialog being opened without a parent removed Executable's constructor with values, replaced with default ctor + setters, all these strings were much too error-prone added Executable ctor overload to convert from ExecutableInfo plugin executables now override most of the custom changes renamed browseButton to browseBinaryButton to avoid confusion with the other browseDirButton fixed both browse dialogs not handling cancel EditExecutablesDialog's list used to change the text color for custom executables, replaced with italics --- src/editexecutablesdialog.cpp | 35 +++++++++---- src/editexecutablesdialog.h | 2 +- src/editexecutablesdialog.ui | 4 +- src/executableslist.cpp | 118 ++++++++++++++++++++++++++++-------------- src/executableslist.h | 18 +++++-- src/mainwindow.cpp | 13 +++-- 6 files changed, 132 insertions(+), 58 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 83756ac8..a3b2808a 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -81,7 +81,14 @@ void EditExecutablesDialog::refreshExecutablesWidget() for(const auto& exe : m_ExecutablesList) { QListWidgetItem *newItem = new QListWidgetItem(exe.title()); - newItem->setTextColor(exe.isCustom() ? QColor(Qt::black) : QColor(Qt::darkGray)); + + if (!exe.isCustom()) { + auto f = newItem->font(); + f.setItalic(true); + + newItem->setFont(f); + } + ui->executablesListBox->addItem(newItem); } @@ -142,13 +149,13 @@ void EditExecutablesDialog::saveExecutable() if (ui->useAppIconCheckBox->isChecked()) flags |= Executable::UseApplicationIcon; - m_ExecutablesList.setExecutable({ - ui->titleEdit->text(), - QDir::fromNativeSeparators(ui->binaryEdit->text()), - ui->argumentsEdit->text(), - QDir::fromNativeSeparators(ui->workingDirEdit->text()), - ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", - flags}); + m_ExecutablesList.setExecutable(Executable() + .title(ui->titleEdit->text()) + .binaryInfo(QDir::fromNativeSeparators(ui->binaryEdit->text())) + .arguments(ui->argumentsEdit->text()) + .steamAppID(ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "") + .workingDirectory(QDir::fromNativeSeparators(ui->workingDirEdit->text())) + .flags(flags)); if (ui->newFilesModCheckBox->isChecked()) { m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(), @@ -198,12 +205,17 @@ void EditExecutablesDialog::on_addButton_clicked() refreshExecutablesWidget(); } -void EditExecutablesDialog::on_browseButton_clicked() +void EditExecutablesDialog::on_browseBinaryButton_clicked() { QString binaryName = FileDialogMemory::getOpenFileName( "editExecutableBinary", this, tr("Select a binary"), QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar")); + if (binaryName.isNull()) { + // canceled + return; + } + if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { QString binaryPath; { // try to find java automatically @@ -250,6 +262,11 @@ void EditExecutablesDialog::on_browseDirButton_clicked() QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, tr("Select a directory")); + if (dirName.isNull()) { + // canceled + return; + } + ui->workingDirEdit->setText(dirName); } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index bee3cba6..3a856afa 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -78,7 +78,7 @@ private slots: void on_addButton_clicked(); - void on_browseButton_clicked(); + void on_browseBinaryButton_clicked(); void on_removeButton_clicked(); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 4f9223d5..adda050c 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -80,7 +80,7 @@ - + Browse filesystem @@ -310,7 +310,7 @@ Right now the only case I know of where this needs to be overwritten is for the executablesListBox titleEdit binaryEdit - browseButton + browseBinaryButton workingDirEdit browseDirButton argumentsEdit diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 9c291708..d884bdf2 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -81,13 +81,13 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) if (settings.value("ownicon", false).toBool()) flags |= Executable::UseApplicationIcon; - setExecutable({ - settings.value("title").toString(), - settings.value("binary").toString(), - settings.value("arguments").toString(), - settings.value("workingDirectory", "").toString(), - settings.value("steamAppID", "").toString(), - flags}); + setExecutable(Executable() + .title(settings.value("title").toString()) + .binaryInfo(settings.value("binary").toString()) + .arguments(settings.value("arguments").toString()) + .steamAppID(settings.value("steamAppID", "").toString()) + .workingDirectory(settings.value("workingDirectory", "").toString()) + .flags(flags)); } settings.endArray(); @@ -125,27 +125,22 @@ void ExecutablesList::addFromPlugin(IPluginGame const *game) for (const ExecutableInfo &info : game->executables()) { if (info.isValid()) { - setExecutable({ - info.title(), - info.binary().absoluteFilePath(), - info.arguments().join(" "), - info.workingDirectory().absolutePath(), - info.steamAppID(), - Executable::UseApplicationIcon}); + setExecutable({info, Executable::UseApplicationIcon}); } } - ExecutableInfo explorerpp = ExecutableInfo("Explore Virtual Folder", QFileInfo(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe" )) - .withArgument(QString("\"%1\"").arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath()))); - - if (explorerpp.isValid()) { - setExecutable({ - explorerpp.title(), - explorerpp.binary().absoluteFilePath(), - explorerpp.arguments().join(" "), - explorerpp.workingDirectory().absolutePath(), - explorerpp.steamAppID(), - Executable::UseApplicationIcon}); + const QFileInfo eppBin(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe"); + + if (eppBin.exists()) { + const auto args = QString("\"%1\"") + .arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath())); + + setExecutable(Executable() + .title("Explore Virtual Folder") + .binaryInfo(eppBin) + .arguments(args) + .workingDirectory(eppBin.absolutePath()) + .flags(Executable::UseApplicationIcon)); } } @@ -213,15 +208,13 @@ void ExecutablesList::remove(const QString &title) } -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) +Executable::Executable(const MOBase::ExecutableInfo& info, Flags flags) : + m_title(info.title()), + m_binaryInfo(info.binary()), + m_arguments(info.arguments().join(" ")), + m_steamAppID(info.steamAppID()), + m_workingDirectory(info.workingDirectory().absolutePath()), + m_flags(flags) { } @@ -255,6 +248,42 @@ Executable::Flags Executable::flags() const return m_flags; } +Executable& Executable::title(const QString& s) +{ + m_title = s; + return *this; +} + +Executable& Executable::binaryInfo(const QFileInfo& fi) +{ + m_binaryInfo = fi; + return *this; +} + +Executable& Executable::arguments(const QString& s) +{ + m_arguments = s; + return *this; +} + +Executable& Executable::steamAppID(const QString& s) +{ + m_steamAppID = s; + return *this; +} + +Executable& Executable::workingDirectory(const QString& s) +{ + m_workingDirectory = s; + return *this; +} + +Executable& Executable::flags(Flags f) +{ + m_flags = f; + return *this; +} + bool Executable::isCustom() const { return m_flags.testFlag(CustomExecutable); @@ -281,12 +310,25 @@ bool Executable::usesOwnIcon() const void Executable::mergeFrom(const Executable& other) { - if (!isCustom()) { - // this happens when the user is trying to modify a plugin executable + // flags on plugin executables that the user is allowed to chnage + const auto allow = ShowInToolbar; + + + if (!isCustom() && !other.isCustom()) { + // this happens when loading plugin executables in addFromPlugin(), replace + // everything in case the plugin has changed - // only change some of the flags - const auto allow = ShowInToolbar; + // remember the flags though + const auto flags = m_flags; + // overwrite everything + *this = other; + + // set the user flags + m_flags |= (flags & allow); + } + else if (!isCustom()) { + // this happens when the user is trying to modify a plugin executable m_flags |= (other.flags() & allow); } else { // this happens after executables are loaded from settings and plugin diff --git a/src/executableslist.h b/src/executableslist.h index e35cb550..54a105d0 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -27,7 +27,7 @@ along with Mod Organizer. If not, see . #include #include -namespace MOBase { class IPluginGame; } +namespace MOBase { class IPluginGame; class ExecutableInfo; } /*! * @brief Information about an executable @@ -44,9 +44,12 @@ public: Q_DECLARE_FLAGS(Flags, Flag); - Executable( - QString title, QFileInfo binaryInfo, QString arguments, - QString steamAppID, QString workingDirectory, Flags flags); + Executable() = default; + + /** + * @brief Executable from plugin + */ + Executable(const MOBase::ExecutableInfo& info, Flags flags); const QString& title() const; const QFileInfo& binaryInfo() const; @@ -55,6 +58,13 @@ public: const QString& workingDirectory() const; Flags flags() const; + Executable& title(const QString& s); + Executable& binaryInfo(const QFileInfo& fi); + Executable& arguments(const QString& s); + Executable& steamAppID(const QString& s); + Executable& workingDirectory(const QString& s); + Executable& flags(Flags f); + bool isCustom() const; bool isShownOnToolbar() const; void setShownOnToolbar(bool state); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 926ba43c..267a2971 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2483,7 +2483,9 @@ bool MainWindow::modifyExecutablesDialog() EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(), *m_OrganizerCore.modList(), m_OrganizerCore.currentProfile(), - m_OrganizerCore.managedGame()); + m_OrganizerCore.managedGame(), + this); + QSettings &settings = m_OrganizerCore.settings().directInterface(); QString key = QString("geometry/%1").arg(dialog.objectName()); if (settings.contains(key)) { @@ -5507,9 +5509,12 @@ void MainWindow::addAsExecutable() if (!name.isEmpty()) { //Note: If this already exists, you'll lose custom settings - m_OrganizerCore.executablesList()->setExecutable({ - name, binaryInfo, arguments, targetInfo.absolutePath(), QString(), - Executable::CustomExecutable}); + m_OrganizerCore.executablesList()->setExecutable(Executable() + .title(name) + .binaryInfo(binaryInfo) + .arguments(arguments) + .workingDirectory(targetInfo.absolutePath()) + .flags(Executable::CustomExecutable)); refreshExecutablesList(); } -- cgit v1.3.1 From 840dba26bacd044164d3236218e42f441433252c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 19:55:41 -0400 Subject: rework of the executables dialog to have a horizontal splitter and use a standard QDialogButtonBox --- src/editexecutablesdialog.cpp | 10 +- src/editexecutablesdialog.h | 5 +- src/editexecutablesdialog.ui | 613 +++++++++++++++++++++++------------------- 3 files changed, 347 insertions(+), 281 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index a3b2808a..559bc8f0 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -42,6 +42,9 @@ EditExecutablesDialog::EditExecutablesDialog( , m_GamePlugin(game) { ui->setupUi(this); + ui->splitter->setSizes({200, 1}); + ui->splitter->setStretchFactor(0, 0); + ui->splitter->setStretchFactor(1, 1); refreshExecutablesWidget(); @@ -367,8 +370,10 @@ void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) ui->appIDOverwriteEdit->setEnabled(checked); } -void EditExecutablesDialog::on_closeButton_clicked() +void EditExecutablesDialog::on_buttonBox_clicked(QAbstractButton*) { + // there's only a close button for now, so the actual button doesn't matter + if (executableChanged()) { QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), tr("You made changes to the current executable, do you want to save them?"), @@ -382,7 +387,8 @@ void EditExecutablesDialog::on_closeButton_clicked() refreshExecutablesWidget(); } } - this->accept(); + + accept(); } void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex ¤t) diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 3a856afa..07d6459c 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -22,10 +22,11 @@ along with Mod Organizer. If not, see . #include "tutorabledialog.h" #include -#include #include "executableslist.h" #include "profile.h" #include "iplugingame.h" +#include +#include namespace Ui { class EditExecutablesDialog; @@ -88,7 +89,7 @@ private slots: void on_browseDirButton_clicked(); - void on_closeButton_clicked(); + void on_buttonBox_clicked(QAbstractButton *button); void delayedRefresh(); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index adda050c..1cf1116a 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -6,8 +6,8 @@ 0 0 - 426 - 460 + 524 + 366 @@ -21,293 +21,354 @@ - - - List of configured executables + + + + 0 + 100 + - - This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. - - - QAbstractItemView::InternalMove - - - Qt::TargetMoveAction - - - QAbstractItemView::ExtendedSelection - - - - - - - - - Title - - - - - - - Name of the executable. This is only for display purposes. - - - Name of the executable. This is only for display purposes. - - - - - - - - - - - Binary - - - - - - - Binary to run - - - Binary to run - - - - - - - Browse filesystem - - - Browse filesystem for the executable to run. - - - ... - - - - - - - - - - - Start in - - - - - - - - - - ... - - - - - - - - - - - Arguments - - - - - - - Arguments to pass to the application - - - Arguments to pass to the application - - - - - - - - - - - Allow the Steam AppID to be used for this executable to be changed. - - - Allow the Steam AppID to be used for this executable to be changed. + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + false + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + List of configured executables + + + This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. + + + QAbstractItemView::InternalMove + + + Qt::TargetMoveAction + + + QAbstractItemView::ExtendedSelection + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Title + + + + + + + Name of the executable. This is only for display purposes. + + + Name of the executable. This is only for display purposes. + + + + + + + + + + + Binary + + + + + + + Binary to run + + + Binary to run + + + + + + + Browse filesystem + + + Browse filesystem for the executable to run. + + + ... + + + + + + + + + + + Start in + + + + + + + + + + ... + + + + + + + + + + + Arguments + + + + + + + Arguments to pass to the application + + + Arguments to pass to the application + + + + + + + + + + + Allow the Steam AppID to be used for this executable to be changed. + + + Allow the Steam AppID to be used for this executable to be changed. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game. Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured. - - - Overwrite Steam AppID - - - - - - - false - - - Steam AppID to use for this executable that differs from the games AppID. - - - Steam AppID to use for this executable that differs from the games AppID. + + + Overwrite Steam AppID + + + + + + + false + + + Steam AppID to use for this executable that differs from the games AppID. + + + Steam AppID to use for this executable that differs from the games AppID. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850). Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured. - - - - - - - - - - - If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. - - - Create Files in Mod instead of Overwrite (*) - - - - - - - false - - - - - - - - - - - If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - - - Force Load Libraries (*) - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Configure Libraries - - - - - - - - - Use Application's Icon for shortcuts - + + + + + + + + + + + If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. + + + Create Files in Mod instead of Overwrite (*) + + + + + + + false + + + + + + + + + + + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. + + + Force Load Libraries (*) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Configure Libraries + + + + + + + + + Use Application's Icon for shortcuts + + + + + + + (*) This setting is profile-specific + + + + + + + + + Add an executable + + + Add an executable + + + Add + + + + :/new/guiresources/resources/list-add.png:/new/guiresources/resources/list-add.png + + + + + + + Remove the selected executable + + + Remove the selected executable + + + Remove + + + + :/new/guiresources/resources/list-remove.png:/new/guiresources/resources/list-remove.png + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + - - - (*) This setting is profile-specific + + + QDialogButtonBox::Close - - - - - - Add an executable - - - Add an executable - - - Add - - - - :/new/guiresources/resources/list-add.png:/new/guiresources/resources/list-add.png - - - - - - - Remove the selected executable - - - Remove the selected executable - - - Remove - - - - :/new/guiresources/resources/list-remove.png:/new/guiresources/resources/list-remove.png - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Close - - - - - - executablesListBox titleEdit binaryEdit browseBinaryButton @@ -318,10 +379,8 @@ Right now the only case I know of where this needs to be overwritten is for the appIDOverwriteEdit newFilesModCheckBox newFilesModBox - useAppIconCheckBox addButton removeButton - closeButton -- cgit v1.3.1 From 5556e2b152decad3392c30c9760a5584454b52e6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 20:08:53 -0400 Subject: replaced close with ok/cancel moved add/remove below the list --- src/editexecutablesdialog.cpp | 9 +- src/editexecutablesdialog.h | 3 +- src/editexecutablesdialog.ui | 218 +++++++++++++++++++++++------------------- 3 files changed, 125 insertions(+), 105 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 559bc8f0..f7348889 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -370,10 +370,8 @@ void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) ui->appIDOverwriteEdit->setEnabled(checked); } -void EditExecutablesDialog::on_buttonBox_clicked(QAbstractButton*) +void EditExecutablesDialog::on_buttonBox_accepted() { - // there's only a close button for now, so the actual button doesn't matter - if (executableChanged()) { QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), tr("You made changes to the current executable, do you want to save them?"), @@ -391,6 +389,11 @@ void EditExecutablesDialog::on_buttonBox_clicked(QAbstractButton*) accept(); } +void EditExecutablesDialog::on_buttonBox_rejected() +{ + reject(); +} + void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex ¤t) { if (current.isValid()) { diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 07d6459c..7f389b24 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -89,7 +89,8 @@ private slots: void on_browseDirButton_clicked(); - void on_buttonBox_clicked(QAbstractButton *button); + void on_buttonBox_accepted(); + void on_buttonBox_rejected(); void delayedRefresh(); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 1cf1116a..52bbf187 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -6,8 +6,8 @@ 0 0 - 524 - 366 + 710 + 293 @@ -82,6 +82,70 @@ + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add an executable + + + Add an executable + + + Add + + + + :/new/guiresources/resources/list-add.png:/new/guiresources/resources/list-add.png + + + + + + + Remove the selected executable + + + Remove the selected executable + + + Remove + + + + :/new/guiresources/resources/list-remove.png:/new/guiresources/resources/list-remove.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -99,15 +163,18 @@ 0 - - + + + 20 + + Title - + Name of the executable. This is only for display purposes. @@ -117,73 +184,69 @@ - - - - - + Binary - - - - Binary to run - - - Binary to run - - - - - - - Browse filesystem - - - Browse filesystem for the executable to run. - - - ... - - + + + + + + Binary to run + + + Binary to run + + + + + + + Browse filesystem + + + Browse filesystem for the executable to run. + + + ... + + + + - - - - - + Start in - - - - - - - ... - - + + + + + + + + + ... + + + + - - - - - + Arguments - + Arguments to pass to the application @@ -258,7 +321,7 @@ Right now the only case I know of where this needs to be overwritten is for the If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - Force Load Libraries (*) + Force Load Libraries (Profile Specific) @@ -294,51 +357,6 @@ Right now the only case I know of where this needs to be overwritten is for the - - - - (*) This setting is profile-specific - - - - - - - - - Add an executable - - - Add an executable - - - Add - - - - :/new/guiresources/resources/list-add.png:/new/guiresources/resources/list-add.png - - - - - - - Remove the selected executable - - - Remove the selected executable - - - Remove - - - - :/new/guiresources/resources/list-remove.png:/new/guiresources/resources/list-remove.png - - - - - @@ -362,19 +380,17 @@ Right now the only case I know of where this needs to be overwritten is for the - QDialogButtonBox::Close + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - titleEdit binaryEdit browseBinaryButton workingDirEdit browseDirButton - argumentsEdit overwriteAppIDBox appIDOverwriteEdit newFilesModCheckBox -- cgit v1.3.1 From 39d953c838d459085b4044c9ca5bda0248e47f6b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 21:03:25 -0400 Subject: load plugin executables after settings, allows for changing the order added warning that an executable is provided by a plugin, disable widgets that can't be changed refactoring EditExecutablesDialog --- src/editexecutablesdialog.cpp | 134 +++++++++++--- src/editexecutablesdialog.h | 9 +- src/editexecutablesdialog.ui | 405 ++++++++++++++++++++++-------------------- src/executableslist.cpp | 4 +- 4 files changed, 328 insertions(+), 224 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index f7348889..8494c892 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -47,15 +47,96 @@ EditExecutablesDialog::EditExecutablesDialog( ui->splitter->setStretchFactor(1, 1); refreshExecutablesWidget(); - ui->newFilesModBox->addItems(modList.allMods()); m_ForcedLibraries = m_Profile->determineForcedLibraries(ui->titleEdit->text()); + + updateUI(nullptr); +} + +EditExecutablesDialog::~EditExecutablesDialog() = default; + +void EditExecutablesDialog::updateUI(const Executable* e) +{ + if (e) { + setEdits(*e); + } else { + clearEdits(); + ui->removeButton->setEnabled(false); + } +} + +void EditExecutablesDialog::clearEdits() +{ + ui->titleEdit->clear(); + ui->binaryEdit->clear(); + ui->workingDirEdit->clear(); + ui->argumentsEdit->clear(); + ui->overwriteAppIDBox->setChecked(false); + ui->appIDOverwriteEdit->clear(); + ui->newFilesModCheckBox->setChecked(false); + ui->newFilesModBox->setCurrentIndex(-1); + ui->forceLoadCheckBox->setChecked(false); + ui->useAppIconCheckBox->setChecked(false); + + ui->pluginProvidedLabel->setVisible(false); } -EditExecutablesDialog::~EditExecutablesDialog() +void EditExecutablesDialog::setEdits(const Executable& e) { - delete ui; + ui->titleEdit->setText(e.title()); + ui->binaryEdit->setText(QDir::toNativeSeparators(e.binaryInfo().absoluteFilePath())); + ui->workingDirEdit->setText(QDir::toNativeSeparators(e.workingDirectory())); + ui->argumentsEdit->setText(e.arguments()); + ui->overwriteAppIDBox->setChecked(!e.steamAppID().isEmpty()); + ui->appIDOverwriteEdit->setText(e.steamAppID()); + ui->useAppIconCheckBox->setChecked(e.usesOwnIcon()); + + int modIndex = -1; + + QString customOverwrite = m_Profile->setting("custom_overwrites", e.title()).toString(); + if (!customOverwrite.isEmpty()) { + modIndex = ui->newFilesModBox->findText(customOverwrite); + } + + ui->newFilesModCheckBox->setChecked(modIndex != -1); + ui->newFilesModBox->setCurrentIndex(modIndex); + + const bool forcedLibraries = m_Profile->forcedLibrariesEnabled(e.title()); + ui->forceLoadCheckBox->setChecked(forcedLibraries); + ui->forceLoadButton->setEnabled(forcedLibraries); + + ui->pluginProvidedLabel->setVisible(!e.isCustom()); + + // only enabled for custom executables + ui->titleEdit->setEnabled(e.isCustom()); + ui->binaryEdit->setEnabled(e.isCustom()); + ui->browseBinaryButton->setEnabled(e.isCustom()); + ui->workingDirEdit->setEnabled(e.isCustom()); + ui->browseWorkingDirButton->setEnabled(e.isCustom()); + ui->argumentsEdit->setEnabled(e.isCustom()); + ui->overwriteAppIDBox->setEnabled(e.isCustom()); + ui->appIDOverwriteEdit->setEnabled(e.isCustom()); + ui->useAppIconCheckBox->setEnabled(e.isCustom()); + + // always enabled + ui->newFilesModCheckBox->setEnabled(true); + ui->newFilesModBox->setEnabled(true); + ui->forceLoadCheckBox->setEnabled(true); +} + +void EditExecutablesDialog::resetInput() +{ + ui->binaryEdit->setText(""); + ui->titleEdit->setText(""); + ui->workingDirEdit->clear(); + ui->argumentsEdit->setText(""); + ui->appIDOverwriteEdit->clear(); + ui->overwriteAppIDBox->setChecked(false); + ui->useAppIconCheckBox->setChecked(false); + ui->newFilesModCheckBox->setChecked(false); + ui->forceLoadCheckBox->setChecked(false); + m_CurrentItem = nullptr; } ExecutablesList EditExecutablesDialog::getExecutablesList() const @@ -95,8 +176,8 @@ void EditExecutablesDialog::refreshExecutablesWidget() ui->executablesListBox->addItem(newItem); } - ui->addButton->setEnabled(false); - ui->removeButton->setEnabled(false); + //ui->addButton->setEnabled(false); + //ui->removeButton->setEnabled(false); } @@ -131,20 +212,6 @@ void EditExecutablesDialog::updateButtonStates() ui->addButton->setEnabled(enabled); } -void EditExecutablesDialog::resetInput() -{ - ui->binaryEdit->setText(""); - ui->titleEdit->setText(""); - ui->workingDirEdit->clear(); - ui->argumentsEdit->setText(""); - ui->appIDOverwriteEdit->clear(); - ui->overwriteAppIDBox->setChecked(false); - ui->useAppIconCheckBox->setChecked(false); - ui->newFilesModCheckBox->setChecked(false); - ui->forceLoadCheckBox->setChecked(false); - m_CurrentItem = nullptr; -} - void EditExecutablesDialog::saveExecutable() { @@ -260,7 +327,7 @@ void EditExecutablesDialog::on_browseBinaryButton_clicked() } } -void EditExecutablesDialog::on_browseDirButton_clicked() +void EditExecutablesDialog::on_browseWorkingDirButton_clicked() { QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, tr("Select a directory")); @@ -359,10 +426,27 @@ bool EditExecutablesDialog::executableChanged() } void EditExecutablesDialog::on_executablesListBox_itemSelectionChanged() { - if (ui->executablesListBox->selectedItems().size() == 0) { - // deselected - resetInput(); + const auto selection = ui->executablesListBox->selectedItems(); + + if (selection.empty()) { + updateUI(nullptr); + return; + } + + auto* item = selection[0]; + if (!item) { + return; } + + const auto& title = item->text(); + auto itor = m_ExecutablesList.find(title); + + if (itor == m_ExecutablesList.end()) { + qWarning().nospace() << "selection: executable '" << title << "' not found"; + return; + } + + updateUI(&*itor); } void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) @@ -395,7 +479,7 @@ void EditExecutablesDialog::on_buttonBox_rejected() } void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex ¤t) -{ +{/* if (current.isValid()) { if (executableChanged()) { @@ -459,7 +543,7 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur bool forcedLibraries = m_Profile->forcedLibrariesEnabled(ui->titleEdit->text()); ui->forceLoadButton->setEnabled(forcedLibraries); ui->forceLoadCheckBox->setChecked(forcedLibraries); - } + }*/ } void EditExecutablesDialog::on_newFilesModCheckBox_toggled(bool checked) diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 7f389b24..bb1538ef 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -87,7 +87,7 @@ private slots: void on_overwriteAppIDBox_toggled(bool checked); - void on_browseDirButton_clicked(); + void on_browseWorkingDirButton_clicked(); void on_buttonBox_accepted(); void on_buttonBox_rejected(); @@ -113,7 +113,7 @@ private: void updateButtonStates(); private: - Ui::EditExecutablesDialog *ui; + std::unique_ptr ui; QListWidgetItem *m_CurrentItem; @@ -124,6 +124,11 @@ private: QList m_ForcedLibraries; const MOBase::IPluginGame *m_GamePlugin; + + + void updateUI(const Executable* e); + void clearEdits(); + void setEdits(const Executable& e); }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 52bbf187..2c9a74f3 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -7,15 +7,9 @@ 0 0 710 - 293 + 348 - - - 200 - 200 - - Modify Executables @@ -77,9 +71,6 @@ Qt::TargetMoveAction - - QAbstractItemView::ExtendedSelection - @@ -163,197 +154,221 @@ 0 - - - 20 - - - - - Title - - - - - - - Name of the executable. This is only for display purposes. - - - Name of the executable. This is only for display purposes. - - - - - - - Binary - - - - - - - - - Binary to run - - - Binary to run - - - - - - - Browse filesystem - - - Browse filesystem for the executable to run. - - - ... - - - - - - - - - Start in - - - - - - - - - - - - ... - - - - - - - - - Arguments - - - - - - - Arguments to pass to the application - - - Arguments to pass to the application - - - - - - - - - - - Allow the Steam AppID to be used for this executable to be changed. - - - Allow the Steam AppID to be used for this executable to be changed. + + + + 10 + + + + + 20 + + + + + Title + + + + + + + Name of the executable. This is only for display purposes. + + + Name of the executable. This is only for display purposes. + + + + + + + Binary + + + + + + + + + Binary to run + + + Binary to run + + + + + + + Browse filesystem + + + Browse filesystem for the executable to run. + + + ... + + + + + + + + + Start in + + + + + + + + + + + + ... + + + + + + + + + Arguments + + + + + + + Arguments to pass to the application + + + Arguments to pass to the application + + + + + + + + + + + Allow the Steam AppID to be used for this executable to be changed. + + + Allow the Steam AppID to be used for this executable to be changed. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game. Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured. - - - Overwrite Steam AppID - - - - - - - false - - - Steam AppID to use for this executable that differs from the games AppID. - - - Steam AppID to use for this executable that differs from the games AppID. + + + Overwrite Steam AppID + + + + + + + false + + + Steam AppID to use for this executable that differs from the games AppID. + + + Steam AppID to use for this executable that differs from the games AppID. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850). Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured. - - - - - - - - - - - If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. - - - Create Files in Mod instead of Overwrite (*) - - - - - - - false - - - - - - - - - - - If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - - - Force Load Libraries (Profile Specific) - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - false - - - Configure Libraries - - - - + + + + + + + + + + + If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. + + + Create Files in Mod instead of Overwrite (*) + + + + + + + false + + + + + + + + + + + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. + + + Force Load Libraries (Profile Specific) + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Configure Libraries + + + + + + + + + Use Application's Icon for shortcuts + + + + + - + + + + true + + - Use Application's Icon for shortcuts + This executable is provided by the game plugin + + + Qt::AlignCenter @@ -390,7 +405,7 @@ Right now the only case I know of where this needs to be overwritten is for the binaryEdit browseBinaryButton workingDirEdit - browseDirButton + browseWorkingDirButton overwriteAppIDBox appIDOverwriteEdit newFilesModCheckBox diff --git a/src/executableslist.cpp b/src/executableslist.cpp index d884bdf2..8174eb1b 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -65,8 +65,6 @@ bool ExecutablesList::empty() const void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) { - addFromPlugin(game); - qDebug("setting up configured executables"); int numCustomExecutables = settings.beginReadArray("customExecutables"); @@ -91,6 +89,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) } settings.endArray(); + + addFromPlugin(game); } void ExecutablesList::store(QSettings& settings) -- cgit v1.3.1 From d125036903bbf5d956233ab79e1fdda3724a1e9e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 21:15:29 -0400 Subject: stop changing add button to modify, disable remove for plugin executables general clean up of member variable names, whitespace --- src/editexecutablesdialog.cpp | 78 +++++++++++++++++++++++-------------------- src/editexecutablesdialog.h | 63 +++++++++------------------------- 2 files changed, 57 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 8494c892..941ab4e0 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -36,10 +36,11 @@ EditExecutablesDialog::EditExecutablesDialog( Profile *profile, const IPluginGame *game, QWidget *parent) : TutorableDialog("EditExecutables", parent) , ui(new Ui::EditExecutablesDialog) - , m_CurrentItem(nullptr) - , m_ExecutablesList(executablesList) - , m_Profile(profile) - , m_GamePlugin(game) + , m_currentItem(nullptr) + , m_executablesList(executablesList) + , m_profile(profile) + , m_gamePlugin(game) + , m_dirty(false) { ui->setupUi(this); ui->splitter->setSizes({200, 1}); @@ -49,7 +50,7 @@ EditExecutablesDialog::EditExecutablesDialog( refreshExecutablesWidget(); ui->newFilesModBox->addItems(modList.allMods()); - m_ForcedLibraries = m_Profile->determineForcedLibraries(ui->titleEdit->text()); + m_forcedLibraries = m_profile->determineForcedLibraries(ui->titleEdit->text()); updateUI(nullptr); } @@ -60,6 +61,7 @@ void EditExecutablesDialog::updateUI(const Executable* e) { if (e) { setEdits(*e); + ui->removeButton->setEnabled(e->isCustom()); } else { clearEdits(); ui->removeButton->setEnabled(false); @@ -94,7 +96,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) int modIndex = -1; - QString customOverwrite = m_Profile->setting("custom_overwrites", e.title()).toString(); + QString customOverwrite = m_profile->setting("custom_overwrites", e.title()).toString(); if (!customOverwrite.isEmpty()) { modIndex = ui->newFilesModBox->findText(customOverwrite); } @@ -102,7 +104,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->newFilesModCheckBox->setChecked(modIndex != -1); ui->newFilesModBox->setCurrentIndex(modIndex); - const bool forcedLibraries = m_Profile->forcedLibrariesEnabled(e.title()); + const bool forcedLibraries = m_profile->forcedLibrariesEnabled(e.title()); ui->forceLoadCheckBox->setChecked(forcedLibraries); ui->forceLoadButton->setEnabled(forcedLibraries); @@ -125,6 +127,10 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->forceLoadCheckBox->setEnabled(true); } + + + + void EditExecutablesDialog::resetInput() { ui->binaryEdit->setText(""); @@ -136,7 +142,7 @@ void EditExecutablesDialog::resetInput() ui->useAppIconCheckBox->setChecked(false); ui->newFilesModCheckBox->setChecked(false); ui->forceLoadCheckBox->setChecked(false); - m_CurrentItem = nullptr; + m_currentItem = nullptr; } ExecutablesList EditExecutablesDialog::getExecutablesList() const @@ -144,9 +150,9 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const ExecutablesList newList; for (int i = 0; i < ui->executablesListBox->count(); ++i) { const auto& title = ui->executablesListBox->item(i)->text(); - auto itor = m_ExecutablesList.find(title); + auto itor = m_executablesList.find(title); - if (itor == m_ExecutablesList.end()) { + if (itor == m_executablesList.end()) { qWarning().nospace() << "getExecutablesList(): executable '" << title << "' not found"; @@ -163,7 +169,7 @@ void EditExecutablesDialog::refreshExecutablesWidget() { ui->executablesListBox->clear(); - for(const auto& exe : m_ExecutablesList) { + for(const auto& exe : m_executablesList) { QListWidgetItem *newItem = new QListWidgetItem(exe.title()); if (!exe.isCustom()) { @@ -209,7 +215,7 @@ void EditExecutablesDialog::updateButtonStates() enabled = false; } - ui->addButton->setEnabled(enabled); + //ui->addButton->setEnabled(enabled); } @@ -219,7 +225,7 @@ void EditExecutablesDialog::saveExecutable() if (ui->useAppIconCheckBox->isChecked()) flags |= Executable::UseApplicationIcon; - m_ExecutablesList.setExecutable(Executable() + m_executablesList.setExecutable(Executable() .title(ui->titleEdit->text()) .binaryInfo(QDir::fromNativeSeparators(ui->binaryEdit->text())) .arguments(ui->argumentsEdit->text()) @@ -228,16 +234,16 @@ void EditExecutablesDialog::saveExecutable() .flags(flags)); if (ui->newFilesModCheckBox->isChecked()) { - m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(), + m_profile->storeSetting("custom_overwrites", ui->titleEdit->text(), ui->newFilesModBox->currentText()); } else { - m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text()); + m_profile->removeSetting("custom_overwrites", ui->titleEdit->text()); } - m_Profile->removeForcedLibraries(ui->titleEdit->text()); - m_Profile->storeForcedLibraries(ui->titleEdit->text(), m_ForcedLibraries); - m_Profile->setForcedLibrariesEnabled(ui->titleEdit->text(), ui->forceLoadCheckBox->isChecked()); + m_profile->removeForcedLibraries(ui->titleEdit->text()); + m_profile->storeForcedLibraries(ui->titleEdit->text(), m_forcedLibraries); + m_profile->setForcedLibrariesEnabled(ui->titleEdit->text(), ui->forceLoadCheckBox->isChecked()); } @@ -252,10 +258,10 @@ void EditExecutablesDialog::delayedRefresh() void EditExecutablesDialog::on_forceLoadButton_clicked() { - ForcedLoadDialog dialog(m_GamePlugin, this); - dialog.setValues(m_ForcedLibraries); + ForcedLoadDialog dialog(m_gamePlugin, this); + dialog.setValues(m_forcedLibraries); if (dialog.exec() == QDialog::Accepted) { - m_ForcedLibraries = dialog.values(); + m_forcedLibraries = dialog.values(); } } @@ -344,9 +350,9 @@ void EditExecutablesDialog::on_removeButton_clicked() { if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text()); - m_Profile->removeForcedLibraries(ui->titleEdit->text()); - m_ExecutablesList.remove(ui->titleEdit->text()); + m_profile->removeSetting("custom_overwrites", ui->titleEdit->text()); + m_profile->removeForcedLibraries(ui->titleEdit->text()); + m_executablesList.remove(ui->titleEdit->text()); } resetInput(); @@ -355,7 +361,7 @@ void EditExecutablesDialog::on_removeButton_clicked() void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) { - QPushButton *addButton = findChild("addButton"); + /*QPushButton *addButton = findChild("addButton"); QPushButton *removeButton = findChild("removeButton"); QListWidget *executablesWidget = findChild("executablesListBox"); @@ -371,17 +377,17 @@ void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) // existing item. is it a custom one? addButton->setText(tr("Modify")); removeButton->setEnabled(true); - } + }*/ } bool EditExecutablesDialog::executableChanged() { - if (m_CurrentItem != nullptr) { - const auto& title = m_CurrentItem->text(); - auto itor = m_ExecutablesList.find(title); + if (m_currentItem != nullptr) { + const auto& title = m_currentItem->text(); + auto itor = m_executablesList.find(title); - if (itor == m_ExecutablesList.end()) { + if (itor == m_executablesList.end()) { qWarning().nospace() << "executableChanged(): title '" << title << "' not found"; @@ -390,12 +396,12 @@ bool EditExecutablesDialog::executableChanged() const Executable& selectedExecutable = *itor; - QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.title()).toString(); + QString storedCustomOverwrite = m_profile->setting("custom_overwrites", selectedExecutable.title()).toString(); bool forcedLibrariesDirty = false; - auto forcedLibaries = m_Profile->determineForcedLibraries(selectedExecutable.title()); + auto forcedLibaries = m_profile->determineForcedLibraries(selectedExecutable.title()); forcedLibrariesDirty |= !std::equal(forcedLibaries.begin(), forcedLibaries.end(), - m_ForcedLibraries.begin(), m_ForcedLibraries.end(), + m_forcedLibraries.begin(), m_forcedLibraries.end(), [](const ExecutableForcedLoadSetting &lhs, const ExecutableForcedLoadSetting &rhs) { return lhs.enabled() == rhs.enabled() && @@ -403,7 +409,7 @@ bool EditExecutablesDialog::executableChanged() lhs.library() == rhs.library() && lhs.process() == rhs.process(); }); - forcedLibrariesDirty |= m_Profile->setting("forced_libraries", ui->titleEdit->text() + "/enabled", false).toBool() != + forcedLibrariesDirty |= m_profile->setting("forced_libraries", ui->titleEdit->text() + "/enabled", false).toBool() != ui->forceLoadCheckBox->isChecked(); return selectedExecutable.title() != ui->titleEdit->text() @@ -439,9 +445,9 @@ void EditExecutablesDialog::on_executablesListBox_itemSelectionChanged() } const auto& title = item->text(); - auto itor = m_ExecutablesList.find(title); + auto itor = m_executablesList.find(title); - if (itor == m_ExecutablesList.end()) { + if (itor == m_executablesList.end()) { qWarning().nospace() << "selection: executable '" << title << "' not found"; return; } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index bb1538ef..9214941d 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -32,10 +32,8 @@ namespace Ui { class EditExecutablesDialog; } - class ModList; - /** * @brief Dialog to manage the list of executables **/ @@ -44,91 +42,60 @@ class EditExecutablesDialog : public MOBase::TutorableDialog Q_OBJECT public: - /** - * @brief constructor - * * @param executablesList current list of executables * @param parent parent widget **/ - explicit EditExecutablesDialog(const ExecutablesList &executablesList, - const ModList &modList, - Profile *profile, - const MOBase::IPluginGame *game, - QWidget *parent = 0); + explicit EditExecutablesDialog( + const ExecutablesList &executablesList, const ModList &modList, + Profile *profile, const MOBase::IPluginGame *game, QWidget *parent = 0); ~EditExecutablesDialog(); /** * @brief retrieve the updated list of executables - * * @return updated list of executables **/ ExecutablesList getExecutablesList() const; - void saveExecutable(); - private slots: void on_newFilesModCheckBox_toggled(bool checked); - -private slots: - void on_binaryEdit_textChanged(const QString &arg1); - void on_workingDirEdit_textChanged(const QString &arg1); - void on_addButton_clicked(); - void on_browseBinaryButton_clicked(); - void on_removeButton_clicked(); - void on_titleEdit_textChanged(const QString &arg1); - void on_overwriteAppIDBox_toggled(bool checked); - void on_browseWorkingDirButton_clicked(); - void on_buttonBox_accepted(); void on_buttonBox_rejected(); - void delayedRefresh(); - void on_executablesListBox_itemSelectionChanged(); - void on_executablesListBox_clicked(const QModelIndex &index); - void on_forceLoadButton_clicked(); - void on_forceLoadCheckBox_toggled(); -private: - - void resetInput(); - - void refreshExecutablesWidget(); - - bool executableChanged(); - - void updateButtonStates(); - private: std::unique_ptr ui; + ExecutablesList m_executablesList; + Profile *m_profile; + const MOBase::IPluginGame *m_gamePlugin; + bool m_dirty; - QListWidgetItem *m_CurrentItem; - - ExecutablesList m_ExecutablesList; - - Profile *m_Profile; - - QList m_ForcedLibraries; - - const MOBase::IPluginGame *m_GamePlugin; + QListWidgetItem *m_currentItem; + QList m_forcedLibraries; void updateUI(const Executable* e); void clearEdits(); void setEdits(const Executable& e); + + void resetInput(); + void refreshExecutablesWidget(); + bool executableChanged(); + void updateButtonStates(); + void saveExecutable(); }; #endif // EDITEXECUTABLESDIALOG_H -- cgit v1.3.1 From b8199f0c471f2b5ee2b070a96b42ad0040cddf2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 21:57:29 -0400 Subject: renamed most of the widgets to shorter or more descriptive names (newFilesModCheckBox?) modifying widgets calls save() --- src/editexecutablesdialog.cpp | 361 +++++++++++++++++++++++------------------- src/editexecutablesdialog.h | 40 +++-- src/editexecutablesdialog.ui | 63 ++++---- 3 files changed, 257 insertions(+), 207 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 941ab4e0..621c670f 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -40,7 +40,7 @@ EditExecutablesDialog::EditExecutablesDialog( , m_executablesList(executablesList) , m_profile(profile) , m_gamePlugin(game) - , m_dirty(false) + , m_settingUI(false) { ui->setupUi(this); ui->splitter->setSizes({200, 1}); @@ -48,108 +48,189 @@ EditExecutablesDialog::EditExecutablesDialog( ui->splitter->setStretchFactor(1, 1); refreshExecutablesWidget(); - ui->newFilesModBox->addItems(modList.allMods()); + ui->mods->addItems(modList.allMods()); - m_forcedLibraries = m_profile->determineForcedLibraries(ui->titleEdit->text()); + m_forcedLibraries = m_profile->determineForcedLibraries(ui->title->text()); + + // title textbox also has to change the list item, will call save manually + connect(ui->title, &QLineEdit::textChanged, [&](auto&& s){ onTitleChanged(s); }); + connect(ui->binary, &QLineEdit::textChanged, [&]{ save(); }); + connect(ui->workingDirectory, &QLineEdit::textChanged, [&]{ save(); }); + connect(ui->arguments, &QLineEdit::textChanged, [&]{ save(); }); updateUI(nullptr); } EditExecutablesDialog::~EditExecutablesDialog() = default; +QListWidgetItem* EditExecutablesDialog::selectedItem() +{ + const auto selection = ui->list->selectedItems(); + + if (selection.empty()) { + return nullptr; + } + + return selection[0]; +} + +Executable* EditExecutablesDialog::selectedExe() +{ + auto* item = selectedItem(); + if (!item) { + return nullptr; + } + + const auto& title = item->text(); + auto itor = m_executablesList.find(title); + + if (itor == m_executablesList.end()) { + return nullptr; + } + + return &*itor; +} + void EditExecutablesDialog::updateUI(const Executable* e) { + m_settingUI = true; + if (e) { setEdits(*e); - ui->removeButton->setEnabled(e->isCustom()); + ui->remove->setEnabled(e->isCustom()); } else { clearEdits(); - ui->removeButton->setEnabled(false); + ui->remove->setEnabled(false); } + + m_settingUI = false; } void EditExecutablesDialog::clearEdits() { - ui->titleEdit->clear(); - ui->binaryEdit->clear(); - ui->workingDirEdit->clear(); - ui->argumentsEdit->clear(); - ui->overwriteAppIDBox->setChecked(false); - ui->appIDOverwriteEdit->clear(); - ui->newFilesModCheckBox->setChecked(false); - ui->newFilesModBox->setCurrentIndex(-1); - ui->forceLoadCheckBox->setChecked(false); - ui->useAppIconCheckBox->setChecked(false); + ui->title->clear(); + ui->binary->clear(); + ui->workingDirectory->clear(); + ui->arguments->clear(); + ui->overwriteSteamAppID->setChecked(false); + ui->steamAppID->clear(); + ui->createFilesInMod->setChecked(false); + ui->mods->setCurrentIndex(-1); + ui->forceLoadLibraries->setChecked(false); + ui->useApplicationIcon->setChecked(false); ui->pluginProvidedLabel->setVisible(false); } void EditExecutablesDialog::setEdits(const Executable& e) { - ui->titleEdit->setText(e.title()); - ui->binaryEdit->setText(QDir::toNativeSeparators(e.binaryInfo().absoluteFilePath())); - ui->workingDirEdit->setText(QDir::toNativeSeparators(e.workingDirectory())); - ui->argumentsEdit->setText(e.arguments()); - ui->overwriteAppIDBox->setChecked(!e.steamAppID().isEmpty()); - ui->appIDOverwriteEdit->setText(e.steamAppID()); - ui->useAppIconCheckBox->setChecked(e.usesOwnIcon()); + ui->title->setText(e.title()); + ui->binary->setText(QDir::toNativeSeparators(e.binaryInfo().absoluteFilePath())); + ui->workingDirectory->setText(QDir::toNativeSeparators(e.workingDirectory())); + ui->arguments->setText(e.arguments()); + ui->overwriteSteamAppID->setChecked(!e.steamAppID().isEmpty()); + ui->steamAppID->setText(e.steamAppID()); + ui->useApplicationIcon->setChecked(e.usesOwnIcon()); int modIndex = -1; QString customOverwrite = m_profile->setting("custom_overwrites", e.title()).toString(); if (!customOverwrite.isEmpty()) { - modIndex = ui->newFilesModBox->findText(customOverwrite); + modIndex = ui->mods->findText(customOverwrite); } - ui->newFilesModCheckBox->setChecked(modIndex != -1); - ui->newFilesModBox->setCurrentIndex(modIndex); + ui->createFilesInMod->setChecked(modIndex != -1); + ui->mods->setCurrentIndex(modIndex); const bool forcedLibraries = m_profile->forcedLibrariesEnabled(e.title()); - ui->forceLoadCheckBox->setChecked(forcedLibraries); - ui->forceLoadButton->setEnabled(forcedLibraries); + ui->forceLoadLibraries->setChecked(forcedLibraries); + ui->configureLibraries->setEnabled(forcedLibraries); ui->pluginProvidedLabel->setVisible(!e.isCustom()); // only enabled for custom executables - ui->titleEdit->setEnabled(e.isCustom()); - ui->binaryEdit->setEnabled(e.isCustom()); - ui->browseBinaryButton->setEnabled(e.isCustom()); - ui->workingDirEdit->setEnabled(e.isCustom()); - ui->browseWorkingDirButton->setEnabled(e.isCustom()); - ui->argumentsEdit->setEnabled(e.isCustom()); - ui->overwriteAppIDBox->setEnabled(e.isCustom()); - ui->appIDOverwriteEdit->setEnabled(e.isCustom()); - ui->useAppIconCheckBox->setEnabled(e.isCustom()); + ui->title->setEnabled(e.isCustom()); + ui->binary->setEnabled(e.isCustom()); + ui->browseBinary->setEnabled(e.isCustom()); + ui->workingDirectory->setEnabled(e.isCustom()); + ui->browseWorkingDirectory->setEnabled(e.isCustom()); + ui->arguments->setEnabled(e.isCustom()); + ui->overwriteSteamAppID->setEnabled(e.isCustom()); + ui->steamAppID->setEnabled(e.isCustom()); + ui->useApplicationIcon->setEnabled(e.isCustom()); // always enabled - ui->newFilesModCheckBox->setEnabled(true); - ui->newFilesModBox->setEnabled(true); - ui->forceLoadCheckBox->setEnabled(true); + ui->createFilesInMod->setEnabled(true); + ui->mods->setEnabled(true); + ui->forceLoadLibraries->setEnabled(true); } +void EditExecutablesDialog::save() +{ + if (m_settingUI) { + // the ui is currently being set, ignore changes + return; + } + + auto* e = selectedExe(); + if (!e) { + qWarning("trying to save but nothing is selected"); + return; + } + + qDebug().nospace() << "saving '" << e->title() << "'"; + + e->title(ui->title->text()); + e->binaryInfo(ui->binary->text()); + e->workingDirectory(ui->workingDirectory->text()); + e->arguments(ui->arguments->text()); + + if (ui->overwriteSteamAppID->isChecked()) { + e->steamAppID(ui->steamAppID->text()); + } else { + e->steamAppID(""); + } +} + +void EditExecutablesDialog::onTitleChanged(const QString& s) +{ + if (m_settingUI) { + // the ui is currently being set, ignore changes + return; + } + // must save first because it relies on the text in the list to find the + // executable to modify + save(); + // once the executable is saved, the list item must be changed to match the + // new name + if (auto* i=selectedItem()) { + i->setText(s); + } +} void EditExecutablesDialog::resetInput() { - ui->binaryEdit->setText(""); - ui->titleEdit->setText(""); - ui->workingDirEdit->clear(); - ui->argumentsEdit->setText(""); - ui->appIDOverwriteEdit->clear(); - ui->overwriteAppIDBox->setChecked(false); - ui->useAppIconCheckBox->setChecked(false); - ui->newFilesModCheckBox->setChecked(false); - ui->forceLoadCheckBox->setChecked(false); + ui->binary->setText(""); + ui->title->setText(""); + ui->workingDirectory->clear(); + ui->arguments->setText(""); + ui->overwriteSteamAppID->setChecked(false); + ui->createFilesInMod->setChecked(false); + ui->forceLoadLibraries->setChecked(false); + ui->steamAppID->clear(); + ui->useApplicationIcon->setChecked(false); + m_currentItem = nullptr; } ExecutablesList EditExecutablesDialog::getExecutablesList() const { ExecutablesList newList; - for (int i = 0; i < ui->executablesListBox->count(); ++i) { - const auto& title = ui->executablesListBox->item(i)->text(); + for (int i = 0; i < ui->list->count(); ++i) { + const auto& title = ui->list->item(i)->text(); auto itor = m_executablesList.find(title); if (itor == m_executablesList.end()) { @@ -167,7 +248,7 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const void EditExecutablesDialog::refreshExecutablesWidget() { - ui->executablesListBox->clear(); + ui->list->clear(); for(const auto& exe : m_executablesList) { QListWidgetItem *newItem = new QListWidgetItem(exe.title()); @@ -179,7 +260,7 @@ void EditExecutablesDialog::refreshExecutablesWidget() newItem->setFont(f); } - ui->executablesListBox->addItem(newItem); + ui->list->addItem(newItem); } //ui->addButton->setEnabled(false); @@ -187,28 +268,18 @@ void EditExecutablesDialog::refreshExecutablesWidget() } -void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &name) -{ - updateButtonStates(); -} - -void EditExecutablesDialog::on_workingDirEdit_textChanged(const QString &dir) -{ - updateButtonStates(); -} - void EditExecutablesDialog::updateButtonStates() { bool enabled = true; - QString filePath(ui->binaryEdit->text()); + QString filePath(ui->binary->text()); QFileInfo fileInfo(filePath); if (!fileInfo.exists()) enabled = false; if (!fileInfo.isFile()) enabled = false; - QString dirPath(ui->workingDirEdit->text()); + QString dirPath(ui->workingDirectory->text()); if (!dirPath.isEmpty()) { QDir dirInfo(dirPath); if (!dirInfo.exists()) @@ -222,41 +293,41 @@ void EditExecutablesDialog::updateButtonStates() void EditExecutablesDialog::saveExecutable() { Executable::Flags flags = Executable::CustomExecutable; - if (ui->useAppIconCheckBox->isChecked()) + if (ui->useApplicationIcon->isChecked()) flags |= Executable::UseApplicationIcon; m_executablesList.setExecutable(Executable() - .title(ui->titleEdit->text()) - .binaryInfo(QDir::fromNativeSeparators(ui->binaryEdit->text())) - .arguments(ui->argumentsEdit->text()) - .steamAppID(ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "") - .workingDirectory(QDir::fromNativeSeparators(ui->workingDirEdit->text())) + .title(ui->title->text()) + .binaryInfo(QDir::fromNativeSeparators(ui->binary->text())) + .arguments(ui->arguments->text()) + .steamAppID(ui->overwriteSteamAppID->isChecked() ? ui->steamAppID->text() : "") + .workingDirectory(QDir::fromNativeSeparators(ui->workingDirectory->text())) .flags(flags)); - if (ui->newFilesModCheckBox->isChecked()) { - m_profile->storeSetting("custom_overwrites", ui->titleEdit->text(), - ui->newFilesModBox->currentText()); + if (ui->createFilesInMod->isChecked()) { + m_profile->storeSetting("custom_overwrites", ui->title->text(), + ui->mods->currentText()); } else { - m_profile->removeSetting("custom_overwrites", ui->titleEdit->text()); + m_profile->removeSetting("custom_overwrites", ui->title->text()); } - m_profile->removeForcedLibraries(ui->titleEdit->text()); - m_profile->storeForcedLibraries(ui->titleEdit->text(), m_forcedLibraries); - m_profile->setForcedLibrariesEnabled(ui->titleEdit->text(), ui->forceLoadCheckBox->isChecked()); + m_profile->removeForcedLibraries(ui->title->text()); + m_profile->storeForcedLibraries(ui->title->text(), m_forcedLibraries); + m_profile->setForcedLibrariesEnabled(ui->title->text(), ui->forceLoadLibraries->isChecked()); } void EditExecutablesDialog::delayedRefresh() { - QModelIndex index = ui->executablesListBox->currentIndex(); + /*QModelIndex index = ui->executablesListBox->currentIndex(); resetInput(); refreshExecutablesWidget(); - on_executablesListBox_clicked(index); + on_executablesListBox_clicked(index);*/ } -void EditExecutablesDialog::on_forceLoadButton_clicked() +void EditExecutablesDialog::on_configureLibraries_clicked() { ForcedLoadDialog dialog(m_gamePlugin, this); dialog.setValues(m_forcedLibraries); @@ -265,13 +336,13 @@ void EditExecutablesDialog::on_forceLoadButton_clicked() } } -void EditExecutablesDialog::on_forceLoadCheckBox_toggled() +void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) { - ui->forceLoadButton->setEnabled(ui->forceLoadCheckBox->isChecked()); + ui->configureLibraries->setEnabled(ui->forceLoadLibraries->isChecked()); } -void EditExecutablesDialog::on_addButton_clicked() +void EditExecutablesDialog::on_add_clicked() { if (executableChanged()) { saveExecutable(); @@ -281,7 +352,7 @@ void EditExecutablesDialog::on_addButton_clicked() refreshExecutablesWidget(); } -void EditExecutablesDialog::on_browseBinaryButton_clicked() +void EditExecutablesDialog::on_browseBinary_clicked() { QString binaryName = FileDialogMemory::getOpenFileName( "editExecutableBinary", this, tr("Select a binary"), QString(), @@ -319,21 +390,21 @@ void EditExecutablesDialog::on_browseBinaryButton_clicked() tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe " "from that installation as the binary.")); } else { - ui->binaryEdit->setText(binaryPath); + ui->binary->setText(binaryPath); } - ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); - ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); + ui->workingDirectory->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); + ui->arguments->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); } else { - ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); + ui->binary->setText(QDir::toNativeSeparators(binaryName)); } - if (ui->titleEdit->text().isEmpty()) { - ui->titleEdit->setText(QFileInfo(binaryName).baseName()); + if (ui->title->text().isEmpty()) { + ui->title->setText(QFileInfo(binaryName).baseName()); } } -void EditExecutablesDialog::on_browseWorkingDirButton_clicked() +void EditExecutablesDialog::on_browseWorkingDirectory_clicked() { QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, tr("Select a directory")); @@ -343,43 +414,22 @@ void EditExecutablesDialog::on_browseWorkingDirButton_clicked() return; } - ui->workingDirEdit->setText(dirName); + ui->workingDirectory->setText(dirName); } -void EditExecutablesDialog::on_removeButton_clicked() +void EditExecutablesDialog::on_remove_clicked() { - if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), + if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->title->text()), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_profile->removeSetting("custom_overwrites", ui->titleEdit->text()); - m_profile->removeForcedLibraries(ui->titleEdit->text()); - m_executablesList.remove(ui->titleEdit->text()); + m_profile->removeSetting("custom_overwrites", ui->title->text()); + m_profile->removeForcedLibraries(ui->title->text()); + m_executablesList.remove(ui->title->text()); } resetInput(); refreshExecutablesWidget(); } -void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) -{ - /*QPushButton *addButton = findChild("addButton"); - QPushButton *removeButton = findChild("removeButton"); - - QListWidget *executablesWidget = findChild("executablesListBox"); - - QList existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); - - addButton->setEnabled(arg1.length() != 0); - - if (existingItems.count() == 0) { - addButton->setText(tr("Add")); - removeButton->setEnabled(false); - } else { - // existing item. is it a custom one? - addButton->setText(tr("Modify")); - removeButton->setEnabled(true); - }*/ -} - bool EditExecutablesDialog::executableChanged() { @@ -409,58 +459,39 @@ bool EditExecutablesDialog::executableChanged() lhs.library() == rhs.library() && lhs.process() == rhs.process(); }); - forcedLibrariesDirty |= m_profile->setting("forced_libraries", ui->titleEdit->text() + "/enabled", false).toBool() != - ui->forceLoadCheckBox->isChecked(); - - 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.workingDirectory() != QDir::fromNativeSeparators(ui->workingDirEdit->text()) - || selectedExecutable.binaryInfo().absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) - || selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked() + forcedLibrariesDirty |= m_profile->setting("forced_libraries", ui->title->text() + "/enabled", false).toBool() != + ui->forceLoadLibraries->isChecked(); + + return selectedExecutable.title() != ui->title->text() + || selectedExecutable.arguments() != ui->arguments->text() + || selectedExecutable.steamAppID() != ui->steamAppID->text() + || !storedCustomOverwrite.isEmpty() != ui->createFilesInMod->isChecked() + || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->mods->currentText()) + || selectedExecutable.workingDirectory() != QDir::fromNativeSeparators(ui->workingDirectory->text()) + || selectedExecutable.binaryInfo().absoluteFilePath() != QDir::fromNativeSeparators(ui->binary->text()) + || selectedExecutable.usesOwnIcon() != ui->useApplicationIcon->isChecked() || forcedLibrariesDirty ; } else { - QFileInfo fileInfo(ui->binaryEdit->text()); - return !ui->binaryEdit->text().isEmpty() - && !ui->titleEdit->text().isEmpty() + QFileInfo fileInfo(ui->binary->text()); + return !ui->binary->text().isEmpty() + && !ui->title->text().isEmpty() && fileInfo.exists() && fileInfo.isFile(); } } -void EditExecutablesDialog::on_executablesListBox_itemSelectionChanged() -{ - const auto selection = ui->executablesListBox->selectedItems(); - - if (selection.empty()) { - updateUI(nullptr); - return; - } - - auto* item = selection[0]; - if (!item) { - return; - } - - const auto& title = item->text(); - auto itor = m_executablesList.find(title); - if (itor == m_executablesList.end()) { - qWarning().nospace() << "selection: executable '" << title << "' not found"; - return; - } - - updateUI(&*itor); +void EditExecutablesDialog::on_list_itemSelectionChanged() +{ + updateUI(selectedExe()); } -void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) +void EditExecutablesDialog::on_overwriteSteamAppID_toggled(bool checked) { - ui->appIDOverwriteEdit->setEnabled(checked); + ui->steamAppID->setEnabled(checked); } -void EditExecutablesDialog::on_buttonBox_accepted() +void EditExecutablesDialog::on_buttons_accepted() { if (executableChanged()) { QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), @@ -479,13 +510,13 @@ void EditExecutablesDialog::on_buttonBox_accepted() accept(); } -void EditExecutablesDialog::on_buttonBox_rejected() +void EditExecutablesDialog::on_buttons_rejected() { reject(); } -void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex ¤t) -{/* +/*void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex ¤t) +{ if (current.isValid()) { if (executableChanged()) { @@ -549,10 +580,14 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur bool forcedLibraries = m_Profile->forcedLibrariesEnabled(ui->titleEdit->text()); ui->forceLoadButton->setEnabled(forcedLibraries); ui->forceLoadCheckBox->setChecked(forcedLibraries); - }*/ + } +}*/ + +void EditExecutablesDialog::on_createFilesInMod_toggled(bool checked) +{ + ui->mods->setEnabled(checked); } -void EditExecutablesDialog::on_newFilesModCheckBox_toggled(bool checked) +void EditExecutablesDialog::on_useApplicationIcon_toggled(bool checked) { - ui->newFilesModBox->setEnabled(checked); } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 9214941d..f28658e3 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -59,43 +59,51 @@ public: ExecutablesList getExecutablesList() const; private slots: - void on_newFilesModCheckBox_toggled(bool checked); - void on_binaryEdit_textChanged(const QString &arg1); - void on_workingDirEdit_textChanged(const QString &arg1); - void on_addButton_clicked(); - void on_browseBinaryButton_clicked(); - void on_removeButton_clicked(); - void on_titleEdit_textChanged(const QString &arg1); - void on_overwriteAppIDBox_toggled(bool checked); - void on_browseWorkingDirButton_clicked(); - void on_buttonBox_accepted(); - void on_buttonBox_rejected(); + void on_list_itemSelectionChanged(); + + void on_add_clicked(); + void on_remove_clicked(); + + void on_overwriteSteamAppID_toggled(bool checked); + void on_createFilesInMod_toggled(bool checked); + void on_forceLoadLibraries_toggled(bool checked); + void on_useApplicationIcon_toggled(bool checked); + + void on_browseBinary_clicked(); + void on_browseWorkingDirectory_clicked(); + void on_configureLibraries_clicked(); + + void on_buttons_accepted(); + void on_buttons_rejected(); + void delayedRefresh(); - void on_executablesListBox_itemSelectionChanged(); - void on_executablesListBox_clicked(const QModelIndex &index); - void on_forceLoadButton_clicked(); - void on_forceLoadCheckBox_toggled(); private: std::unique_ptr ui; ExecutablesList m_executablesList; Profile *m_profile; const MOBase::IPluginGame *m_gamePlugin; - bool m_dirty; + bool m_settingUI; QListWidgetItem *m_currentItem; QList m_forcedLibraries; + QListWidgetItem* selectedItem(); + Executable* selectedExe(); + void updateUI(const Executable* e); void clearEdits(); void setEdits(const Executable& e); + void save(); void resetInput(); void refreshExecutablesWidget(); bool executableChanged(); void updateButtonStates(); void saveExecutable(); + + void onTitleChanged(const QString& s); }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 2c9a74f3..6cb8e0b8 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -58,7 +58,7 @@ 0 - + List of configured executables @@ -89,7 +89,7 @@ - + Add an executable @@ -106,7 +106,7 @@ - + Remove the selected executable @@ -172,7 +172,7 @@ - + Name of the executable. This is only for display purposes. @@ -191,7 +191,7 @@ - + Binary to run @@ -201,7 +201,7 @@ - + Browse filesystem @@ -225,10 +225,10 @@ - + - + ... @@ -244,7 +244,7 @@ - + Arguments to pass to the application @@ -258,7 +258,7 @@ - + Allow the Steam AppID to be used for this executable to be changed. @@ -273,7 +273,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + false @@ -292,7 +292,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. @@ -302,7 +302,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + false @@ -313,12 +313,12 @@ Right now the only case I know of where this needs to be overwritten is for the - + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - Force Load Libraries (Profile Specific) + Force Load Libraries (*) @@ -336,7 +336,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + false @@ -348,12 +348,19 @@ Right now the only case I know of where this needs to be overwritten is for the - + Use Application's Icon for shortcuts + + + + (*) Profile Specific + + + @@ -393,7 +400,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -402,16 +409,16 @@ Right now the only case I know of where this needs to be overwritten is for the - binaryEdit - browseBinaryButton - workingDirEdit - browseWorkingDirButton - overwriteAppIDBox - appIDOverwriteEdit - newFilesModCheckBox - newFilesModBox - addButton - removeButton + binary + browseBinary + workingDirectory + browseWorkingDirectory + overwriteSteamAppID + steamAppID + createFilesInMod + mods + add + remove -- cgit v1.3.1 From cd2fefca1928f374c302c275efcc0bbaf36357bb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 22:24:43 -0400 Subject: save steam app id, custom overwrite and application icon custom overwrite directories now set locally, will be written to profile when closing the dialog --- src/editexecutablesdialog.cpp | 149 +++++++++++++++++++++++++++--------------- src/editexecutablesdialog.h | 6 +- src/executableslist.cpp | 2 + 3 files changed, 100 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 621c670f..2c376cd1 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -47,16 +47,28 @@ EditExecutablesDialog::EditExecutablesDialog( ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); - refreshExecutablesWidget(); + for (const auto& e : m_executablesList) { + QString customOverwrite = m_profile->setting("custom_overwrites", e.title()).toString(); + + if (!customOverwrite.isEmpty()) { + m_customOverwrites[e.title()] = customOverwrite; + } + } + + + fillExecutableList(); ui->mods->addItems(modList.allMods()); m_forcedLibraries = m_profile->determineForcedLibraries(ui->title->text()); - // title textbox also has to change the list item, will call save manually - connect(ui->title, &QLineEdit::textChanged, [&](auto&& s){ onTitleChanged(s); }); + // some widgets need to do more than just save() and have their own handler + connect(ui->binary, &QLineEdit::textChanged, [&]{ save(); }); connect(ui->workingDirectory, &QLineEdit::textChanged, [&]{ save(); }); connect(ui->arguments, &QLineEdit::textChanged, [&]{ save(); }); + connect(ui->steamAppID, &QLineEdit::textChanged, [&]{ save(); }); + connect(ui->mods, &QComboBox::currentTextChanged, [&]{ save(); }); + connect(ui->useApplicationIcon, &QCheckBox::toggled, [&]{ save(); }); updateUI(nullptr); } @@ -91,8 +103,27 @@ Executable* EditExecutablesDialog::selectedExe() return &*itor; } +void EditExecutablesDialog::fillExecutableList() +{ + ui->list->clear(); + + for(const auto& exe : m_executablesList) { + QListWidgetItem *newItem = new QListWidgetItem(exe.title()); + + if (!exe.isCustom()) { + auto f = newItem->font(); + f.setItalic(true); + + newItem->setFont(f); + } + + ui->list->addItem(newItem); + } +} + void EditExecutablesDialog::updateUI(const Executable* e) { + // the ui is currently being set, ignore changes m_settingUI = true; if (e) { @@ -103,6 +134,7 @@ void EditExecutablesDialog::updateUI(const Executable* e) ui->remove->setEnabled(false); } + // any changes from now on are from the user m_settingUI = false; } @@ -113,10 +145,13 @@ void EditExecutablesDialog::clearEdits() ui->workingDirectory->clear(); ui->arguments->clear(); ui->overwriteSteamAppID->setChecked(false); + ui->steamAppID->setEnabled(false); ui->steamAppID->clear(); ui->createFilesInMod->setChecked(false); + ui->mods->setEnabled(false); ui->mods->setCurrentIndex(-1); ui->forceLoadLibraries->setChecked(false); + ui->configureLibraries->setEnabled(false); ui->useApplicationIcon->setChecked(false); ui->pluginProvidedLabel->setVisible(false); @@ -129,17 +164,25 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->workingDirectory->setText(QDir::toNativeSeparators(e.workingDirectory())); ui->arguments->setText(e.arguments()); ui->overwriteSteamAppID->setChecked(!e.steamAppID().isEmpty()); + ui->steamAppID->setEnabled(!e.steamAppID().isEmpty()); ui->steamAppID->setText(e.steamAppID()); ui->useApplicationIcon->setChecked(e.usesOwnIcon()); int modIndex = -1; - QString customOverwrite = m_profile->setting("custom_overwrites", e.title()).toString(); - if (!customOverwrite.isEmpty()) { - modIndex = ui->mods->findText(customOverwrite); + auto itor = m_customOverwrites.find(e.title()); + if (itor != m_customOverwrites.end()) { + modIndex = ui->mods->findText(itor->second); + + if (modIndex == -1) { + qWarning().nospace() + << "executable '" << e.title() << "' uses mod '" << itor->second << "' " + << "as a custom overwrite, but that mod doesn't exist"; + } } ui->createFilesInMod->setChecked(modIndex != -1); + ui->mods->setEnabled(modIndex != -1); ui->mods->setCurrentIndex(modIndex); const bool forcedLibraries = m_profile->forcedLibrariesEnabled(e.title()); @@ -156,19 +199,16 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->browseWorkingDirectory->setEnabled(e.isCustom()); ui->arguments->setEnabled(e.isCustom()); ui->overwriteSteamAppID->setEnabled(e.isCustom()); - ui->steamAppID->setEnabled(e.isCustom()); ui->useApplicationIcon->setEnabled(e.isCustom()); // always enabled ui->createFilesInMod->setEnabled(true); - ui->mods->setEnabled(true); ui->forceLoadLibraries->setEnabled(true); } void EditExecutablesDialog::save() { if (m_settingUI) { - // the ui is currently being set, ignore changes return; } @@ -180,6 +220,16 @@ void EditExecutablesDialog::save() qDebug().nospace() << "saving '" << e->title() << "'"; + // title may have changed, start with the stuff using it + if (ui->createFilesInMod->isChecked()) { + m_customOverwrites[e->title()] = ui->mods->currentText(); + } else { + auto itor = m_customOverwrites.find(e->title()); + if (itor != m_customOverwrites.end()) { + m_customOverwrites.erase(itor); + } + } + e->title(ui->title->text()); e->binaryInfo(ui->binary->text()); e->workingDirectory(ui->workingDirectory->text()); @@ -192,10 +242,9 @@ void EditExecutablesDialog::save() } } -void EditExecutablesDialog::onTitleChanged(const QString& s) +void EditExecutablesDialog::on_title_textChanged(const QString& s) { if (m_settingUI) { - // the ui is currently being set, ignore changes return; } @@ -210,6 +259,38 @@ void EditExecutablesDialog::onTitleChanged(const QString& s) } } +void EditExecutablesDialog::on_overwriteSteamAppID_toggled(bool checked) +{ + if (m_settingUI) { + return; + } + + ui->steamAppID->setEnabled(checked); + save(); +} + +void EditExecutablesDialog::on_createFilesInMod_toggled(bool checked) +{ + if (m_settingUI) { + return; + } + + ui->mods->setEnabled(checked); + save(); +} + +void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) +{ + if (m_settingUI) { + return; + } + + ui->configureLibraries->setEnabled(ui->forceLoadLibraries->isChecked()); + save(); +} + + + void EditExecutablesDialog::resetInput() { @@ -246,27 +327,6 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const return newList; } -void EditExecutablesDialog::refreshExecutablesWidget() -{ - ui->list->clear(); - - for(const auto& exe : m_executablesList) { - QListWidgetItem *newItem = new QListWidgetItem(exe.title()); - - if (!exe.isCustom()) { - auto f = newItem->font(); - f.setItalic(true); - - newItem->setFont(f); - } - - ui->list->addItem(newItem); - } - - //ui->addButton->setEnabled(false); - //ui->removeButton->setEnabled(false); -} - void EditExecutablesDialog::updateButtonStates() { @@ -336,11 +396,6 @@ void EditExecutablesDialog::on_configureLibraries_clicked() } } -void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) -{ - ui->configureLibraries->setEnabled(ui->forceLoadLibraries->isChecked()); -} - void EditExecutablesDialog::on_add_clicked() { @@ -349,7 +404,7 @@ void EditExecutablesDialog::on_add_clicked() } resetInput(); - refreshExecutablesWidget(); + //refreshExecutablesWidget(); } void EditExecutablesDialog::on_browseBinary_clicked() @@ -427,7 +482,7 @@ void EditExecutablesDialog::on_remove_clicked() } resetInput(); - refreshExecutablesWidget(); + //refreshExecutablesWidget(); } @@ -486,11 +541,6 @@ void EditExecutablesDialog::on_list_itemSelectionChanged() updateUI(selectedExe()); } -void EditExecutablesDialog::on_overwriteSteamAppID_toggled(bool checked) -{ - ui->steamAppID->setEnabled(checked); -} - void EditExecutablesDialog::on_buttons_accepted() { if (executableChanged()) { @@ -503,7 +553,7 @@ void EditExecutablesDialog::on_buttons_accepted() saveExecutable(); // the executable list returned to callers is generated from the user data in the widgets, // NOT the list we just saved - refreshExecutablesWidget(); + //refreshExecutablesWidget(); } } @@ -582,12 +632,3 @@ void EditExecutablesDialog::on_buttons_rejected() ui->forceLoadCheckBox->setChecked(forcedLibraries); } }*/ - -void EditExecutablesDialog::on_createFilesInMod_toggled(bool checked) -{ - ui->mods->setEnabled(checked); -} - -void EditExecutablesDialog::on_useApplicationIcon_toggled(bool checked) -{ -} diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index f28658e3..8ee56e3e 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -64,10 +64,10 @@ private slots: void on_add_clicked(); void on_remove_clicked(); + void on_title_textChanged(const QString& s); void on_overwriteSteamAppID_toggled(bool checked); void on_createFilesInMod_toggled(bool checked); void on_forceLoadLibraries_toggled(bool checked); - void on_useApplicationIcon_toggled(bool checked); void on_browseBinary_clicked(); void on_browseWorkingDirectory_clicked(); @@ -81,6 +81,7 @@ private slots: private: std::unique_ptr ui; ExecutablesList m_executablesList; + std::map m_customOverwrites; Profile *m_profile; const MOBase::IPluginGame *m_gamePlugin; bool m_settingUI; @@ -92,18 +93,17 @@ private: QListWidgetItem* selectedItem(); Executable* selectedExe(); + void fillExecutableList(); void updateUI(const Executable* e); void clearEdits(); void setEdits(const Executable& e); void save(); void resetInput(); - void refreshExecutablesWidget(); bool executableChanged(); void updateButtonStates(); void saveExecutable(); - void onTitleChanged(const QString& s); }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 8174eb1b..f592a2b7 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -67,6 +67,8 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) { qDebug("setting up configured executables"); + m_Executables.clear(); + int numCustomExecutables = settings.beginReadArray("customExecutables"); for (int i = 0; i < numCustomExecutables; ++i) { settings.setArrayIndex(i); -- cgit v1.3.1 From f22b64bff76e713d761832584307cffece18114d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 22:39:57 -0400 Subject: save forced libraries internally --- src/editexecutablesdialog.cpp | 84 ++++++++++++++++++++++++++++--------------- src/editexecutablesdialog.h | 2 +- 2 files changed, 56 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 2c376cd1..5fbb30dd 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -48,18 +48,22 @@ EditExecutablesDialog::EditExecutablesDialog( ui->splitter->setStretchFactor(1, 1); for (const auto& e : m_executablesList) { + // custom overwrites QString customOverwrite = m_profile->setting("custom_overwrites", e.title()).toString(); - if (!customOverwrite.isEmpty()) { m_customOverwrites[e.title()] = customOverwrite; } + + // forced libraries + if (m_profile->forcedLibrariesEnabled(e.title())) { + m_forcedLibraries[e.title()] = m_profile->determineForcedLibraries(e.title()); + } } fillExecutableList(); ui->mods->addItems(modList.allMods()); - m_forcedLibraries = m_profile->determineForcedLibraries(ui->title->text()); // some widgets need to do more than just save() and have their own handler @@ -168,26 +172,32 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->steamAppID->setText(e.steamAppID()); ui->useApplicationIcon->setChecked(e.usesOwnIcon()); - int modIndex = -1; + { + int modIndex = -1; - auto itor = m_customOverwrites.find(e.title()); - if (itor != m_customOverwrites.end()) { - modIndex = ui->mods->findText(itor->second); + auto itor = m_customOverwrites.find(e.title()); + if (itor != m_customOverwrites.end()) { + modIndex = ui->mods->findText(itor->second); - if (modIndex == -1) { - qWarning().nospace() - << "executable '" << e.title() << "' uses mod '" << itor->second << "' " - << "as a custom overwrite, but that mod doesn't exist"; + if (modIndex == -1) { + qWarning().nospace() + << "executable '" << e.title() << "' uses mod '" << itor->second << "' " + << "as a custom overwrite, but that mod doesn't exist"; + } } + + ui->createFilesInMod->setChecked(modIndex != -1); + ui->mods->setEnabled(modIndex != -1); + ui->mods->setCurrentIndex(modIndex); } - ui->createFilesInMod->setChecked(modIndex != -1); - ui->mods->setEnabled(modIndex != -1); - ui->mods->setCurrentIndex(modIndex); + { + auto itor = m_forcedLibraries.find(e.title()); + const auto hasForcedLibraries = (itor != m_forcedLibraries.end()); - const bool forcedLibraries = m_profile->forcedLibrariesEnabled(e.title()); - ui->forceLoadLibraries->setChecked(forcedLibraries); - ui->configureLibraries->setEnabled(forcedLibraries); + ui->forceLoadLibraries->setChecked(hasForcedLibraries); + ui->configureLibraries->setEnabled(hasForcedLibraries); + } ui->pluginProvidedLabel->setVisible(!e.isCustom()); @@ -230,6 +240,8 @@ void EditExecutablesDialog::save() } } + // forced libraries are saved in on_configureLibraries_clicked() + e->title(ui->title->text()); e->binaryInfo(ui->binary->text()); e->workingDirectory(ui->workingDirectory->text()); @@ -289,6 +301,26 @@ void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) save(); } +void EditExecutablesDialog::on_configureLibraries_clicked() +{ + auto* e = selectedExe(); + if (!e) { + qWarning("trying to configure libraries but nothing is selected"); + return; + } + + ForcedLoadDialog dialog(m_gamePlugin, this); + + auto itor = m_forcedLibraries.find(e->title()); + if (itor != m_forcedLibraries.end()) { + dialog.setValues(itor->second); + } + + if (dialog.exec() == QDialog::Accepted) { + m_forcedLibraries[e->title()] = dialog.values(); + save(); + } +} @@ -372,9 +404,9 @@ void EditExecutablesDialog::saveExecutable() m_profile->removeSetting("custom_overwrites", ui->title->text()); } - m_profile->removeForcedLibraries(ui->title->text()); - m_profile->storeForcedLibraries(ui->title->text(), m_forcedLibraries); - m_profile->setForcedLibrariesEnabled(ui->title->text(), ui->forceLoadLibraries->isChecked()); + //m_profile->removeForcedLibraries(ui->title->text()); + //m_profile->storeForcedLibraries(ui->title->text(), m_forcedLibraries); + //m_profile->setForcedLibrariesEnabled(ui->title->text(), ui->forceLoadLibraries->isChecked()); } @@ -387,14 +419,6 @@ void EditExecutablesDialog::delayedRefresh() } -void EditExecutablesDialog::on_configureLibraries_clicked() -{ - ForcedLoadDialog dialog(m_gamePlugin, this); - dialog.setValues(m_forcedLibraries); - if (dialog.exec() == QDialog::Accepted) { - m_forcedLibraries = dialog.values(); - } -} void EditExecutablesDialog::on_add_clicked() @@ -488,7 +512,7 @@ void EditExecutablesDialog::on_remove_clicked() bool EditExecutablesDialog::executableChanged() { - if (m_currentItem != nullptr) { + /*if (m_currentItem != nullptr) { const auto& title = m_currentItem->text(); auto itor = m_executablesList.find(title); @@ -533,7 +557,9 @@ bool EditExecutablesDialog::executableChanged() && !ui->title->text().isEmpty() && fileInfo.exists() && fileInfo.isFile(); - } + }*/ + + return false; } void EditExecutablesDialog::on_list_itemSelectionChanged() diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 8ee56e3e..37b50127 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -82,12 +82,12 @@ private: std::unique_ptr ui; ExecutablesList m_executablesList; std::map m_customOverwrites; + std::map> m_forcedLibraries; Profile *m_profile; const MOBase::IPluginGame *m_gamePlugin; bool m_settingUI; QListWidgetItem *m_currentItem; - QList m_forcedLibraries; QListWidgetItem* selectedItem(); -- cgit v1.3.1 From 7026aeaec859fd3632b1062e0cf14d2479c23076 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 23:00:08 -0400 Subject: fixed FileDialogMemory::getOpenFileName() not using given directory disable all widgets when there's no selection now saves application icon browse working directory uses current value in file dialog --- src/editexecutablesdialog.cpp | 54 ++++++++++++++++++++++++++++--------------- src/filedialogmemory.cpp | 28 ++++++++++++++-------- src/filedialogmemory.h | 23 ++++++++---------- 3 files changed, 63 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 5fbb30dd..efcec8e0 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -145,19 +145,28 @@ void EditExecutablesDialog::updateUI(const Executable* e) void EditExecutablesDialog::clearEdits() { ui->title->clear(); + ui->title->setEnabled(false); ui->binary->clear(); + ui->binary->setEnabled(false); + ui->browseBinary->setEnabled(false); ui->workingDirectory->clear(); + ui->workingDirectory->setEnabled(false); + ui->browseWorkingDirectory->setEnabled(false); ui->arguments->clear(); + ui->arguments->setEnabled(false); + ui->overwriteSteamAppID->setEnabled(false); ui->overwriteSteamAppID->setChecked(false); ui->steamAppID->setEnabled(false); ui->steamAppID->clear(); + ui->createFilesInMod->setEnabled(false); ui->createFilesInMod->setChecked(false); ui->mods->setEnabled(false); ui->mods->setCurrentIndex(-1); + ui->forceLoadLibraries->setEnabled(false); ui->forceLoadLibraries->setChecked(false); ui->configureLibraries->setEnabled(false); + ui->useApplicationIcon->setEnabled(false); ui->useApplicationIcon->setChecked(false); - ui->pluginProvidedLabel->setVisible(false); } @@ -252,6 +261,17 @@ void EditExecutablesDialog::save() } else { e->steamAppID(""); } + + if (ui->useApplicationIcon->isChecked()) { + e->flags(e->flags() | Executable::UseApplicationIcon); + } else { + e->flags(e->flags() & (~Executable::UseApplicationIcon)); + } +} + +void EditExecutablesDialog::on_list_itemSelectionChanged() +{ + updateUI(selectedExe()); } void EditExecutablesDialog::on_title_textChanged(const QString& s) @@ -301,6 +321,20 @@ void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) save(); } +void EditExecutablesDialog::on_browseWorkingDirectory_clicked() +{ + QString dirName = FileDialogMemory::getExistingDirectory( + "editExecutableDirectory", this, tr("Select a directory"), + ui->workingDirectory->text()); + + if (dirName.isNull()) { + // canceled + return; + } + + ui->workingDirectory->setText(dirName); +} + void EditExecutablesDialog::on_configureLibraries_clicked() { auto* e = selectedExe(); @@ -483,19 +517,6 @@ void EditExecutablesDialog::on_browseBinary_clicked() } } -void EditExecutablesDialog::on_browseWorkingDirectory_clicked() -{ - QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, - tr("Select a directory")); - - if (dirName.isNull()) { - // canceled - return; - } - - ui->workingDirectory->setText(dirName); -} - void EditExecutablesDialog::on_remove_clicked() { if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->title->text()), @@ -562,11 +583,6 @@ bool EditExecutablesDialog::executableChanged() return false; } -void EditExecutablesDialog::on_list_itemSelectionChanged() -{ - updateUI(selectedExe()); -} - void EditExecutablesDialog::on_buttons_accepted() { if (executableChanged()) { diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 6b440d0f..9607beb9 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -56,10 +56,10 @@ void FileDialogMemory::restore(QSettings &settings) } -QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent, - const QString &caption, const QString &dir, - const QString &filter, QString *selectedFilter, - QFileDialog::Options options) +QString FileDialogMemory::getOpenFileName( + const QString &dirID, QWidget *parent, const QString &caption, + const QString &dir, const QString &filter, QString *selectedFilter, + QFileDialog::Options options) { std::pair::iterator, bool> currentDir = instance().m_Cache.insert(std::make_pair(dirID, dir)); @@ -73,16 +73,26 @@ QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent, } -QString FileDialogMemory::getExistingDirectory(const QString &dirID, QWidget *parent, - const QString &caption, const QString &dir, QFileDialog::Options options) +QString FileDialogMemory::getExistingDirectory( + const QString &dirID, QWidget *parent, const QString &caption, + const QString &dir, QFileDialog::Options options) { - std::pair::iterator, bool> currentDir = - instance().m_Cache.insert(std::make_pair(dirID, dir)); + QString currentDir = dir; + + if (currentDir.isEmpty()) { + auto itor = instance().m_Cache.find(dirID); + if (itor != instance().m_Cache.end()) { + currentDir = itor->first; + } + } + + QString result = QFileDialog::getExistingDirectory( + parent, caption, currentDir, options); - QString result = QFileDialog::getExistingDirectory(parent, caption, currentDir.first->second, options); if (!result.isNull()) { instance().m_Cache[dirID] = QFileInfo(result).path(); } + return result; } diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index b2bfdb53..81d7ba40 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -29,30 +29,25 @@ along with Mod Organizer. If not, see . class FileDialogMemory { - public: - static void save(QSettings &settings); static void restore(QSettings &settings); - static QString getOpenFileName(const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), - const QString &dir = QString(), const QString &filter = QString(), - QString *selectedFilter = 0, QFileDialog::Options options = 0); + static QString getOpenFileName( + const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), + const QString &dir = QString(), const QString &filter = QString(), + QString *selectedFilter = 0, QFileDialog::Options options = 0); - static QString getExistingDirectory(const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), - const QString &dir = QString(), - QFileDialog::Options options = QFileDialog::ShowDirsOnly); + static QString getExistingDirectory( + const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), + const QString &dir = QString(), + QFileDialog::Options options = QFileDialog::ShowDirsOnly); private: + std::map m_Cache; FileDialogMemory(); - static FileDialogMemory &instance(); - -private: - - std::map m_Cache; - }; #endif // FILEDIALOGMEMORY_H -- cgit v1.3.1 From 1897d60134e1cff31375f1c602196a99ece7fc86 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 23:31:58 -0400 Subject: pulled java installation detection out of getFileExecutionContext() and into findJavaInstallation() because it was copy/pasted into EditExecutablesDialog fixed FileDialogMemory::getOpenFileName() to also use the given directory correctly handle browse binary button --- src/editexecutablesdialog.cpp | 104 +++++++++++++++++++++--------------------- src/editexecutablesdialog.h | 1 + src/filedialogmemory.cpp | 16 +++++-- src/organizercore.cpp | 72 +++++++++++++++-------------- src/organizercore.h | 2 + 5 files changed, 105 insertions(+), 90 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index efcec8e0..08e2c3d1 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -22,10 +22,12 @@ along with Mod Organizer. If not, see . #include "filedialogmemory.h" #include "stackdata.h" #include "modlist.h" +#include "forcedloaddialog.h" +#include "organizercore.h" + #include #include #include -#include "forcedloaddialog.h" #include using namespace MOBase; @@ -321,6 +323,30 @@ void EditExecutablesDialog::on_forceLoadLibraries_toggled(bool checked) save(); } +void EditExecutablesDialog::on_browseBinary_clicked() +{ + const QString binaryName = FileDialogMemory::getOpenFileName( + "editExecutableBinary", this, tr("Select a binary"), ui->binary->text(), + tr("Executable (%1)").arg("*.exe *.bat *.jar")); + + if (binaryName.isNull()) { + // canceled + return; + } + + if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { + setJarBinary(binaryName); + } else { + ui->binary->setText(QDir::toNativeSeparators(binaryName)); + } + + if (ui->title->text().isEmpty()) { + ui->title->setText(QFileInfo(binaryName).baseName()); + } + + save(); +} + void EditExecutablesDialog::on_browseWorkingDirectory_clicked() { QString dirName = FileDialogMemory::getExistingDirectory( @@ -356,6 +382,30 @@ void EditExecutablesDialog::on_configureLibraries_clicked() } } +void EditExecutablesDialog::setJarBinary(const QString& binaryName) +{ + auto java = OrganizerCore::findJavaInstallation(binaryName); + + if (java.isEmpty()) { + QMessageBox::information( + this, tr("Java (32-bit) required"), + tr("MO requires 32-bit java to run this application. If you already " + "have it installed, select javaw.exe from that installation as " + "the binary.")); + } + + // only save once + + m_settingUI = true; + ui->binary->setText(java); + ui->workingDirectory->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); + ui->arguments->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); + m_settingUI = false; + + save(); +} + + void EditExecutablesDialog::resetInput() @@ -465,58 +515,6 @@ void EditExecutablesDialog::on_add_clicked() //refreshExecutablesWidget(); } -void EditExecutablesDialog::on_browseBinary_clicked() -{ - QString binaryName = FileDialogMemory::getOpenFileName( - "editExecutableBinary", this, tr("Select a binary"), QString(), - tr("Executable (%1)").arg("*.exe *.bat *.jar")); - - if (binaryName.isNull()) { - // canceled - return; - } - - if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { - QString binaryPath; - { // try to find java automatically - std::wstring binaryNameW = ToWString(binaryName); - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(binaryNameW.c_str(), nullptr, buffer) - > reinterpret_cast(32)) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty()) { - QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); - if (javaReg.contains("CurrentVersion")) { - QString currentVersion = javaReg.value("CurrentVersion").toString(); - binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - } - if (binaryPath.isEmpty()) { - QMessageBox::information(this, tr("Java (32-bit) required"), - tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe " - "from that installation as the binary.")); - } else { - ui->binary->setText(binaryPath); - } - - ui->workingDirectory->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); - ui->arguments->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); - } else { - ui->binary->setText(QDir::toNativeSeparators(binaryName)); - } - - if (ui->title->text().isEmpty()) { - ui->title->setText(QFileInfo(binaryName).baseName()); - } -} - void EditExecutablesDialog::on_remove_clicked() { if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->title->text()), diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 37b50127..1f3f0082 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -98,6 +98,7 @@ private: void clearEdits(); void setEdits(const Executable& e); void save(); + void setJarBinary(const QString& binaryName); void resetInput(); bool executableChanged(); diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 9607beb9..554a6235 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -61,14 +61,22 @@ QString FileDialogMemory::getOpenFileName( const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { - std::pair::iterator, bool> currentDir = - instance().m_Cache.insert(std::make_pair(dirID, dir)); + QString currentDir = dir; + + if (currentDir.isEmpty()) { + auto itor = instance().m_Cache.find(dirID); + if (itor != instance().m_Cache.end()) { + currentDir = itor->first; + } + } + + QString result = QFileDialog::getOpenFileName( + parent, caption, currentDir, filter, selectedFilter, options); - QString result = QFileDialog::getOpenFileName(parent, caption, currentDir.first->second, - filter, selectedFilter, options); if (!result.isNull()) { instance().m_Cache[dirID] = QFileInfo(result).path(); } + return result; } diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2172538e..c724e57f 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1182,6 +1182,34 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res; } +QString OrganizerCore::findJavaInstallation(const QString& jarFile) +{ + if (!jarFile.isEmpty()) { + // try to find java automatically based on the given jar file + std::wstring jarFileW = jarFile.toStdWString(); + + WCHAR buffer[MAX_PATH]; + if (::FindExecutableW(jarFileW.c_str(), nullptr, buffer) > (HINSTANCE)32) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(buffer, &binaryType)) { + qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); + } else if (binaryType == SCS_32BIT_BINARY || binaryType == SCS_64BIT_BINARY) { + return QString::fromWCharArray(buffer); + } + } + } + + // second attempt: look to the registry + QSettings reg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); + if (reg.contains("CurrentVersion")) { + QString currentVersion = reg.value("CurrentVersion").toString(); + return reg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); + } + + // not found + return {}; +} + bool OrganizerCore::getFileExecutionContext( QWidget* parent, const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type) @@ -1199,44 +1227,22 @@ bool OrganizerCore::getFileExecutionContext( type = FileExecutionTypes::Executable; return true; } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - // types that need to be injected into - std::wstring targetPathW = targetInfo.absoluteFilePath().toStdWString(); - QString binaryPath; - - { // try to find java automatically - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(buffer, &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = QString::fromWCharArray(buffer); - } - } - } - if (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)"); + auto java = findJavaInstallation(targetInfo.absoluteFilePath()); + + if (java.isEmpty()) { + java = QFileDialog::getOpenFileName( + parent, QObject::tr("Select binary"), + QString(), QObject::tr("Binary") + " (*.exe)"); } - if (binaryPath.isEmpty()) { + + if (java.isEmpty()) { return false; } - binaryInfo = QFileInfo(binaryPath); - if (extension == "jar") { - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } else { - arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } + binaryInfo = QFileInfo(java); + arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); type = FileExecutionTypes::Executable; + return true; } else { type = FileExecutionTypes::Other; diff --git a/src/organizercore.h b/src/organizercore.h index 8ed34e24..a4a57496 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -147,6 +147,8 @@ public: void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } + static QString findJavaInstallation(const QString& jarFile={}); + static bool getFileExecutionContext( QWidget* parent, const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments, FileExecutionTypes& type); -- cgit v1.3.1 From 6e937f7d09da6ad773874fc6095e2fb5b51d9afc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 23:44:58 -0400 Subject: add executable --- src/editexecutablesdialog.cpp | 74 +++++++++++++++++++++++++------------------ src/editexecutablesdialog.h | 3 +- 2 files changed, 46 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 08e2c3d1..570d1396 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -114,17 +114,21 @@ void EditExecutablesDialog::fillExecutableList() ui->list->clear(); for(const auto& exe : m_executablesList) { - QListWidgetItem *newItem = new QListWidgetItem(exe.title()); - - if (!exe.isCustom()) { - auto f = newItem->font(); - f.setItalic(true); + ui->list->addItem(createListItem(exe)); + } +} - newItem->setFont(f); - } +QListWidgetItem* EditExecutablesDialog::createListItem(const Executable& exe) +{ + QListWidgetItem *newItem = new QListWidgetItem(exe.title()); - ui->list->addItem(newItem); + if (!exe.isCustom()) { + auto f = newItem->font(); + f.setItalic(true); + newItem->setFont(f); } + + return newItem; } void EditExecutablesDialog::updateUI(const Executable* e) @@ -276,6 +280,24 @@ void EditExecutablesDialog::on_list_itemSelectionChanged() updateUI(selectedExe()); } +void EditExecutablesDialog::on_add_clicked() +{ + auto title = newExecutableTitle(); + if (title.isNull()) { + return; + } + + auto e = Executable() + .title(title) + .flags(Executable::CustomExecutable); + + m_executablesList.setExecutable(e); + + auto* item = createListItem(e); + ui->list->addItem(item); + item->setSelected(true); +} + void EditExecutablesDialog::on_title_textChanged(const QString& s) { if (m_settingUI) { @@ -405,24 +427,26 @@ void EditExecutablesDialog::setJarBinary(const QString& binaryName) save(); } +QString EditExecutablesDialog::newExecutableTitle() +{ + const auto prefix = tr("New Executable"); + QString title = prefix; + for (int i=1; i<100; ++i) { + if (!m_executablesList.titleExists(title)) { + return title; + } -void EditExecutablesDialog::resetInput() -{ - ui->binary->setText(""); - ui->title->setText(""); - ui->workingDirectory->clear(); - ui->arguments->setText(""); - ui->overwriteSteamAppID->setChecked(false); - ui->createFilesInMod->setChecked(false); - ui->forceLoadLibraries->setChecked(false); - ui->steamAppID->clear(); - ui->useApplicationIcon->setChecked(false); + title = prefix + QString(" (%1)").arg(i); + } - m_currentItem = nullptr; + qCritical().nospace() << "ran out of new executable titles"; + return QString::null; } + + ExecutablesList EditExecutablesDialog::getExecutablesList() const { ExecutablesList newList; @@ -505,15 +529,6 @@ void EditExecutablesDialog::delayedRefresh() -void EditExecutablesDialog::on_add_clicked() -{ - if (executableChanged()) { - saveExecutable(); - } - - resetInput(); - //refreshExecutablesWidget(); -} void EditExecutablesDialog::on_remove_clicked() { @@ -524,7 +539,6 @@ void EditExecutablesDialog::on_remove_clicked() m_executablesList.remove(ui->title->text()); } - resetInput(); //refreshExecutablesWidget(); } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 1f3f0082..3145c94f 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -94,13 +94,14 @@ private: Executable* selectedExe(); void fillExecutableList(); + QListWidgetItem* createListItem(const Executable& exe); void updateUI(const Executable* e); void clearEdits(); void setEdits(const Executable& e); void save(); void setJarBinary(const QString& binaryName); + QString newExecutableTitle(); - void resetInput(); bool executableChanged(); void updateButtonStates(); void saveExecutable(); -- cgit v1.3.1 From f60431f92f9475d59a3e2af41c95cc6723892f9a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 7 Jun 2019 23:57:52 -0400 Subject: remove executable --- src/editexecutablesdialog.cpp | 61 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 570d1396..15b496ef 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -298,6 +298,56 @@ void EditExecutablesDialog::on_add_clicked() item->setSelected(true); } +void EditExecutablesDialog::on_remove_clicked() +{ + auto* item = selectedItem(); + if (!item) { + qWarning("trying to remove entry but nothing is selected"); + return; + } + + auto* exe = selectedExe(); + if (!exe) { + qWarning("trying to remove entry but nothing is selected"); + return; + } + + const int currentRow = ui->list->row(item); + delete item; + + + // removing custom overwrite + { + auto itor = m_customOverwrites.find(exe->title()); + if (itor != m_customOverwrites.end()) { + m_customOverwrites.erase(itor); + } + } + + // removing forced libraries + { + auto itor = m_forcedLibraries.find(exe->title()); + if (itor != m_forcedLibraries.end()) { + m_forcedLibraries.erase(itor); + } + } + + // removing from main list, must be done last because it invalidates the + // exe pointer + m_executablesList.remove(exe->title()); + + + // reselecting the same row as before, or the last one + if (currentRow >= ui->list->count()) { + // that was the last item, select the new list item, if any + if (ui->list->count() > 0) { + ui->list->item(ui->list->count() - 1)->setSelected(true); + } + } else { + ui->list->item(currentRow)->setSelected(true); + } +} + void EditExecutablesDialog::on_title_textChanged(const QString& s) { if (m_settingUI) { @@ -530,17 +580,6 @@ void EditExecutablesDialog::delayedRefresh() -void EditExecutablesDialog::on_remove_clicked() -{ - if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->title->text()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_profile->removeSetting("custom_overwrites", ui->title->text()); - m_profile->removeForcedLibraries(ui->title->text()); - m_executablesList.remove(ui->title->text()); - } - - //refreshExecutablesWidget(); -} bool EditExecutablesDialog::executableChanged() -- cgit v1.3.1 From 57c7b1568518acefcbcc1801954a62fdc94e3b1c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 8 Jun 2019 00:30:51 -0400 Subject: moved functionality to CustomOverwrites and ForcedLibraries helper classes --- src/editexecutablesdialog.cpp | 163 ++++++++++++++++++++++++++++++------------ src/editexecutablesdialog.h | 43 ++++++++++- 2 files changed, 157 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 15b496ef..6f63416d 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -49,26 +49,13 @@ EditExecutablesDialog::EditExecutablesDialog( ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); - for (const auto& e : m_executablesList) { - // custom overwrites - QString customOverwrite = m_profile->setting("custom_overwrites", e.title()).toString(); - if (!customOverwrite.isEmpty()) { - m_customOverwrites[e.title()] = customOverwrite; - } - - // forced libraries - if (m_profile->forcedLibrariesEnabled(e.title())) { - m_forcedLibraries[e.title()] = m_profile->determineForcedLibraries(e.title()); - } - } - + m_customOverwrites.load(profile, m_executablesList); + m_forcedLibraries.load(profile, m_executablesList); fillExecutableList(); ui->mods->addItems(modList.allMods()); - // some widgets need to do more than just save() and have their own handler - connect(ui->binary, &QLineEdit::textChanged, [&]{ save(); }); connect(ui->workingDirectory, &QLineEdit::textChanged, [&]{ save(); }); connect(ui->arguments, &QLineEdit::textChanged, [&]{ save(); }); @@ -190,13 +177,12 @@ void EditExecutablesDialog::setEdits(const Executable& e) { int modIndex = -1; - auto itor = m_customOverwrites.find(e.title()); - if (itor != m_customOverwrites.end()) { - modIndex = ui->mods->findText(itor->second); + if (const auto mod=m_customOverwrites.find(e.title())) { + modIndex = ui->mods->findText(*mod); if (modIndex == -1) { qWarning().nospace() - << "executable '" << e.title() << "' uses mod '" << itor->second << "' " + << "executable '" << e.title() << "' uses mod '" << *mod << "' " << "as a custom overwrite, but that mod doesn't exist"; } } @@ -207,8 +193,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) } { - auto itor = m_forcedLibraries.find(e.title()); - const auto hasForcedLibraries = (itor != m_forcedLibraries.end()); + const auto hasForcedLibraries = m_forcedLibraries.find(e.title()).has_value(); ui->forceLoadLibraries->setChecked(hasForcedLibraries); ui->configureLibraries->setEnabled(hasForcedLibraries); @@ -247,16 +232,20 @@ void EditExecutablesDialog::save() // title may have changed, start with the stuff using it if (ui->createFilesInMod->isChecked()) { - m_customOverwrites[e->title()] = ui->mods->currentText(); + m_customOverwrites.set(e->title(), ui->mods->currentText()); } else { - auto itor = m_customOverwrites.find(e->title()); - if (itor != m_customOverwrites.end()) { - m_customOverwrites.erase(itor); - } + m_customOverwrites.remove(e->title()); } // forced libraries are saved in on_configureLibraries_clicked() + // now rename both the custom overwrites and forced libraries if the title + // is being changed + if (e->title() != ui->title->text()) { + m_customOverwrites.rename(e->title(), ui->title->text()); + m_forcedLibraries.rename(e->title(), ui->title->text()); + } + e->title(ui->title->text()); e->binaryInfo(ui->binary->text()); e->workingDirectory(ui->workingDirectory->text()); @@ -316,21 +305,8 @@ void EditExecutablesDialog::on_remove_clicked() delete item; - // removing custom overwrite - { - auto itor = m_customOverwrites.find(exe->title()); - if (itor != m_customOverwrites.end()) { - m_customOverwrites.erase(itor); - } - } - - // removing forced libraries - { - auto itor = m_forcedLibraries.find(exe->title()); - if (itor != m_forcedLibraries.end()) { - m_forcedLibraries.erase(itor); - } - } + m_customOverwrites.remove(exe->title()); + m_forcedLibraries.remove(exe->title()); // removing from main list, must be done last because it invalidates the // exe pointer @@ -354,8 +330,8 @@ void EditExecutablesDialog::on_title_textChanged(const QString& s) return; } - // must save first because it relies on the text in the list to find the - // executable to modify + // must save before modifying the item in the list widget because saving + // relies on the item's text being the same as an item in m_executablesList save(); // once the executable is saved, the list item must be changed to match the @@ -443,13 +419,12 @@ void EditExecutablesDialog::on_configureLibraries_clicked() ForcedLoadDialog dialog(m_gamePlugin, this); - auto itor = m_forcedLibraries.find(e->title()); - if (itor != m_forcedLibraries.end()) { - dialog.setValues(itor->second); + if (auto list=m_forcedLibraries.find(e->title())) { + dialog.setValues(*list); } if (dialog.exec() == QDialog::Accepted) { - m_forcedLibraries[e->title()] = dialog.values(); + m_forcedLibraries.set(e->title(), dialog.values()); save(); } } @@ -725,3 +700,97 @@ void EditExecutablesDialog::on_buttons_rejected() ui->forceLoadCheckBox->setChecked(forcedLibraries); } }*/ + + +void CustomOverwrites::load(Profile* p, const ExecutablesList& exes) +{ + for (const auto& e : exes) { + const auto s = p->setting("custom_overwrites", e.title()).toString(); + + if (!s.isEmpty()) { + m_map[e.title()] = s; + } + } +} + +std::optional CustomOverwrites::find(const QString& title) const +{ + auto itor = m_map.find(title); + if (itor == m_map.end()) { + return {}; + } + + return itor->second; +} + +void CustomOverwrites::set(const QString& title, const QString& mod) +{ + m_map[title] = mod; +} + +void CustomOverwrites::rename(const QString& oldTitle, const QString& newTitle) +{ + auto itor = m_map.find(oldTitle); + if (itor == m_map.end()) { + return; + } + + // copy to new title, erase old + m_map[newTitle] = itor->second; + m_map.erase(itor); +} + +void CustomOverwrites::remove(const QString& title) +{ + auto itor = m_map.find(title); + + if (itor != m_map.end()) { + m_map.erase(itor); + } +} + + +void ForcedLibraries::load(Profile* p, const ExecutablesList& exes) +{ + for (const auto& e : exes) { + if (p->forcedLibrariesEnabled(e.title())) { + m_map[e.title()] = p->determineForcedLibraries(e.title()); + } + } +} + +std::optional ForcedLibraries::find(const QString& title) const +{ + auto itor = m_map.find(title); + if (itor == m_map.end()) { + return {}; + } + + return itor->second; +} + +void ForcedLibraries::set(const QString& title, const list_type& mod) +{ + m_map[title] = mod; +} + +void ForcedLibraries::rename(const QString& oldTitle, const QString& newTitle) +{ + auto itor = m_map.find(oldTitle); + if (itor == m_map.end()) { + return; + } + + // copy to new title, erase old + m_map[newTitle] = itor->second; + m_map.erase(itor); +} + +void ForcedLibraries::remove(const QString& title) +{ + auto itor = m_map.find(title); + + if (itor != m_map.end()) { + m_map.erase(itor); + } +} diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 3145c94f..f8382915 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" #include #include +#include namespace Ui { class EditExecutablesDialog; @@ -34,6 +35,44 @@ namespace Ui { class ModList; + +/** helper class to manage custom overwrites within the edit executables + * dialog + **/ +class CustomOverwrites +{ +public: + void load(Profile* p, const ExecutablesList& exes); + std::optional find(const QString& title) const; + + void set(const QString& title, const QString& mod); + void rename(const QString& oldTitle, const QString& newTitle); + void remove(const QString& title); + +private: + std::map m_map; +}; + + +/** helper class to manage forced libraries within the edit executables dialog + **/ +class ForcedLibraries +{ +public: + using list_type = QList; + + void load(Profile* p, const ExecutablesList& exes); + std::optional find(const QString& title) const; + + void set(const QString& title, const list_type& list); + void rename(const QString& oldTitle, const QString& newTitle); + void remove(const QString& title); + +private: + std::map m_map; +}; + + /** * @brief Dialog to manage the list of executables **/ @@ -81,8 +120,8 @@ private slots: private: std::unique_ptr ui; ExecutablesList m_executablesList; - std::map m_customOverwrites; - std::map> m_forcedLibraries; + CustomOverwrites m_customOverwrites; + ForcedLibraries m_forcedLibraries; Profile *m_profile; const MOBase::IPluginGame *m_gamePlugin; bool m_settingUI; -- cgit v1.3.1 From 626045511160b9d44a4ffd29f6dce11f5db6096b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 10:37:08 -0400 Subject: removed old, unused stuff have mainwindow save all the new settings once the dialog closes --- src/editexecutablesdialog.cpp | 281 ++++++++---------------------------------- src/editexecutablesdialog.h | 13 +- src/mainwindow.cpp | 38 +++++- 3 files changed, 86 insertions(+), 246 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 6f63416d..28a6b148 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -63,11 +63,49 @@ EditExecutablesDialog::EditExecutablesDialog( connect(ui->mods, &QComboBox::currentTextChanged, [&]{ save(); }); connect(ui->useApplicationIcon, &QCheckBox::toggled, [&]{ save(); }); - updateUI(nullptr); + // select the first one in the list, if any + if (ui->list->count() > 0) { + ui->list->item(0)->setSelected(true); + } else { + updateUI(nullptr); + } } EditExecutablesDialog::~EditExecutablesDialog() = default; +ExecutablesList EditExecutablesDialog::getExecutablesList() const +{ + ExecutablesList newList; + + // make sure the executables are in the same order as in the list + for (int i = 0; i < ui->list->count(); ++i) { + const auto& title = ui->list->item(i)->text(); + + auto itor = m_executablesList.find(title); + + if (itor == m_executablesList.end()) { + qWarning().nospace() + << "getExecutablesList(): executable '" << title << "' not found"; + + continue; + } + + newList.setExecutable(*itor); + } + + return newList; +} + +const CustomOverwrites& EditExecutablesDialog::getCustomOverwrites() const +{ + return m_customOverwrites; +} + +const ForcedLibraries& EditExecutablesDialog::getForcedLibraries() const +{ + return m_forcedLibraries; +} + QListWidgetItem* EditExecutablesDialog::selectedItem() { const auto selection = ui->list->selectedItems(); @@ -429,6 +467,16 @@ void EditExecutablesDialog::on_configureLibraries_clicked() } } +void EditExecutablesDialog::on_buttons_accepted() +{ + accept(); +} + +void EditExecutablesDialog::on_buttons_rejected() +{ + reject(); +} + void EditExecutablesDialog::setJarBinary(const QString& binaryName) { auto java = OrganizerCore::findJavaInstallation(binaryName); @@ -471,237 +519,6 @@ QString EditExecutablesDialog::newExecutableTitle() } - -ExecutablesList EditExecutablesDialog::getExecutablesList() const -{ - ExecutablesList newList; - for (int i = 0; i < ui->list->count(); ++i) { - const auto& title = ui->list->item(i)->text(); - auto itor = m_executablesList.find(title); - - if (itor == m_executablesList.end()) { - qWarning().nospace() - << "getExecutablesList(): executable '" << title << "' not found"; - - continue; - } - - newList.setExecutable(*itor); - } - - return newList; -} - - -void EditExecutablesDialog::updateButtonStates() -{ - bool enabled = true; - - QString filePath(ui->binary->text()); - QFileInfo fileInfo(filePath); - if (!fileInfo.exists()) - enabled = false; - if (!fileInfo.isFile()) - enabled = false; - - QString dirPath(ui->workingDirectory->text()); - if (!dirPath.isEmpty()) { - QDir dirInfo(dirPath); - if (!dirInfo.exists()) - enabled = false; - } - - //ui->addButton->setEnabled(enabled); -} - - -void EditExecutablesDialog::saveExecutable() -{ - Executable::Flags flags = Executable::CustomExecutable; - if (ui->useApplicationIcon->isChecked()) - flags |= Executable::UseApplicationIcon; - - m_executablesList.setExecutable(Executable() - .title(ui->title->text()) - .binaryInfo(QDir::fromNativeSeparators(ui->binary->text())) - .arguments(ui->arguments->text()) - .steamAppID(ui->overwriteSteamAppID->isChecked() ? ui->steamAppID->text() : "") - .workingDirectory(QDir::fromNativeSeparators(ui->workingDirectory->text())) - .flags(flags)); - - if (ui->createFilesInMod->isChecked()) { - m_profile->storeSetting("custom_overwrites", ui->title->text(), - ui->mods->currentText()); - } - else { - m_profile->removeSetting("custom_overwrites", ui->title->text()); - } - - //m_profile->removeForcedLibraries(ui->title->text()); - //m_profile->storeForcedLibraries(ui->title->text(), m_forcedLibraries); - //m_profile->setForcedLibrariesEnabled(ui->title->text(), ui->forceLoadLibraries->isChecked()); -} - - -void EditExecutablesDialog::delayedRefresh() -{ - /*QModelIndex index = ui->executablesListBox->currentIndex(); - resetInput(); - refreshExecutablesWidget(); - on_executablesListBox_clicked(index);*/ -} - - - - - - - -bool EditExecutablesDialog::executableChanged() -{ - /*if (m_currentItem != nullptr) { - const auto& title = m_currentItem->text(); - auto itor = m_executablesList.find(title); - - if (itor == m_executablesList.end()) { - qWarning().nospace() - << "executableChanged(): title '" << title << "' not found"; - - return false; - } - - const Executable& selectedExecutable = *itor; - - QString storedCustomOverwrite = m_profile->setting("custom_overwrites", selectedExecutable.title()).toString(); - - bool forcedLibrariesDirty = false; - 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) - { - return lhs.enabled() == rhs.enabled() && - lhs.forced() == rhs.forced() && - lhs.library() == rhs.library() && - lhs.process() == rhs.process(); - }); - forcedLibrariesDirty |= m_profile->setting("forced_libraries", ui->title->text() + "/enabled", false).toBool() != - ui->forceLoadLibraries->isChecked(); - - return selectedExecutable.title() != ui->title->text() - || selectedExecutable.arguments() != ui->arguments->text() - || selectedExecutable.steamAppID() != ui->steamAppID->text() - || !storedCustomOverwrite.isEmpty() != ui->createFilesInMod->isChecked() - || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->mods->currentText()) - || selectedExecutable.workingDirectory() != QDir::fromNativeSeparators(ui->workingDirectory->text()) - || selectedExecutable.binaryInfo().absoluteFilePath() != QDir::fromNativeSeparators(ui->binary->text()) - || selectedExecutable.usesOwnIcon() != ui->useApplicationIcon->isChecked() - || forcedLibrariesDirty - ; - } else { - QFileInfo fileInfo(ui->binary->text()); - return !ui->binary->text().isEmpty() - && !ui->title->text().isEmpty() - && fileInfo.exists() - && fileInfo.isFile(); - }*/ - - return false; -} - -void EditExecutablesDialog::on_buttons_accepted() -{ - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), - tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return; - } else if (res == QMessageBox::Yes) { - saveExecutable(); - // the executable list returned to callers is generated from the user data in the widgets, - // NOT the list we just saved - //refreshExecutablesWidget(); - } - } - - accept(); -} - -void EditExecutablesDialog::on_buttons_rejected() -{ - reject(); -} - -/*void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex ¤t) -{ - if (current.isValid()) { - - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), - tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No); - if (res == QMessageBox::Yes) { - saveExecutable(); - - //This is necessary if we're adding a new item, but it doesn't look very nice. - //Ideally we'd end up with the correct row displayed - ui->executablesListBox->selectionModel()->clearSelection(); - ui->executablesListBox->selectionModel()->select(current, QItemSelectionModel::SelectCurrent); - QTimer::singleShot(50, this, SLOT(delayedRefresh())); - return; - } - } - - ui->executablesListBox->selectionModel()->clearSelection(); - ui->executablesListBox->selectionModel()->select(current, QItemSelectionModel::SelectCurrent); - - m_CurrentItem = ui->executablesListBox->item(current.row()); - - const auto& title = m_CurrentItem->text(); - auto itor = m_ExecutablesList.find(title); - - if (itor == m_ExecutablesList.end()) { - qWarning().nospace() << "selection: executable '" << title << "' not found"; - return; - } - - const Executable& selectedExecutable = *itor; - - 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.steamAppID().isEmpty()); - if (!selectedExecutable.steamAppID().isEmpty()) { - ui->appIDOverwriteEdit->setText(selectedExecutable.steamAppID()); - } else { - ui->appIDOverwriteEdit->clear(); - } - ui->useAppIconCheckBox->setChecked(selectedExecutable.usesOwnIcon()); - - int index = -1; - - 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); - } - - ui->newFilesModCheckBox->setChecked(index != -1); - if (index != -1) { - ui->newFilesModBox->setCurrentIndex(index); - } - - m_ForcedLibraries = m_Profile->determineForcedLibraries(ui->titleEdit->text()); - bool forcedLibraries = m_Profile->forcedLibrariesEnabled(ui->titleEdit->text()); - ui->forceLoadButton->setEnabled(forcedLibraries); - ui->forceLoadCheckBox->setChecked(forcedLibraries); - } -}*/ - - void CustomOverwrites::load(Profile* p, const ExecutablesList& exes) { for (const auto& e : exes) { diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index f8382915..5199537c 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -91,11 +91,9 @@ public: ~EditExecutablesDialog(); - /** - * @brief retrieve the updated list of executables - * @return updated list of executables - **/ ExecutablesList getExecutablesList() const; + const CustomOverwrites& getCustomOverwrites() const; + const ForcedLibraries& getForcedLibraries() const; private slots: void on_list_itemSelectionChanged(); @@ -115,8 +113,6 @@ private slots: void on_buttons_accepted(); void on_buttons_rejected(); - void delayedRefresh(); - private: std::unique_ptr ui; ExecutablesList m_executablesList; @@ -140,11 +136,6 @@ private: void save(); void setJarBinary(const QString& binaryName); QString newExecutableTitle(); - - bool executableChanged(); - void updateButtonStates(); - void saveExecutable(); - }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 267a2971..05417f51 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2479,24 +2479,56 @@ static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, bool MainWindow::modifyExecutablesDialog() { bool result = false; + try { - EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(), + const auto oldExecutables = *m_OrganizerCore.executablesList(); + auto* profile = m_OrganizerCore.currentProfile(); + + EditExecutablesDialog dialog(oldExecutables, *m_OrganizerCore.modList(), - m_OrganizerCore.currentProfile(), + profile, m_OrganizerCore.managedGame(), this); QSettings &settings = m_OrganizerCore.settings().directInterface(); QString key = QString("geometry/%1").arg(dialog.objectName()); + if (settings.contains(key)) { dialog.restoreGeometry(settings.value(key).toByteArray()); } + if (dialog.exec() == QDialog::Accepted) { - m_OrganizerCore.setExecutablesList(dialog.getExecutablesList()); + const auto newExecutables = dialog.getExecutablesList(); + + // remove all the custom overwrites and forced libraries + for (const auto& e : oldExecutables) { + profile->removeSetting("custom_overwrites", e.title()); + profile->removeForcedLibraries(e.title()); + } + + // set the new custom overwrites and forced libraries + for (const auto& e : newExecutables) { + if (auto modName=dialog.getCustomOverwrites().find(e.title())) { + profile->storeSetting("custom_overwrites", e.title(), *modName); + } + + if (auto list=dialog.getForcedLibraries().find(e.title())) { + if (!list->empty()) { + profile->setForcedLibrariesEnabled(e.title(), true); + profile->storeForcedLibraries(e.title(), *list); + } + } + } + + // set the new executables list + m_OrganizerCore.setExecutablesList(newExecutables); + result = true; } + settings.setValue(key, dialog.saveGeometry()); refreshExecutablesList(); + updatePinnedExecutables(); } catch (const std::exception &e) { reportError(e.what()); } -- cgit v1.3.1 From d6d05dcae8d2b26aff5917449e38f9886bca65ba Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 11:02:43 -0400 Subject: handles changing the title to one that already exists by just ignoring it --- src/editexecutablesdialog.cpp | 57 +++++++++++++++++++++++++++++++++---------- src/editexecutablesdialog.h | 3 ++- 2 files changed, 46 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 28a6b148..102ac4d5 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -275,16 +275,22 @@ void EditExecutablesDialog::save() m_customOverwrites.remove(e->title()); } + QString newTitle = ui->title->text(); + if (isTitleConflicting(newTitle)) { + // don't save conflicting titles + newTitle = e->title(); + } + // forced libraries are saved in on_configureLibraries_clicked() // now rename both the custom overwrites and forced libraries if the title // is being changed - if (e->title() != ui->title->text()) { - m_customOverwrites.rename(e->title(), ui->title->text()); - m_forcedLibraries.rename(e->title(), ui->title->text()); + if (e->title() != newTitle) { + m_customOverwrites.rename(e->title(), newTitle); + m_forcedLibraries.rename(e->title(), newTitle); + e->title(newTitle); } - e->title(ui->title->text()); e->binaryInfo(ui->binary->text()); e->workingDirectory(ui->workingDirectory->text()); e->arguments(ui->arguments->text()); @@ -309,13 +315,13 @@ void EditExecutablesDialog::on_list_itemSelectionChanged() void EditExecutablesDialog::on_add_clicked() { - auto title = newExecutableTitle(); - if (title.isNull()) { + auto title = makeNonConflictingTitle(tr("New Executable")); + if (!title) { return; } auto e = Executable() - .title(title) + .title(*title) .flags(Executable::CustomExecutable); m_executablesList.setExecutable(e); @@ -362,12 +368,31 @@ void EditExecutablesDialog::on_remove_clicked() } } +bool EditExecutablesDialog::isTitleConflicting(const QString& s) +{ + for (const auto& exe : m_executablesList) { + if (exe.title() == s) { + if (&exe != selectedExe()) { + // found an executable that's not the current one with the same title + return true; + } + } + } + + return false; +} + void EditExecutablesDialog::on_title_textChanged(const QString& s) { if (m_settingUI) { return; } + // don't allow changing the title to something that already exists + if (isTitleConflicting(s)) { + return; + } + // must save before modifying the item in the list widget because saving // relies on the item's text being the same as an item in m_executablesList save(); @@ -427,7 +452,12 @@ void EditExecutablesDialog::on_browseBinary_clicked() } if (ui->title->text().isEmpty()) { - ui->title->setText(QFileInfo(binaryName).baseName()); + const auto prefix = QFileInfo(binaryName).baseName(); + const auto newTitle = makeNonConflictingTitle(prefix); + + if (newTitle) { + ui->title->setText(*newTitle); + } } save(); @@ -500,10 +530,9 @@ void EditExecutablesDialog::setJarBinary(const QString& binaryName) save(); } -QString EditExecutablesDialog::newExecutableTitle() +std::optional EditExecutablesDialog::makeNonConflictingTitle( + const QString& prefix) { - const auto prefix = tr("New Executable"); - QString title = prefix; for (int i=1; i<100; ++i) { @@ -514,8 +543,10 @@ QString EditExecutablesDialog::newExecutableTitle() title = prefix + QString(" (%1)").arg(i); } - qCritical().nospace() << "ran out of new executable titles"; - return QString::null; + qCritical().nospace() + << "ran out of executable titles for prefix '" << prefix << "'"; + + return {}; } diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 5199537c..6e006ae2 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -135,7 +135,8 @@ private: void setEdits(const Executable& e); void save(); void setJarBinary(const QString& binaryName); - QString newExecutableTitle(); + std::optional makeNonConflictingTitle(const QString& prefix); + bool isTitleConflicting(const QString& s); }; #endif // EDITEXECUTABLESDIALOG_H -- cgit v1.3.1 From e86224ee7ffdb18b7c7d17bc2c5cef2457665103 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 11:38:44 -0400 Subject: moved commiting changes to EditExecutablesDialog itself instead of doing it from the main window use a separate enabled state for custom overwrites and forced libraries, this remembers the values even when unchecking the checkbox, as long as the dialog stays opened pass the whole OrganizerCore to EditExecutablesDialog, simplifies a bunch of things --- src/editexecutablesdialog.cpp | 147 ++++++++++++++++++++++++++++++++---------- src/editexecutablesdialog.h | 39 +++++++---- src/mainwindow.cpp | 36 +---------- 3 files changed, 139 insertions(+), 83 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 102ac4d5..cb6ec6e8 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -33,15 +33,12 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -EditExecutablesDialog::EditExecutablesDialog( - const ExecutablesList &executablesList, const ModList &modList, - Profile *profile, const IPluginGame *game, QWidget *parent) +EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) : TutorableDialog("EditExecutables", parent) , ui(new Ui::EditExecutablesDialog) - , m_currentItem(nullptr) - , m_executablesList(executablesList) - , m_profile(profile) - , m_gamePlugin(game) + , m_organizerCore(oc) + , m_originalExecutables(*oc.executablesList()) + , m_executablesList(*oc.executablesList()) , m_settingUI(false) { ui->setupUi(this); @@ -49,11 +46,11 @@ EditExecutablesDialog::EditExecutablesDialog( ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); - m_customOverwrites.load(profile, m_executablesList); - m_forcedLibraries.load(profile, m_executablesList); + m_customOverwrites.load(m_organizerCore.currentProfile(), m_executablesList); + m_forcedLibraries.load(m_organizerCore.currentProfile(), m_executablesList); fillExecutableList(); - ui->mods->addItems(modList.allMods()); + ui->mods->addItems(m_organizerCore.modList()->allMods()); // some widgets need to do more than just save() and have their own handler connect(ui->binary, &QLineEdit::textChanged, [&]{ save(); }); @@ -106,6 +103,37 @@ const ForcedLibraries& EditExecutablesDialog::getForcedLibraries() const return m_forcedLibraries; } +void EditExecutablesDialog::commitChanges() +{ + const auto newExecutables = getExecutablesList(); + auto* profile = m_organizerCore.currentProfile(); + + // remove all the custom overwrites and forced libraries + for (const auto& e : m_originalExecutables) { + profile->removeSetting("custom_overwrites", e.title()); + profile->removeForcedLibraries(e.title()); + } + + // set the new custom overwrites and forced libraries + for (const auto& e : newExecutables) { + if (auto info=m_customOverwrites.find(e.title())) { + if (info && info->enabled) { + profile->storeSetting("custom_overwrites", e.title(), info->modName); + } + } + + if (auto info=m_forcedLibraries.find(e.title())) { + if (info && info->enabled && !info->list.empty()) { + profile->setForcedLibrariesEnabled(e.title(), true); + profile->storeForcedLibraries(e.title(), info->list); + } + } + } + + // set the new executables list + m_organizerCore.setExecutablesList(newExecutables); +} + QListWidgetItem* EditExecutablesDialog::selectedItem() { const auto selection = ui->list->selectedItems(); @@ -215,23 +243,28 @@ void EditExecutablesDialog::setEdits(const Executable& e) { int modIndex = -1; - if (const auto mod=m_customOverwrites.find(e.title())) { - modIndex = ui->mods->findText(*mod); + const auto info = m_customOverwrites.find(e.title()); + + if (info && !info->modName.isEmpty()) { + modIndex = ui->mods->findText(info->modName); if (modIndex == -1) { qWarning().nospace() - << "executable '" << e.title() << "' uses mod '" << *mod << "' " + << "executable '" << e.title() << "' uses mod '" << info->modName << "' " << "as a custom overwrite, but that mod doesn't exist"; } } - ui->createFilesInMod->setChecked(modIndex != -1); - ui->mods->setEnabled(modIndex != -1); + const bool hasCustomOverwrites = (info && info->enabled); + + ui->createFilesInMod->setChecked(hasCustomOverwrites); + ui->mods->setEnabled(hasCustomOverwrites); ui->mods->setCurrentIndex(modIndex); } { - const auto hasForcedLibraries = m_forcedLibraries.find(e.title()).has_value(); + const auto info = m_forcedLibraries.find(e.title()); + const bool hasForcedLibraries = (info && info->enabled); ui->forceLoadLibraries->setChecked(hasForcedLibraries); ui->configureLibraries->setEnabled(hasForcedLibraries); @@ -269,25 +302,32 @@ void EditExecutablesDialog::save() qDebug().nospace() << "saving '" << e->title() << "'"; // title may have changed, start with the stuff using it + + // custom overwrites if (ui->createFilesInMod->isChecked()) { - m_customOverwrites.set(e->title(), ui->mods->currentText()); + m_customOverwrites.setEnabled(e->title(), true); + m_customOverwrites.setMod(e->title(), ui->mods->currentText()); } else { - m_customOverwrites.remove(e->title()); + m_customOverwrites.setEnabled(e->title(), false); } + // forced libraries + m_forcedLibraries.setEnabled(e->title(), ui->forceLoadLibraries->isChecked()); + + // get the new title, but ignore it if it's conflicting with an already + // existing executable QString newTitle = ui->title->text(); if (isTitleConflicting(newTitle)) { - // don't save conflicting titles newTitle = e->title(); } - // forced libraries are saved in on_configureLibraries_clicked() - - // now rename both the custom overwrites and forced libraries if the title - // is being changed if (e->title() != newTitle) { + // now rename both the custom overwrites and forced libraries if the title + // is being changed m_customOverwrites.rename(e->title(), newTitle); m_forcedLibraries.rename(e->title(), newTitle); + + // save the new title e->title(newTitle); } @@ -485,20 +525,21 @@ void EditExecutablesDialog::on_configureLibraries_clicked() return; } - ForcedLoadDialog dialog(m_gamePlugin, this); + ForcedLoadDialog dialog(m_organizerCore.managedGame(), this); - if (auto list=m_forcedLibraries.find(e->title())) { - dialog.setValues(*list); + if (auto info=m_forcedLibraries.find(e->title())) { + dialog.setValues(info->list); } if (dialog.exec() == QDialog::Accepted) { - m_forcedLibraries.set(e->title(), dialog.values()); + m_forcedLibraries.setList(e->title(), dialog.values()); save(); } } void EditExecutablesDialog::on_buttons_accepted() { + commitChanges(); accept(); } @@ -556,12 +597,13 @@ void CustomOverwrites::load(Profile* p, const ExecutablesList& exes) const auto s = p->setting("custom_overwrites", e.title()).toString(); if (!s.isEmpty()) { - m_map[e.title()] = s; + m_map[e.title()] = {true, s}; } } } -std::optional CustomOverwrites::find(const QString& title) const +std::optional CustomOverwrites::find( + const QString& title) const { auto itor = m_map.find(title); if (itor == m_map.end()) { @@ -571,9 +613,26 @@ std::optional CustomOverwrites::find(const QString& title) const return itor->second; } -void CustomOverwrites::set(const QString& title, const QString& mod) +void CustomOverwrites::setEnabled(const QString& title, bool b) { - m_map[title] = mod; + auto itor = m_map.find(title); + + if (itor == m_map.end()) { + m_map[title] = {b, {}}; + } else { + itor->second.enabled = b; + } +} + +void CustomOverwrites::setMod(const QString& title, const QString& mod) +{ + auto itor = m_map.find(title); + + if (itor == m_map.end()) { + m_map[title] = {true, mod}; + } else { + itor->second.modName = mod; + } } void CustomOverwrites::rename(const QString& oldTitle, const QString& newTitle) @@ -602,12 +661,13 @@ void ForcedLibraries::load(Profile* p, const ExecutablesList& exes) { for (const auto& e : exes) { if (p->forcedLibrariesEnabled(e.title())) { - m_map[e.title()] = p->determineForcedLibraries(e.title()); + m_map[e.title()] = {true, p->determineForcedLibraries(e.title())}; } } } -std::optional ForcedLibraries::find(const QString& title) const +std::optional ForcedLibraries::find( + const QString& title) const { auto itor = m_map.find(title); if (itor == m_map.end()) { @@ -617,9 +677,26 @@ std::optional ForcedLibraries::find(const QString& t return itor->second; } -void ForcedLibraries::set(const QString& title, const list_type& mod) +void ForcedLibraries::setEnabled(const QString& title, bool b) +{ + auto itor = m_map.find(title); + + if (itor == m_map.end()) { + m_map[title] = {b, {}}; + } else { + itor->second.enabled = b; + } +} + +void ForcedLibraries::setList(const QString& title, const list_type& list) { - m_map[title] = mod; + auto itor = m_map.find(title); + + if (itor == m_map.end()) { + m_map[title] = {true, list}; + } else { + itor->second.list = list; + } } void ForcedLibraries::rename(const QString& oldTitle, const QString& newTitle) diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 6e006ae2..4957ebff 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -34,7 +34,7 @@ namespace Ui { } class ModList; - +class OrganizerCore; /** helper class to manage custom overwrites within the edit executables * dialog @@ -42,15 +42,22 @@ class ModList; class CustomOverwrites { public: + struct Info + { + bool enabled; + QString modName; + }; + void load(Profile* p, const ExecutablesList& exes); - std::optional find(const QString& title) const; + std::optional find(const QString& title) const; - void set(const QString& title, const QString& mod); + void setEnabled(const QString& title, bool b); + void setMod(const QString& title, const QString& mod); void rename(const QString& oldTitle, const QString& newTitle); void remove(const QString& title); private: - std::map m_map; + std::map m_map; }; @@ -61,15 +68,22 @@ class ForcedLibraries public: using list_type = QList; + struct Info + { + bool enabled; + list_type list; + }; + void load(Profile* p, const ExecutablesList& exes); - std::optional find(const QString& title) const; + std::optional find(const QString& title) const; - void set(const QString& title, const list_type& list); + void setEnabled(const QString& title, bool b); + void setList(const QString& title, const list_type& list); void rename(const QString& oldTitle, const QString& newTitle); void remove(const QString& title); private: - std::map m_map; + std::map m_map; }; @@ -85,9 +99,7 @@ public: * @param executablesList current list of executables * @param parent parent widget **/ - explicit EditExecutablesDialog( - const ExecutablesList &executablesList, const ModList &modList, - Profile *profile, const MOBase::IPluginGame *game, QWidget *parent = 0); + explicit EditExecutablesDialog(OrganizerCore& oc, QWidget* parent=nullptr); ~EditExecutablesDialog(); @@ -115,15 +127,13 @@ private slots: private: std::unique_ptr ui; + OrganizerCore& m_organizerCore; + const ExecutablesList m_originalExecutables; ExecutablesList m_executablesList; CustomOverwrites m_customOverwrites; ForcedLibraries m_forcedLibraries; - Profile *m_profile; - const MOBase::IPluginGame *m_gamePlugin; bool m_settingUI; - QListWidgetItem *m_currentItem; - QListWidgetItem* selectedItem(); Executable* selectedExe(); @@ -137,6 +147,7 @@ private: void setJarBinary(const QString& binaryName); std::optional makeNonConflictingTitle(const QString& prefix); bool isTitleConflicting(const QString& s); + void commitChanges(); }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 05417f51..15a6eb62 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2482,13 +2482,8 @@ bool MainWindow::modifyExecutablesDialog() try { const auto oldExecutables = *m_OrganizerCore.executablesList(); - auto* profile = m_OrganizerCore.currentProfile(); - EditExecutablesDialog dialog(oldExecutables, - *m_OrganizerCore.modList(), - profile, - m_OrganizerCore.managedGame(), - this); + EditExecutablesDialog dialog(m_OrganizerCore, this); QSettings &settings = m_OrganizerCore.settings().directInterface(); QString key = QString("geometry/%1").arg(dialog.objectName()); @@ -2497,34 +2492,7 @@ bool MainWindow::modifyExecutablesDialog() dialog.restoreGeometry(settings.value(key).toByteArray()); } - if (dialog.exec() == QDialog::Accepted) { - const auto newExecutables = dialog.getExecutablesList(); - - // remove all the custom overwrites and forced libraries - for (const auto& e : oldExecutables) { - profile->removeSetting("custom_overwrites", e.title()); - profile->removeForcedLibraries(e.title()); - } - - // set the new custom overwrites and forced libraries - for (const auto& e : newExecutables) { - if (auto modName=dialog.getCustomOverwrites().find(e.title())) { - profile->storeSetting("custom_overwrites", e.title(), *modName); - } - - if (auto list=dialog.getForcedLibraries().find(e.title())) { - if (!list->empty()) { - profile->setForcedLibrariesEnabled(e.title(), true); - profile->storeForcedLibraries(e.title(), *list); - } - } - } - - // set the new executables list - m_OrganizerCore.setExecutablesList(newExecutables); - - result = true; - } + result = (dialog.exec() == QDialog::Accepted); settings.setValue(key, dialog.saveGeometry()); refreshExecutablesList(); -- cgit v1.3.1 From 5e65325772ec427c0114dee29043d5cd78131cbc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 11:43:20 -0400 Subject: fixed plugin executables not overriding if the custom attribute was inadvertently set --- src/executableslist.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index f592a2b7..0283a845 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -312,11 +312,11 @@ bool Executable::usesOwnIcon() const void Executable::mergeFrom(const Executable& other) { - // flags on plugin executables that the user is allowed to chnage + // flags on plugin executables that the user is allowed to change const auto allow = ShowInToolbar; - if (!isCustom() && !other.isCustom()) { + if (!other.isCustom()) { // this happens when loading plugin executables in addFromPlugin(), replace // everything in case the plugin has changed -- cgit v1.3.1 From 98d5602c9951b49f8033161d8497553293070f8f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 11:53:20 -0400 Subject: adjusted the position of the "(*) Profile Specific" label comments --- src/editexecutablesdialog.cpp | 3 +++ src/editexecutablesdialog.ui | 3 +++ 2 files changed, 6 insertions(+) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index cb6ec6e8..96f4660a 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -485,12 +485,15 @@ void EditExecutablesDialog::on_browseBinary_clicked() return; } + // setting binary if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { + // special case for jar files, uses the system java installation setJarBinary(binaryName); } else { ui->binary->setText(QDir::toNativeSeparators(binaryName)); } + // setting title if currently empty if (ui->title->text().isEmpty()) { const auto prefix = QFileInfo(binaryName).baseName(); const auto newTitle = makeNonConflictingTitle(prefix); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 6cb8e0b8..7a5fa27f 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -359,6 +359,9 @@ Right now the only case I know of where this needs to be overwritten is for the (*) Profile Specific + + 5 + -- cgit v1.3.1 From cbe6dd93b1607f9b95037c2e131f34c27d32753d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 11:56:22 -0400 Subject: made the default size of the dialog a bit larger --- src/editexecutablesdialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 7a5fa27f..e85256c8 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -7,7 +7,7 @@ 0 0 710 - 348 + 440 -- cgit v1.3.1 From 5fa7fd7fb9f3a8f67e8842420d7a2ad600722119 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 17:05:51 -0400 Subject: moved add/remove to the top, changed them to tool buttons with icons, added up/down buttons --- src/editexecutablesdialog.cpp | 48 +++++++++++++++++++++++++ src/editexecutablesdialog.h | 3 ++ src/editexecutablesdialog.ui | 84 +++++++++++++++++++++++++------------------ 3 files changed, 101 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 96f4660a..9747a86f 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -348,6 +348,44 @@ void EditExecutablesDialog::save() } } +void EditExecutablesDialog::moveSelection(int by) +{ + auto* item = selectedItem(); + if (!item) { + return; + } + + // moving down the list + while (by > 0) { + const auto row = ui->list->row(item); + + if (row >= (ui->list->count() - 1)) { + break; + } + + ui->list->takeItem(row); + ui->list->insertItem(row + 1, item); + item->setSelected(true); + + --by; + } + + // moving up the list + while (by < 0) { + const auto row = ui->list->row(item); + + if (row <= 0) { + break; + } + + ui->list->takeItem(row); + ui->list->insertItem(row - 1, item); + item->setSelected(true); + + ++by; + } +} + void EditExecutablesDialog::on_list_itemSelectionChanged() { updateUI(selectedExe()); @@ -408,6 +446,16 @@ void EditExecutablesDialog::on_remove_clicked() } } +void EditExecutablesDialog::on_up_clicked() +{ + moveSelection(-1); +} + +void EditExecutablesDialog::on_down_clicked() +{ + moveSelection(+1); +} + bool EditExecutablesDialog::isTitleConflicting(const QString& s) { for (const auto& exe : m_executablesList) { diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 4957ebff..80ec56df 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -112,6 +112,8 @@ private slots: void on_add_clicked(); void on_remove_clicked(); + void on_up_clicked(); + void on_down_clicked(); void on_title_textChanged(const QString& s); void on_overwriteSteamAppID_toggled(bool checked); @@ -144,6 +146,7 @@ private: void clearEdits(); void setEdits(const Executable& e); void save(); + void moveSelection(int by); void setJarBinary(const QString& binaryName); std::optional makeNonConflictingTitle(const QString& prefix); bool isTitleConflicting(const QString& s); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index e85256c8..9801a75c 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -57,24 +57,15 @@ 0 - - - - List of configured executables - - - This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. - - - QAbstractItemView::InternalMove - - - Qt::TargetMoveAction - - - + + + + Executables + + + @@ -89,7 +80,7 @@ - + Add an executable @@ -100,13 +91,13 @@ Add - - :/new/guiresources/resources/list-add.png:/new/guiresources/resources/list-add.png + + :/MO/gui/add:/MO/gui/add - + Remove the selected executable @@ -117,26 +108,51 @@ Remove - - :/new/guiresources/resources/list-remove.png:/new/guiresources/resources/list-remove.png + + :/MO/gui/resources/list-remove.png:/MO/gui/resources/list-remove.png - - - Qt::Horizontal + + + Move the executable up in the list - - - 40 - 20 - + + + :/MO/gui/resources/go-up.png:/MO/gui/resources/go-up.png - + + + + + + Move the executable down in the list + + + + :/MO/gui/resources/go-down.png:/MO/gui/resources/go-down.png + + + + + + List of configured executables + + + This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. + + + QAbstractItemView::InternalMove + + + Qt::TargetMoveAction + + + @@ -420,9 +436,9 @@ Right now the only case I know of where this needs to be overwritten is for the steamAppID createFilesInMod mods - add - remove - + + + -- cgit v1.3.1 From c8e1b4ab3a51cfe0f39a6f0d78cf0988c380549c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 17:43:10 -0400 Subject: changed the down/up pngs to be slightly offset vertically from what they were, they did not look aligned when next to each other added status/tooltip/whatsthis strings to new buttons change enabled status of up/down dynamically simplified move() to just move by one --- src/editexecutablesdialog.cpp | 87 ++++++++++++++++++++++++++---------------- src/editexecutablesdialog.h | 6 ++- src/editexecutablesdialog.ui | 24 ++++++++++++ src/resources/go-down.png | Bin 874 -> 937 bytes src/resources/go-up.png | Bin 877 -> 974 bytes 5 files changed, 82 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 9747a86f..e2772757 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -64,7 +64,7 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) if (ui->list->count() > 0) { ui->list->item(0)->setSelected(true); } else { - updateUI(nullptr); + updateUI(nullptr, nullptr); } } @@ -184,23 +184,40 @@ QListWidgetItem* EditExecutablesDialog::createListItem(const Executable& exe) return newItem; } -void EditExecutablesDialog::updateUI(const Executable* e) +void EditExecutablesDialog::updateUI( + const QListWidgetItem* item, const Executable* e) { // the ui is currently being set, ignore changes m_settingUI = true; if (e) { setEdits(*e); - ui->remove->setEnabled(e->isCustom()); } else { clearEdits(); - ui->remove->setEnabled(false); } + setButtons(item, e); + // any changes from now on are from the user m_settingUI = false; } +void EditExecutablesDialog::setButtons( + const QListWidgetItem* item, const Executable* e) +{ + // add is always enabled + + if (item) { + ui->remove->setEnabled(e->isCustom()); + ui->up->setEnabled(canMove(item, -1)); + ui->down->setEnabled(canMove(item, +1)); + } else { + ui->remove->setEnabled(false); + ui->up->setEnabled(false); + ui->down->setEnabled(false); + } +} + void EditExecutablesDialog::clearEdits() { ui->title->clear(); @@ -348,47 +365,41 @@ void EditExecutablesDialog::save() } } -void EditExecutablesDialog::moveSelection(int by) +bool EditExecutablesDialog::canMove(const QListWidgetItem* item, int direction) { - auto* item = selectedItem(); if (!item) { - return; + return false; } - // moving down the list - while (by > 0) { - const auto row = ui->list->row(item); + if (direction < 0) { + // moving up + return (ui->list->row(item) > 0); - if (row >= (ui->list->count() - 1)) { - break; - } - - ui->list->takeItem(row); - ui->list->insertItem(row + 1, item); - item->setSelected(true); - - --by; + } else if (direction > 0) { + // moving down + return (ui->list->row(item) < (ui->list->count() - 1)); } - // moving up the list - while (by < 0) { - const auto row = ui->list->row(item); + return false; +} - if (row <= 0) { - break; - } +void EditExecutablesDialog::move(QListWidgetItem* item, int direction) +{ + if (!canMove(item, direction)) { + return; + } - ui->list->takeItem(row); - ui->list->insertItem(row - 1, item); - item->setSelected(true); + const auto row = ui->list->row(item); - ++by; - } + // removing item + ui->list->takeItem(row); + ui->list->insertItem(row + (direction > 0 ? 1 : -1), item); + item->setSelected(true); } void EditExecutablesDialog::on_list_itemSelectionChanged() { - updateUI(selectedExe()); + updateUI(selectedItem(), selectedExe()); } void EditExecutablesDialog::on_add_clicked() @@ -448,12 +459,22 @@ void EditExecutablesDialog::on_remove_clicked() void EditExecutablesDialog::on_up_clicked() { - moveSelection(-1); + auto* item = selectedItem(); + if (!item) { + return; + } + + move(item, -1); } void EditExecutablesDialog::on_down_clicked() { - moveSelection(+1); + auto* item = selectedItem(); + if (!item) { + return; + } + + move(item, +1); } bool EditExecutablesDialog::isTitleConflicting(const QString& s) diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 80ec56df..10a6166f 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -142,11 +142,13 @@ private: void fillExecutableList(); QListWidgetItem* createListItem(const Executable& exe); - void updateUI(const Executable* e); + void updateUI(const QListWidgetItem* item, const Executable* e); void clearEdits(); void setEdits(const Executable& e); + void setButtons(const QListWidgetItem* item, const Executable* e); void save(); - void moveSelection(int by); + bool canMove(const QListWidgetItem* item, int direction); + void move(QListWidgetItem* item, int direction); void setJarBinary(const QString& binaryName); std::optional makeNonConflictingTitle(const QString& prefix); bool isTitleConflicting(const QString& s); diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 9801a75c..fb65dbbe 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -84,6 +84,9 @@ Add an executable + + Add an executable + Add an executable @@ -101,6 +104,9 @@ Remove the selected executable + + Remove the selected executable + Remove the selected executable @@ -115,6 +121,15 @@ + + Move the executable up in the list + + + Move the executable up in the list + + + Move the executable up in the list + Move the executable up in the list @@ -126,6 +141,15 @@ + + Move the executable down in the list + + + Move the executable down in the list + + + Move the executable down in the list + Move the executable down in the list diff --git a/src/resources/go-down.png b/src/resources/go-down.png index af237881..bf0ce4fd 100644 Binary files a/src/resources/go-down.png and b/src/resources/go-down.png differ diff --git a/src/resources/go-up.png b/src/resources/go-up.png index b0a0cd72..a4b4e022 100644 Binary files a/src/resources/go-up.png and b/src/resources/go-up.png differ -- cgit v1.3.1 From b04782877e34254ae0205c71d0bf15bef1633749 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 9 Jun 2019 17:51:15 -0400 Subject: reduced margins around edit widgets, increased splitter handle width to separate the top buttons from the edit widgets a bit more --- src/editexecutablesdialog.ui | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src') diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index fb65dbbe..83d81225 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -40,6 +40,9 @@ Qt::Horizontal + + 9 + false @@ -196,6 +199,15 @@ + + 0 + + + 0 + + + 0 + 10 -- cgit v1.3.1 From 82975a6a38d0807e962b0e4b6cbd76337dc8189c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 12 Jun 2019 15:32:07 -0400 Subject: reduced spacing between buttons, removed chatty logging --- src/editexecutablesdialog.cpp | 2 -- src/editexecutablesdialog.ui | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index e2772757..6c0522e4 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -316,8 +316,6 @@ void EditExecutablesDialog::save() return; } - qDebug().nospace() << "saving '" << e->title() << "'"; - // title may have changed, start with the stuff using it // custom overwrites diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 83d81225..9b6c8153 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -48,6 +48,9 @@ + + 3 + 0 @@ -62,6 +65,9 @@ + + 0 + -- cgit v1.3.1 From 7671725436551b7254ea89ff6985c5491fcc743d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 04:30:33 -0400 Subject: removed concept of custom executables, everything is modifiable added apply button to dialog added reset button that re-adds plugin executables and renames existing ones if needed moved executables files to their filter in visual studio --- src/CMakeLists.txt | 11 +-- src/editexecutablesdialog.cpp | 143 ++++++++++++++++++++------------------ src/editexecutablesdialog.h | 9 +-- src/editexecutablesdialog.ui | 37 +++++----- src/executableslist.cpp | 156 ++++++++++++++++++++++++------------------ src/executableslist.h | 24 +++++-- src/mainwindow.cpp | 3 +- src/pch.h | 1 + 8 files changed, 221 insertions(+), 163 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5623a851..645a5a73 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -293,7 +293,6 @@ set(core categories shared/directoryentry directoryrefresher - executableslist installationmanager instancemanager loadmechanism @@ -309,7 +308,6 @@ set(dialogs activatemodsdialog categoriesdialog credentialsdialog - editexecutablesdialog filedialogmemory forcedloaddialog forcedloaddialogwidget @@ -333,6 +331,11 @@ set(downloads downloadmanager ) +set(executables + executableslist + editexecutablesdialog +) + set(locking ilockedwaitingforprocess lockeddialog @@ -412,8 +415,8 @@ set(widgets ) set(src_filters - application core browser dialogs downloads locking modinfo modlist plugins - previews profiles settings utilities widgets + application core browser dialogs downloads executables locking modinfo + modlist plugins previews profiles settings utilities widgets ) foreach(filter in list ${src_filters}) diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 6c0522e4..882bb8b5 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -49,8 +49,9 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) m_customOverwrites.load(m_organizerCore.currentProfile(), m_executablesList); m_forcedLibraries.load(m_organizerCore.currentProfile(), m_executablesList); - fillExecutableList(); + fillList(); ui->mods->addItems(m_organizerCore.modList()->allMods()); + setDirty(false); // some widgets need to do more than just save() and have their own handler connect(ui->binary, &QLineEdit::textChanged, [&]{ save(); }); @@ -59,13 +60,7 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) connect(ui->steamAppID, &QLineEdit::textChanged, [&]{ save(); }); connect(ui->mods, &QComboBox::currentTextChanged, [&]{ save(); }); connect(ui->useApplicationIcon, &QCheckBox::toggled, [&]{ save(); }); - - // select the first one in the list, if any - if (ui->list->count() > 0) { - ui->list->item(0)->setSelected(true); - } else { - updateUI(nullptr, nullptr); - } + connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&]{ saveOrder(); }); } EditExecutablesDialog::~EditExecutablesDialog() = default; @@ -132,6 +127,15 @@ void EditExecutablesDialog::commitChanges() // set the new executables list m_organizerCore.setExecutablesList(newExecutables); + + setDirty(false); +} + +void EditExecutablesDialog::setDirty(bool b) +{ + if (auto* button=ui->buttons->button(QDialogButtonBox::Apply)) { + button->setEnabled(b); + } } QListWidgetItem* EditExecutablesDialog::selectedItem() @@ -162,26 +166,25 @@ Executable* EditExecutablesDialog::selectedExe() return &*itor; } -void EditExecutablesDialog::fillExecutableList() +void EditExecutablesDialog::fillList() { ui->list->clear(); for(const auto& exe : m_executablesList) { ui->list->addItem(createListItem(exe)); } + + // select the first one in the list, if any + if (ui->list->count() > 0) { + ui->list->item(0)->setSelected(true); + } else { + updateUI(nullptr, nullptr); + } } QListWidgetItem* EditExecutablesDialog::createListItem(const Executable& exe) { - QListWidgetItem *newItem = new QListWidgetItem(exe.title()); - - if (!exe.isCustom()) { - auto f = newItem->font(); - f.setItalic(true); - newItem->setFont(f); - } - - return newItem; + return new QListWidgetItem(exe.title()); } void EditExecutablesDialog::updateUI( @@ -205,14 +208,12 @@ void EditExecutablesDialog::updateUI( void EditExecutablesDialog::setButtons( const QListWidgetItem* item, const Executable* e) { - // add is always enabled + // add and remove are always enabled if (item) { - ui->remove->setEnabled(e->isCustom()); ui->up->setEnabled(canMove(item, -1)); ui->down->setEnabled(canMove(item, +1)); } else { - ui->remove->setEnabled(false); ui->up->setEnabled(false); ui->down->setEnabled(false); } @@ -243,7 +244,6 @@ void EditExecutablesDialog::clearEdits() ui->configureLibraries->setEnabled(false); ui->useApplicationIcon->setEnabled(false); ui->useApplicationIcon->setChecked(false); - ui->pluginProvidedLabel->setVisible(false); } void EditExecutablesDialog::setEdits(const Executable& e) @@ -287,19 +287,15 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->configureLibraries->setEnabled(hasForcedLibraries); } - ui->pluginProvidedLabel->setVisible(!e.isCustom()); - - // only enabled for custom executables - ui->title->setEnabled(e.isCustom()); - ui->binary->setEnabled(e.isCustom()); - ui->browseBinary->setEnabled(e.isCustom()); - ui->workingDirectory->setEnabled(e.isCustom()); - ui->browseWorkingDirectory->setEnabled(e.isCustom()); - ui->arguments->setEnabled(e.isCustom()); - ui->overwriteSteamAppID->setEnabled(e.isCustom()); - ui->useApplicationIcon->setEnabled(e.isCustom()); - // always enabled + ui->title->setEnabled(true); + ui->binary->setEnabled(true); + ui->browseBinary->setEnabled(true); + ui->workingDirectory->setEnabled(true); + ui->browseWorkingDirectory->setEnabled(true); + ui->arguments->setEnabled(true); + ui->overwriteSteamAppID->setEnabled(true); + ui->useApplicationIcon->setEnabled(true); ui->createFilesInMod->setEnabled(true); ui->forceLoadLibraries->setEnabled(true); } @@ -361,6 +357,14 @@ void EditExecutablesDialog::save() } else { e->flags(e->flags() & (~Executable::UseApplicationIcon)); } + + setDirty(true); +} + +void EditExecutablesDialog::saveOrder() +{ + m_executablesList = getExecutablesList(); + setDirty(true); } bool EditExecutablesDialog::canMove(const QListWidgetItem* item, int direction) @@ -393,6 +397,8 @@ void EditExecutablesDialog::move(QListWidgetItem* item, int direction) ui->list->takeItem(row); ui->list->insertItem(row + (direction > 0 ? 1 : -1), item); item->setSelected(true); + + setDirty(true); } void EditExecutablesDialog::on_list_itemSelectionChanged() @@ -400,22 +406,43 @@ void EditExecutablesDialog::on_list_itemSelectionChanged() updateUI(selectedItem(), selectedExe()); } +void EditExecutablesDialog::on_reset_clicked() +{ + const auto title = tr("Reset plugin executables"); + + const auto text = tr( + "This will restore all the executables provided by the game plugin. If " + "there are existing executables with the same names, they will be " + "automatically renamed and left unchanged."); + + const auto buttons = QMessageBox::Ok | QMessageBox::Cancel; + + if (QMessageBox::question(this, title, text, buttons) != QMessageBox::Ok) { + return; + } + + m_executablesList.resetFromPlugin(m_organizerCore.managedGame()); + fillList(); + + setDirty(true); +} + void EditExecutablesDialog::on_add_clicked() { - auto title = makeNonConflictingTitle(tr("New Executable")); + auto title = m_executablesList.makeNonConflictingTitle(tr("New Executable")); if (!title) { return; } - auto e = Executable() - .title(*title) - .flags(Executable::CustomExecutable); + const Executable e(*title); m_executablesList.setExecutable(e); auto* item = createListItem(e); ui->list->addItem(item); item->setSelected(true); + + setDirty(true); } void EditExecutablesDialog::on_remove_clicked() @@ -453,6 +480,8 @@ void EditExecutablesDialog::on_remove_clicked() } else { ui->list->item(currentRow)->setSelected(true); } + + setDirty(true); } void EditExecutablesDialog::on_up_clicked() @@ -563,7 +592,7 @@ void EditExecutablesDialog::on_browseBinary_clicked() // setting title if currently empty if (ui->title->text().isEmpty()) { const auto prefix = QFileInfo(binaryName).baseName(); - const auto newTitle = makeNonConflictingTitle(prefix); + const auto newTitle = m_executablesList.makeNonConflictingTitle(prefix); if (newTitle) { ui->title->setText(*newTitle); @@ -607,15 +636,16 @@ void EditExecutablesDialog::on_configureLibraries_clicked() } } -void EditExecutablesDialog::on_buttons_accepted() +void EditExecutablesDialog::on_buttons_clicked(QAbstractButton* b) { - commitChanges(); - accept(); -} - -void EditExecutablesDialog::on_buttons_rejected() -{ - reject(); + if (b == ui->buttons->button(QDialogButtonBox::Ok)) { + commitChanges(); + accept(); + } else if (b == ui->buttons->button(QDialogButtonBox::Apply)) { + commitChanges(); + } else { + reject(); + } } void EditExecutablesDialog::setJarBinary(const QString& binaryName) @@ -641,25 +671,6 @@ void EditExecutablesDialog::setJarBinary(const QString& binaryName) save(); } -std::optional EditExecutablesDialog::makeNonConflictingTitle( - const QString& prefix) -{ - QString title = prefix; - - for (int i=1; i<100; ++i) { - if (!m_executablesList.titleExists(title)) { - return title; - } - - title = prefix + QString(" (%1)").arg(i); - } - - qCritical().nospace() - << "ran out of executable titles for prefix '" << prefix << "'"; - - return {}; -} - void CustomOverwrites::load(Profile* p, const ExecutablesList& exes) { diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 10a6166f..4a04ef42 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -110,6 +110,7 @@ public: private slots: void on_list_itemSelectionChanged(); + void on_reset_clicked(); void on_add_clicked(); void on_remove_clicked(); void on_up_clicked(); @@ -124,8 +125,7 @@ private slots: void on_browseWorkingDirectory_clicked(); void on_configureLibraries_clicked(); - void on_buttons_accepted(); - void on_buttons_rejected(); + void on_buttons_clicked(QAbstractButton* b); private: std::unique_ptr ui; @@ -140,19 +140,20 @@ private: QListWidgetItem* selectedItem(); Executable* selectedExe(); - void fillExecutableList(); + void fillList(); QListWidgetItem* createListItem(const Executable& exe); void updateUI(const QListWidgetItem* item, const Executable* e); void clearEdits(); void setEdits(const Executable& e); void setButtons(const QListWidgetItem* item, const Executable* e); void save(); + void saveOrder(); bool canMove(const QListWidgetItem* item, int direction); void move(QListWidgetItem* item, int direction); void setJarBinary(const QString& binaryName); - std::optional makeNonConflictingTitle(const QString& prefix); bool isTitleConflicting(const QString& s); void commitChanges(); + void setDirty(bool b); }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 9b6c8153..a42dbeed 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -168,6 +168,26 @@ + + + + Adds the executables provided by the game plugin and moves any existing executables out of the way + + + Adds the executables provided by the game plugin and moves any existing executables out of the way + + + Adds the executables provided by the game plugin and moves any existing executables out of the way + + + Reset + + + + :/MO/gui/edit_clear:/MO/gui/edit_clear + + + @@ -425,21 +445,6 @@ Right now the only case I know of where this needs to be overwritten is for the - - - - - true - - - - This executable is provided by the game plugin - - - Qt::AlignCenter - - - @@ -463,7 +468,7 @@ Right now the only case I know of where this needs to be overwritten is for the - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 0283a845..daf200a6 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -74,8 +74,6 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) settings.setArrayIndex(i); Executable::Flags flags; - if (settings.value("custom", true).toBool()) - flags |= Executable::CustomExecutable; if (settings.value("toolbar", false).toBool()) flags |= Executable::ShowInToolbar; if (settings.value("ownicon", false).toBool()) @@ -92,7 +90,7 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) settings.endArray(); - addFromPlugin(game); + addFromPlugin(game, IgnoreExisting); } void ExecutablesList::store(QSettings& settings) @@ -106,29 +104,33 @@ void ExecutablesList::store(QSettings& settings) settings.setArrayIndex(count++); 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.binaryInfo().absoluteFilePath()); - settings.setValue("arguments", item.arguments()); - settings.setValue("workingDirectory", item.workingDirectory()); - settings.setValue("steamAppID", item.steamAppID()); - } + settings.setValue("binary", item.binaryInfo().absoluteFilePath()); + settings.setValue("arguments", item.arguments()); + settings.setValue("workingDirectory", item.workingDirectory()); + settings.setValue("steamAppID", item.steamAppID()); } settings.endArray(); } -void ExecutablesList::addFromPlugin(IPluginGame const *game) +void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game) +{ + qDebug("resetting plugin executables"); + addFromPlugin(game, MoveExisting); +} + +void ExecutablesList::addFromPlugin(IPluginGame const *game, SetFlags flags) { Q_ASSERT(game != nullptr); for (const ExecutableInfo &info : game->executables()) { - if (info.isValid()) { - setExecutable({info, Executable::UseApplicationIcon}); + if (!info.isValid()) { + continue; } + + setExecutable({info, Executable::UseApplicationIcon}, flags); } const QFileInfo eppBin(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe"); @@ -137,12 +139,14 @@ void ExecutablesList::addFromPlugin(IPluginGame const *game) const auto args = QString("\"%1\"") .arg(QDir::toNativeSeparators(game->dataDirectory().absolutePath())); - setExecutable(Executable() + const auto exe = Executable() .title("Explore Virtual Folder") .binaryInfo(eppBin) .arguments(args) .workingDirectory(eppBin.absolutePath()) - .flags(Executable::UseApplicationIcon)); + .flags(Executable::UseApplicationIcon); + + setExecutable(exe, flags); } } @@ -188,28 +192,81 @@ bool ExecutablesList::titleExists(const QString &title) const return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); } -void ExecutablesList::setExecutable(const Executable &executable) +void ExecutablesList::setExecutable(const Executable &exe) +{ + setExecutable(exe, MergeExisting); +} + +void ExecutablesList::setExecutable(const Executable &exe, SetFlags flags) { - auto itor = find(executable.title()); + auto itor = find(exe.title()); + + if (itor != end()) { + if (flags == IgnoreExisting) { + return; + } + + if (flags == MoveExisting) { + const auto newTitle = makeNonConflictingTitle(exe.title()); + if (!newTitle) { + qCritical().nospace() + << "executable '" << exe.title() << "' was in the way but could " + << "not be renamed"; + + return; + } + + qWarning().nospace() + << "executable '" << itor->title() << "' was in the way and was " + << "renamed to '" << *newTitle << "'"; + + itor->title(*newTitle); + itor = end(); + } + } if (itor == m_Executables.end()) { - m_Executables.push_back(executable); + m_Executables.push_back(exe); } else { - itor->mergeFrom(executable); + itor->mergeFrom(exe); } } void ExecutablesList::remove(const QString &title) { - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->isCustom() && (iter->title() == title)) { - m_Executables.erase(iter); - break; + auto itor = find(title); + if (itor != m_Executables.end()) { + m_Executables.erase(itor); + } +} + +std::optional ExecutablesList::makeNonConflictingTitle( + const QString& prefix) +{ + const int max = 100; + + QString title = prefix; + + for (int i=1; i. #include "executableinfo.h" #include +#include #include #include @@ -37,14 +38,13 @@ class Executable public: enum Flag { - CustomExecutable = 0x01, ShowInToolbar = 0x02, UseApplicationIcon = 0x04 }; Q_DECLARE_FLAGS(Flags, Flag); - Executable() = default; + Executable(QString title={}); /** * @brief Executable from plugin @@ -65,7 +65,6 @@ public: Executable& workingDirectory(const QString& s); Executable& flags(Flags f); - bool isCustom() const; bool isShownOnToolbar() const; void setShownOnToolbar(bool state); bool usesOwnIcon() const; @@ -106,6 +105,8 @@ public: */ void load(const MOBase::IPluginGame* game, QSettings& settings); + void resetFromPlugin(MOBase::IPluginGame const *game); + /** * @brief writes the current list to the settings */ @@ -166,13 +167,28 @@ public: **/ void remove(const QString &title); + std::optional makeNonConflictingTitle(const QString& prefix); + private: + enum SetFlags + { + IgnoreExisting = 1, + MergeExisting, + MoveExisting + }; + std::vector m_Executables; /** * @brief add the executables preconfigured for this game **/ - void addFromPlugin(MOBase::IPluginGame const *game); + void addFromPlugin(MOBase::IPluginGame const *game, SetFlags flags); + + /** + * @brief add a new executable to the list + * @param executable + */ + void setExecutable(const Executable &exe, SetFlags flags); }; Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 15a6eb62..b5e9c320 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5513,8 +5513,7 @@ void MainWindow::addAsExecutable() .title(name) .binaryInfo(binaryInfo) .arguments(arguments) - .workingDirectory(targetInfo.absolutePath()) - .flags(Executable::CustomExecutable)); + .workingDirectory(targetInfo.absolutePath())); refreshExecutablesList(); } diff --git a/src/pch.h b/src/pch.h index e86c0da6..1d8df43a 100644 --- a/src/pch.h +++ b/src/pch.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include -- cgit v1.3.1 From 5ca4ea500439bdeee85ca2103374618f2608808d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 19:13:28 -0400 Subject: merged CustomOverwrites and ForcedLibraries, they were mostly identical some comments --- src/editexecutablesdialog.cpp | 202 ++++++++++-------------------------------- src/editexecutablesdialog.h | 131 ++++++++++++++++++++------- src/executableslist.h | 16 +++- src/profile.cpp | 4 +- src/profile.h | 4 +- 5 files changed, 165 insertions(+), 192 deletions(-) (limited to 'src') diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 882bb8b5..8929d207 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -46,11 +46,11 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) ui->splitter->setStretchFactor(0, 0); ui->splitter->setStretchFactor(1, 1); - m_customOverwrites.load(m_organizerCore.currentProfile(), m_executablesList); - m_forcedLibraries.load(m_organizerCore.currentProfile(), m_executablesList); + loadCustomOverwrites(); + loadForcedLibraries(); - fillList(); ui->mods->addItems(m_organizerCore.modList()->allMods()); + fillList(); setDirty(false); // some widgets need to do more than just save() and have their own handler @@ -65,6 +65,31 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, QWidget* parent) EditExecutablesDialog::~EditExecutablesDialog() = default; + +void EditExecutablesDialog::loadCustomOverwrites() +{ + const auto* p = m_organizerCore.currentProfile(); + + for (const auto& e : m_executablesList) { + const auto s = p->setting("custom_overwrites", e.title()).toString(); + + if (!s.isEmpty()) { + m_customOverwrites.set(e.title(), true, s); + } + } +} + +void EditExecutablesDialog::loadForcedLibraries() +{ + const auto* p = m_organizerCore.currentProfile(); + + for (const auto& e : m_executablesList) { + if (p->forcedLibrariesEnabled(e.title())) { + m_forcedLibraries.set(e.title(), true, p->determineForcedLibraries(e.title())); + } + } +} + ExecutablesList EditExecutablesDialog::getExecutablesList() const { ExecutablesList newList; @@ -88,12 +113,14 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const return newList; } -const CustomOverwrites& EditExecutablesDialog::getCustomOverwrites() const +const EditExecutablesDialog::CustomOverwrites& +EditExecutablesDialog::getCustomOverwrites() const { return m_customOverwrites; } -const ForcedLibraries& EditExecutablesDialog::getForcedLibraries() const +const EditExecutablesDialog::ForcedLibraries& +EditExecutablesDialog::getForcedLibraries() const { return m_forcedLibraries; } @@ -111,16 +138,16 @@ void EditExecutablesDialog::commitChanges() // set the new custom overwrites and forced libraries for (const auto& e : newExecutables) { - if (auto info=m_customOverwrites.find(e.title())) { - if (info && info->enabled) { - profile->storeSetting("custom_overwrites", e.title(), info->modName); + if (auto modName=m_customOverwrites.find(e.title())) { + if (modName && modName->enabled) { + profile->storeSetting("custom_overwrites", e.title(), modName->value); } } - if (auto info=m_forcedLibraries.find(e.title())) { - if (info && info->enabled && !info->list.empty()) { + if (auto libraryList=m_forcedLibraries.find(e.title())) { + if (libraryList && libraryList->enabled && !libraryList->value.empty()) { profile->setForcedLibrariesEnabled(e.title(), true); - profile->storeForcedLibraries(e.title(), info->list); + profile->storeForcedLibraries(e.title(), libraryList->value); } } } @@ -260,19 +287,19 @@ void EditExecutablesDialog::setEdits(const Executable& e) { int modIndex = -1; - const auto info = m_customOverwrites.find(e.title()); + const auto modName = m_customOverwrites.find(e.title()); - if (info && !info->modName.isEmpty()) { - modIndex = ui->mods->findText(info->modName); + if (modName && !modName->value.isEmpty()) { + modIndex = ui->mods->findText(modName->value); if (modIndex == -1) { qWarning().nospace() - << "executable '" << e.title() << "' uses mod '" << info->modName << "' " + << "executable '" << e.title() << "' uses mod '" << modName->value << "' " << "as a custom overwrite, but that mod doesn't exist"; } } - const bool hasCustomOverwrites = (info && info->enabled); + const bool hasCustomOverwrites = (modName && modName->enabled); ui->createFilesInMod->setChecked(hasCustomOverwrites); ui->mods->setEnabled(hasCustomOverwrites); @@ -280,8 +307,8 @@ void EditExecutablesDialog::setEdits(const Executable& e) } { - const auto info = m_forcedLibraries.find(e.title()); - const bool hasForcedLibraries = (info && info->enabled); + const auto libraryList = m_forcedLibraries.find(e.title()); + const bool hasForcedLibraries = (libraryList && libraryList->enabled); ui->forceLoadLibraries->setChecked(hasForcedLibraries); ui->configureLibraries->setEnabled(hasForcedLibraries); @@ -316,8 +343,7 @@ void EditExecutablesDialog::save() // custom overwrites if (ui->createFilesInMod->isChecked()) { - m_customOverwrites.setEnabled(e->title(), true); - m_customOverwrites.setMod(e->title(), ui->mods->currentText()); + m_customOverwrites.set(e->title(), true, ui->mods->currentText()); } else { m_customOverwrites.setEnabled(e->title(), false); } @@ -626,12 +652,12 @@ void EditExecutablesDialog::on_configureLibraries_clicked() ForcedLoadDialog dialog(m_organizerCore.managedGame(), this); - if (auto info=m_forcedLibraries.find(e->title())) { - dialog.setValues(info->list); + if (auto libraryList=m_forcedLibraries.find(e->title())) { + dialog.setValues(libraryList->value); } if (dialog.exec() == QDialog::Accepted) { - m_forcedLibraries.setList(e->title(), dialog.values()); + m_forcedLibraries.setValue(e->title(), dialog.values()); save(); } } @@ -670,133 +696,3 @@ void EditExecutablesDialog::setJarBinary(const QString& binaryName) save(); } - - -void CustomOverwrites::load(Profile* p, const ExecutablesList& exes) -{ - for (const auto& e : exes) { - const auto s = p->setting("custom_overwrites", e.title()).toString(); - - if (!s.isEmpty()) { - m_map[e.title()] = {true, s}; - } - } -} - -std::optional CustomOverwrites::find( - const QString& title) const -{ - auto itor = m_map.find(title); - if (itor == m_map.end()) { - return {}; - } - - return itor->second; -} - -void CustomOverwrites::setEnabled(const QString& title, bool b) -{ - auto itor = m_map.find(title); - - if (itor == m_map.end()) { - m_map[title] = {b, {}}; - } else { - itor->second.enabled = b; - } -} - -void CustomOverwrites::setMod(const QString& title, const QString& mod) -{ - auto itor = m_map.find(title); - - if (itor == m_map.end()) { - m_map[title] = {true, mod}; - } else { - itor->second.modName = mod; - } -} - -void CustomOverwrites::rename(const QString& oldTitle, const QString& newTitle) -{ - auto itor = m_map.find(oldTitle); - if (itor == m_map.end()) { - return; - } - - // copy to new title, erase old - m_map[newTitle] = itor->second; - m_map.erase(itor); -} - -void CustomOverwrites::remove(const QString& title) -{ - auto itor = m_map.find(title); - - if (itor != m_map.end()) { - m_map.erase(itor); - } -} - - -void ForcedLibraries::load(Profile* p, const ExecutablesList& exes) -{ - for (const auto& e : exes) { - if (p->forcedLibrariesEnabled(e.title())) { - m_map[e.title()] = {true, p->determineForcedLibraries(e.title())}; - } - } -} - -std::optional ForcedLibraries::find( - const QString& title) const -{ - auto itor = m_map.find(title); - if (itor == m_map.end()) { - return {}; - } - - return itor->second; -} - -void ForcedLibraries::setEnabled(const QString& title, bool b) -{ - auto itor = m_map.find(title); - - if (itor == m_map.end()) { - m_map[title] = {b, {}}; - } else { - itor->second.enabled = b; - } -} - -void ForcedLibraries::setList(const QString& title, const list_type& list) -{ - auto itor = m_map.find(title); - - if (itor == m_map.end()) { - m_map[title] = {true, list}; - } else { - itor->second.list = list; - } -} - -void ForcedLibraries::rename(const QString& oldTitle, const QString& newTitle) -{ - auto itor = m_map.find(oldTitle); - if (itor == m_map.end()) { - return; - } - - // copy to new title, erase old - m_map[newTitle] = itor->second; - m_map.erase(itor); -} - -void ForcedLibraries::remove(const QString& title) -{ - auto itor = m_map.find(title); - - if (itor != m_map.end()) { - m_map.erase(itor); - } -} diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 4a04ef42..9715489e 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -37,53 +37,102 @@ class ModList; class OrganizerCore; /** helper class to manage custom overwrites within the edit executables - * dialog + * dialog, stores a T and a bool in map indexed by a QString **/ -class CustomOverwrites +template +class ToggableMap { public: - struct Info + struct Value { bool enabled; - QString modName; + T value; + + Value(bool b, T&& v) + : enabled(b), value(std::forward(v)) + { + } }; - void load(Profile* p, const ExecutablesList& exes); - std::optional find(const QString& title) const; + /** + * returns the Value associated with the given title, or empty + **/ + std::optional find(const QString& title) const + { + auto itor = m_map.find(title); + if (itor == m_map.end()) { + return {}; + } - void setEnabled(const QString& title, bool b); - void setMod(const QString& title, const QString& mod); - void rename(const QString& oldTitle, const QString& newTitle); - void remove(const QString& title); + return itor->second; + } -private: - std::map m_map; -}; + /** + * sets the given value, adds it if not found + **/ + void set(QString title, bool b, T value) + { + m_map.insert_or_assign(std::move(title), Value(b, std::move(value))); + } + /** + * sets whether the given value is enabled, inserts it if not found + **/ + void setEnabled(const QString& title, bool b) + { + auto itor = m_map.find(title); -/** helper class to manage forced libraries within the edit executables dialog - **/ -class ForcedLibraries -{ -public: - using list_type = QList; + if (itor == m_map.end()) { + m_map.emplace(title, Value(b, {})); + } else { + itor->second.enabled = b; + } + } - struct Info + /** + * sets the given value, inserts it enabled if not found + **/ + void setValue(const QString& title, T value) { - bool enabled; - list_type list; - }; + auto itor = m_map.find(title); - void load(Profile* p, const ExecutablesList& exes); - std::optional find(const QString& title) const; + if (itor == m_map.end()) { + m_map.emplace(title, Value(true, std::move(value))); + } else { + itor->second.value = std::move(value); + } + } - void setEnabled(const QString& title, bool b); - void setList(const QString& title, const list_type& list); - void rename(const QString& oldTitle, const QString& newTitle); - void remove(const QString& title); + /** + * renames the given value, ignored if not found + **/ + void rename(const QString& oldTitle, QString newTitle) + { + auto itor = m_map.find(oldTitle); + if (itor == m_map.end()) { + return; + } + + // move to new title, erase old + m_map.emplace(std::move(newTitle), std::move(itor->second)); + m_map.erase(itor); + } + + /** + * removes the given value, ignored if not found + **/ + void remove(const QString& title) + { + auto itor = m_map.find(title); + if (itor == m_map.end()) { + return; + } + + m_map.erase(itor); + } private: - std::map m_map; + std::map m_map; }; @@ -95,10 +144,9 @@ class EditExecutablesDialog : public MOBase::TutorableDialog Q_OBJECT public: - /** - * @param executablesList current list of executables - * @param parent parent widget - **/ + using CustomOverwrites = ToggableMap; + using ForcedLibraries = ToggableMap>; + explicit EditExecutablesDialog(OrganizerCore& oc, QWidget* parent=nullptr); ~EditExecutablesDialog(); @@ -130,13 +178,28 @@ private slots: private: std::unique_ptr ui; OrganizerCore& m_organizerCore; + + // copy of the original executables, used to clear the current settings when + // committing changes const ExecutablesList m_originalExecutables; + + // current executable list ExecutablesList m_executablesList; + + // custom overwrites set in the dialog CustomOverwrites m_customOverwrites; + + // forced libraries set in the dialog ForcedLibraries m_forcedLibraries; + + // true when the change events being triggered are in response to loading + // the executable's data into the UI, not from a user change bool m_settingUI; + void loadCustomOverwrites(); + void loadForcedLibraries(); + QListWidgetItem* selectedItem(); Executable* selectedExe(); diff --git a/src/executableslist.h b/src/executableslist.h index 61582e71..61bf6734 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -102,9 +102,13 @@ public: /** * @brief initializes the list from the settings and the given plugin - */ + **/ void load(const MOBase::IPluginGame* game, QSettings& settings); + /** + * @brief re-adds all the executables from the plugin and renames existing + * executables that are in the way + **/ void resetFromPlugin(MOBase::IPluginGame const *game); /** @@ -167,18 +171,28 @@ public: **/ void remove(const QString &title); + /** + * returns a title that starts with the given prefix and does not clash with + * an existing executable, may fail + */ std::optional makeNonConflictingTitle(const QString& prefix); private: enum SetFlags { + // executables having the same name as existing ones are ignored IgnoreExisting = 1, + + // executables having the same name are merged MergeExisting, + + // an existing executable with the same name is renamed MoveExisting }; std::vector m_Executables; + /** * @brief add the executables preconfigured for this game **/ diff --git a/src/profile.cpp b/src/profile.cpp index 4ccaa641..ef387027 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -982,7 +982,7 @@ int Profile::getPriorityMinimum() const return m_ModIndexByPriority.begin()->first; } -bool Profile::forcedLibrariesEnabled(const QString &executable) +bool Profile::forcedLibrariesEnabled(const QString &executable) const { return setting("forced_libraries", executable + "/enabled", false).toBool(); } @@ -992,7 +992,7 @@ void Profile::setForcedLibrariesEnabled(const QString &executable, bool enabled) storeSetting("forced_libraries", executable + "/enabled", enabled); } -QList Profile::determineForcedLibraries(const QString &executable) +QList Profile::determineForcedLibraries(const QString &executable) const { QList results; diff --git a/src/profile.h b/src/profile.h index a7ba7e91..bc7964f8 100644 --- a/src/profile.h +++ b/src/profile.h @@ -330,9 +330,9 @@ public: int getPriorityMinimum() const; - bool forcedLibrariesEnabled(const QString &executable); + bool forcedLibrariesEnabled(const QString &executable) const; void setForcedLibrariesEnabled(const QString &executable, bool enabled); - QList determineForcedLibraries(const QString &executable); + QList determineForcedLibraries(const QString &executable) const; void storeForcedLibraries(const QString &executable, const QList &values); void removeForcedLibraries(const QString &executable); -- cgit v1.3.1 From d7dc13e3b9ac2932531d9518c9d303cd302e1539 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 21:28:40 -0400 Subject: fixes plugin executables having empty fields when upgrading from 2.2.0 --- src/executableslist.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++++----- src/executableslist.h | 21 +++++++++--- 2 files changed, 95 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/executableslist.cpp b/src/executableslist.cpp index daf200a6..077d2a93 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -69,6 +69,10 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) m_Executables.clear(); + // whether the executable list in the .ini is still using the old custom + // executables from 2.2.0, see upgradeFromCustom() + bool needsUpgrade = false; + int numCustomExecutables = settings.beginReadArray("customExecutables"); for (int i = 0; i < numCustomExecutables; ++i) { settings.setArrayIndex(i); @@ -79,6 +83,11 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) if (settings.value("ownicon", false).toBool()) flags |= Executable::UseApplicationIcon; + if (settings.contains("custom")) { + // the "custom" setting only exists in older versions + needsUpgrade = true; + } + setExecutable(Executable() .title(settings.value("title").toString()) .binaryInfo(settings.value("binary").toString()) @@ -91,6 +100,9 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, QSettings& settings) settings.endArray(); addFromPlugin(game, IgnoreExisting); + + if (needsUpgrade) + upgradeFromCustom(game); } void ExecutablesList::store(QSettings& settings) @@ -115,22 +127,19 @@ void ExecutablesList::store(QSettings& settings) settings.endArray(); } -void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game) -{ - qDebug("resetting plugin executables"); - addFromPlugin(game, MoveExisting); -} - -void ExecutablesList::addFromPlugin(IPluginGame const *game, SetFlags flags) +std::vector ExecutablesList::getPluginExecutables( + MOBase::IPluginGame const *game) const { Q_ASSERT(game != nullptr); + std::vector v; + for (const ExecutableInfo &info : game->executables()) { if (!info.isValid()) { continue; } - setExecutable({info, Executable::UseApplicationIcon}, flags); + v.push_back({info, Executable::UseApplicationIcon}); } const QFileInfo eppBin(QCoreApplication::applicationDirPath() + "/explorer++/Explorer++.exe"); @@ -146,6 +155,28 @@ void ExecutablesList::addFromPlugin(IPluginGame const *game, SetFlags flags) .workingDirectory(eppBin.absolutePath()) .flags(Executable::UseApplicationIcon); + v.push_back(exe); + } + + return v; +} + +void ExecutablesList::resetFromPlugin(MOBase::IPluginGame const *game) +{ + qDebug("resetting plugin executables"); + + Q_ASSERT(game != nullptr); + + for (const auto& exe : getPluginExecutables(game)) { + setExecutable(exe, MoveExisting); + } +} + +void ExecutablesList::addFromPlugin(IPluginGame const *game, SetFlags flags) +{ + Q_ASSERT(game != nullptr); + + for (const auto& exe : getPluginExecutables(game)) { setExecutable(exe, flags); } } @@ -261,6 +292,46 @@ std::optional ExecutablesList::makeNonConflictingTitle( return {}; } +void ExecutablesList::upgradeFromCustom(MOBase::IPluginGame const *game) +{ + qDebug() << "upgrading executables list"; + + Q_ASSERT(game != nullptr); + + // prior to 2.2.1, plugin executables were special in the sense that they + // did not store certain settings, like the binary info and working directory; + // those were filled in when MO started, but never saved + // + // this interferes with the new executables list, which is completely + // customizable, because plugin executables are only added to the list when + // they don't exist at all and are ignored otherwise, leaving some of the + // fields completely blank + // + // when the "custom" setting is found in the .ini file (see load()), it is + // assumed that the older scheme is still present; in that case, the plugin + // executables force their binary info and working directory into the existing + // executables one last time + // + // from that point on, plugin executables are ignored unless they're + // completely missing from the list, allowing users to customize them as they + // want + + for (const auto& exe : getPluginExecutables(game)) { + auto itor = find(exe.title()); + if (itor == end()){ + continue; + } + + if (!itor->binaryInfo().exists()) { + itor->binaryInfo(exe.binaryInfo()); + } + + if (itor->workingDirectory().isEmpty()) { + itor->workingDirectory(exe.workingDirectory()); + } + } +} + Executable::Executable(QString title) : m_title(title) diff --git a/src/executableslist.h b/src/executableslist.h index 61bf6734..2d1dd28e 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -194,15 +194,26 @@ private: /** - * @brief add the executables preconfigured for this game - **/ + * @brief add the executables preconfigured for this game + **/ void addFromPlugin(MOBase::IPluginGame const *game, SetFlags flags); /** - * @brief add a new executable to the list - * @param executable - */ + * @brief add a new executable to the list + * @param executable + */ void setExecutable(const Executable &exe, SetFlags flags); + + /** + * returns the executables provided by the game plugin + **/ + std::vector getPluginExecutables( + MOBase::IPluginGame const *game) const; + + /** + * called when MO is still using the old custom executables from 2.2.0 + **/ + void upgradeFromCustom(const MOBase::IPluginGame* game); }; Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) -- cgit v1.3.1 From 30ce0ba5ef5256f42b36d6f4194bf444941ad66d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 14:48:59 -0400 Subject: rebase to statusbar --- src/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b5e9c320..4ba5e6ad 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -740,8 +740,8 @@ void MainWindow::updatePinnedExecutables() QAction *exeAction = new QAction( iconForExecutable(exe.binaryInfo().filePath()), exe.title()); - exeAction->setObjectName(QString("custom__") + iter->m_Title); - exeAction->setStatusTip(iter->m_BinaryInfo.filePath()); + exeAction->setObjectName(QString("custom__") + exe.title()); + exeAction->setStatusTip(exe.binaryInfo().filePath()); if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { qDebug("failed to connect trigger?"); -- cgit v1.3.1 From 58fbdc1c22a75ca18ff9a4e1a8dc9e02b0c052c5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 15:08:45 -0400 Subject: removed 'L' from the language code: - it's incorrect, since language codes are unsigned shorts, and - it actually breaks the translation section and makes retrieving it through standard Windows API fail --- src/version.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index af6fd603..04259d7d 100644 --- a/src/version.rc +++ b/src/version.rc @@ -32,6 +32,6 @@ BEGIN BLOCK "VarFileInfo" BEGIN - VALUE "Translation", 0x0409L, 1200 + VALUE "Translation", 0x0409, 1200 END END -- cgit v1.3.1 From f883a14823bdc3ed853b23d425f73ff55068ec55 Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 19 Jun 2019 01:57:16 +0200 Subject: Improved downloads tab deletion and hiding warnings. --- src/downloadlistwidget.cpp | 22 +- src/organizer_en.ts | 2222 +++++++++++++++++++++++++------------------- 2 files changed, 1255 insertions(+), 989 deletions(-) (limited to 'src') diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index e6029270..be3adc5f 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -268,8 +268,8 @@ void DownloadListWidget::issueQueryInfoMd5() void DownloadListWidget::issueDelete() { - if (QMessageBox::question(nullptr, tr("Delete Files?"), - tr("This will permanently delete the selected download."), + if (QMessageBox::warning(nullptr, tr("Delete Files?"), + tr("This will permanently delete the selected download.\n\nAre you absolutely sure you want to proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(m_ContextRow, true); } @@ -323,8 +323,8 @@ void DownloadListWidget::issueResume() void DownloadListWidget::issueDeleteAll() { - if (QMessageBox::question(nullptr, tr("Delete Files?"), - tr("This will remove all finished downloads from this list and from disk."), + if (QMessageBox::warning(nullptr, tr("Delete Files?"), + tr("This will remove all finished downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-1, true); } @@ -332,8 +332,8 @@ void DownloadListWidget::issueDeleteAll() void DownloadListWidget::issueDeleteCompleted() { - if (QMessageBox::question(nullptr, tr("Delete Files?"), - tr("This will remove all installed downloads from this list and from disk."), + if (QMessageBox::warning(nullptr, tr("Delete Files?"), + tr("This will remove all installed downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-2, true); } @@ -341,8 +341,8 @@ void DownloadListWidget::issueDeleteCompleted() void DownloadListWidget::issueDeleteUninstalled() { - if (QMessageBox::question(nullptr, tr("Delete Files?"), - tr("This will remove all uninstalled downloads from this list and from disk."), + if (QMessageBox::warning(nullptr, tr("Delete Files?"), + tr("This will remove all uninstalled downloads from this list and from disk.\n\nAre you absolutely sure you want to proceed?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-3, true); } @@ -350,7 +350,7 @@ void DownloadListWidget::issueDeleteUninstalled() void DownloadListWidget::issueRemoveFromViewAll() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), + if (QMessageBox::question(nullptr, tr("Hide Files?"), tr("This will remove all finished downloads from this list (but NOT from disk)."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-1, false); @@ -359,7 +359,7 @@ void DownloadListWidget::issueRemoveFromViewAll() void DownloadListWidget::issueRemoveFromViewCompleted() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), + if (QMessageBox::question(nullptr, tr("Hide Files?"), tr("This will remove all installed downloads from this list (but NOT from disk)."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-2, false); @@ -368,7 +368,7 @@ void DownloadListWidget::issueRemoveFromViewCompleted() void DownloadListWidget::issueRemoveFromViewUninstalled() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), + if (QMessageBox::question(nullptr, tr("Hide Files?"), tr("This will remove all uninstalled downloads from this list (but NOT from disk)."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { emit removeDownload(-3, false); diff --git a/src/organizer_en.ts b/src/organizer_en.ts index e4a134a5..ae71eefa 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -96,7 +96,8 @@ - Jax, Nubbie (Swedish) + Jax (Swedish) + Jax, Nubbie (Swedish) @@ -111,7 +112,8 @@ - Other Supporters & Contributors + Other Supporters && Contributors + Other Supporters & Contributors @@ -534,29 +536,49 @@ p, li { white-space: pre-wrap; } - This will permanently delete the selected download. + This will permanently delete the selected download. + +Are you absolutely sure you want to proceed? + This will permanently delete the selected download. + +Are you absolutely sure you want to procede? - This will remove all finished downloads from this list and from disk. + This will remove all finished downloads from this list and from disk. + +Are you absolutely sure you want to proceed? + This will remove all finished downloads from this list and from disk. + +Are you absolutely sure you want to procede? - This will remove all installed downloads from this list and from disk. + This will remove all installed downloads from this list and from disk. + +Are you absolutely sure you want to proceed? + This will remove all installed downloads from this list and from disk. + +Are you absolutely sure you want to procede? - This will remove all uninstalled downloads from this list and from disk. + This will remove all uninstalled downloads from this list and from disk. + +Are you absolutely sure you want to proceed? + This will remove all uninstalled downloads from this list and from disk. + +Are you absolutely sure you want to procede? - Are you sure? + Hide Files? @@ -726,174 +748,174 @@ File %3: %4 - + get pending: invalid download index %1 - + get path: invalid download index %1 - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + display name: invalid download index %1 - + file name: invalid download index %1 - + file time: invalid download index %1 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -904,216 +926,233 @@ Canceling download "%2"... EditExecutablesDialog - + Modify Executables - + + Executables + + + + + + + + Move the executable up in the list + + + + + + + + Move the executable down in the list + + + + + + + Adds the executables provided by the game plugin and moves any existing executables out of the way + + + + + Reset + + + + List of configured executables - + This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. - + Title - - + + Name of the executable. This is only for display purposes. - + Binary - - + + Binary to run - + Browse filesystem - + Browse filesystem for the executable to run. - - + + ... - + Start in - + Arguments - - + + Arguments to pass to the application - + Allow the Steam AppID to be used for this executable to be changed. - + Allow the Steam AppID to be used for this executable to be changed. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game. Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured. - + Overwrite Steam AppID - + Steam AppID to use for this executable that differs from the games AppID. - + Steam AppID to use for this executable that differs from the games AppID. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850). Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured. - + If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod. - + Create Files in Mod instead of Overwrite (*) - + If this is enabled, the configured libraries will be automatically loaded when this executable is launched. - + Force Load Libraries (*) - + Configure Libraries - + Use Application's Icon for shortcuts - - (*) This setting is profile-specific + + (*) Profile Specific - - + + + Add an executable - - + Add - - + + + Remove the selected executable - + Remove - - Close - - - - - Select a binary - - - - - Executable (%1) + + Reset plugin executables - - Java (32-bit) required + + This will restore all the executables provided by the game plugin. If there are existing executables with the same names, they will be automatically renamed and left unchanged. - - MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. + + New Executable - - Select a directory - - - - - Confirm + + Select a binary - - Really remove "%1" from executables? + + Executable (%1) - - Modify + + Java (32-bit) required - - - Save Changes? + + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - - - You made changes to the current executable, do you want to save them? + + Select a directory @@ -1453,12 +1492,14 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOApplication - an error occurred: %1 + an error occured: %1 + an error occurred: %1 - an error occurred + an error occured + an error occurred
    @@ -1509,150 +1550,125 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MainWindow - - + + Categories - + Clear - + If checked, only mods that match all selected categories are displayed. - + And - + If checked, all mods that match at least one of the selected categories are displayed. - + Or - + Profile - + Pick a module collection - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> - - - - + Open list options... - + Refresh list. This is usually not necessary unless you modified data outside the program. - + Show Open Folders menu... - - + + Restore Backup... - - - + + + Create Backup - - + + Active: - + This provides statistics about the mod list. The total number of active mod is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available mods. - + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - - - - + + + + Filter - - 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> - - - - - API: Q: 0 | D: 0 | H: 0 - - - - + Clear all Filters - + No groups - + Nexus IDs - + Pick a program to run. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1662,12 +1678,12 @@ p, li { white-space: pre-wrap; } - + Run program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1676,17 +1692,17 @@ p, li { white-space: pre-wrap; } - + Run - + Create a shortcut in your start menu or on the desktop to the specified program - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1695,32 +1711,32 @@ p, li { white-space: pre-wrap; } - + Shortcut - + Plugins - + Sort - + This provides statistics about the plugin list. The total number of active plugins is normally displayed. Other statistics may be accessed with the tooltip of this counter. - + List of available esp/esm files - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1729,27 +1745,27 @@ p, li { white-space: pre-wrap; } - + Archives - + <html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! @@ -1757,72 +1773,72 @@ p, li { white-space: pre-wrap; } - + Data - + refresh data-directory overview - + Refresh the overview. This may take a moment. - - - - + + + + Refresh - + This is an overview of your data directory as visible to the game (and tools). - + File - + Mod - - + + Filters the above list so that only conflicts are displayed. - + Show only conflicts - - + + Filters the above list so that files from archives are not shown - + Show files from Archives - + Saves - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -1833,1097 +1849,1218 @@ p, li { white-space: pre-wrap; } - + Downloads - + Refresh downloads view - + This is a list of mods you downloaded from Nexus. Double click one to install it. You can also drag an archive into here. - + Show Hidden - - Tool Bar + + &Settings... - - Install Mod + + + Visit &Nexus - - Install &Mod + + + Visit the Nexus website in your browser for more mods - - Install a new mod from an archive + + + &Update Mod Organizer - - Ctrl+M + + &Notifications... - - Profiles + + + Open the notifications dialog - - &Profiles + + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - Configure Profiles + + + Show help options - - Ctrl+P + + + &Endorse ModOrganizer - - Executables + + + Copy &Log + + + + + + Copy log to clipboard + + + + + &Change Game... + + + + + &Change Game + + + + + + E&xit + + + + + + Exits Mod Organizer + + + + + M&ain Toolbar + + + + + &Small Icons + + + + + Lar&ge Icons - + + &Icons Only + + + + + &Text Only + + + + + I&cons and Text + + + + + M&edium Icons + + + + + &Menu + + + + + St&atus bar + + + + + Install &Mod + + + + + + Install a new mod from an archive + + + + + Ctrl+M + + + + + &Profiles + + + + + Ctrl+P + + + + &Executables - + + Configure the executables that can be started through Mod Organizer - + Ctrl+E - - + Tools - + + + &Tools - - Ctrl+I + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - - Settings + + Main ToolBar - - &Settings + + &File - - Configure settings and workarounds + + + + &Help - - Ctrl+S + + &Edit - - Nexus + + &View - - Search nexus network for more mods + + &Toolbars - - Ctrl+N + + &Run - - - Update + + Install &Mod... - - Mod Organizer is up-to-date + + &Profiles... - - - No Notifications + + + Configure profiles - - This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. + + &Executables... - - - Help + + Ctrl+I - - Ctrl+H + + &Settings - - Endorse MO + + + Configure settings and workarounds - - - Endorse Mod Organizer + + Ctrl+S - - Copy Log to Clipboard + + Ctrl+N - - Change Game + + + Mod Organizer is up-to-date - - Open the Instance selection dialog to manage a different Game + + Ctrl+H - - Toolbar + + + + Endorse Mod Organizer - - Desktop + + + Open the Instance selection dialog to manage a different Game + Desktop + + + + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - - Notifications + + Toolbar and Menu - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + You need to be logged in with Nexus to resume a download - - - - + + + + You need to be logged in with Nexus to endorse - - + + Endorsing multiple mods will take a while. Please wait... - - + + Unendorsing multiple mods will take a while. Please wait... - - - + + + You need to be logged in with Nexus to track - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this Mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + Web page for this mod is unknown - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit web page - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -2931,12 +3068,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -2944,22 +3081,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -2967,373 +3104,348 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - - Select binary + + Remove '%1' from the toolbar - - Binary + + Errors occured - + + Backup of modlist created + + + + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - - file not found: %1 - - - - - failed to generate preview for %1 - - - - - Sorry, can't preview anything. This function currently does not support extracting from bsas. - - - - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - - Remove - - - - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - - Errors occurred - - - - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - - Backup of mod list created - - - - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3508,7 +3620,8 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod whether the tweaks are really optional. + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod whether the tweaks are really optional. @@ -3582,7 +3695,8 @@ Most mods do not have optional esps, so chances are good you are looking at an e - These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. + These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. @@ -3596,88 +3710,149 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + + General + + + + The following conflicted files are provided by this mod - - - + + + + File - + Overwritten Mods - + The following conflicted files are provided by other mods - + Providing Mod - + The following files have no conflicts - + + Advanced + + + + + Overwrites + + + + + Overwritten by + + + + + Whether files that have no conflicts should be visible in the list + + + + + Show files that have no conflicts + + + + + Shows all mods overwriting or being overwritten by this mod + + + + + Show all conflicting mods + + + + + Shows only the nearest conflicting mods, in order of priority + + + + + Show nearest conflicting mod + + + + + Filter + + + + Categories - + Primary Category - + Nexus Info - + Mod ID - + Mod ID for this mod on Nexus. - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer2 on Nexus. Why not go there now and endorse us?</p></body></html> - + + Web page URL (only used if invalid NexusID) : + + + + Source Game - + Source game for this mod. - + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3686,76 +3861,71 @@ p, li { white-space: pre-wrap; } - + Version - + Refresh - + Refresh all information from Nexus. - + Description - + about:blank - + Endorse - - Web page URL (only used if invalid Nexus ID) : - - - - + Notes - - - + + + Enter comments about the mod here. These are displayed in the notes column of the mod list. - - - + + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - + Filetree - + Open Mod in Explorer - + A directory view of this mod - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -3765,260 +3935,236 @@ p, li { white-space: pre-wrap; } - + Previous - + Next - + Close - + &Delete - + &Rename - + &Hide - + &Unhide - + &Open - + &New Folder - - + + &Preview + + + + + Save changes? - - + + Save changes to "%1"? - + File Exists - + A file with that name exists, please enter a new one - + failed to move file - + failed to create directory "optional" - + Info requested, please wait - + Main - + Update - + Optional - + Old - + Miscellaneous - + Deleted - + Unknown - + Current Version: %1 - + No update available - + <div style="text-align: center;"><h1>Uh oh!</h1><p>Sorry, there is no description available for this mod. :(</p></div> - + <a href="%1">Visit on Nexus</a> - + Failed to delete %1 - - - - + + + + Confirm - - - Are you sure you want to delete "%1"? - - - - - - Are you sure you want to delete the selected files? - - - - - - New Folder - - - - - Failed to create "%1" - - - - - Select binary + + + Are sure you want to delete "%1"? - - Binary + + + Are sure you want to delete the selected files? - - file not found: %1 + + Unhide - - failed to generate preview for %1 - - - - - Sorry + + + New Folder - - Sorry, can't preview anything. This function currently does not support extracting from bsas. + + Failed to create "%1" - + Hide - - Un-Hide + + Open/Execute - - - - Open/Execute + + Preview - - - - Preview + + Go to... - + Name - + Please enter a name - - + + Error - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -4424,12 +4570,12 @@ p, li { white-space: pre-wrap; } MyFileSystemModel - + Overwrites - + not implemented @@ -4443,27 +4589,27 @@ p, li { white-space: pre-wrap; } - + There was a timeout during the request - + Unknown error - + Validation failed, please reauthenticate in the Settings -> Nexus tab: %1 - + Could not parse response. Invalid JSON. - + Unknown error. @@ -4479,267 +4625,340 @@ p, li { white-space: pre-wrap; } NexusInterface - + Failed to guess mod id for "%1", please pick the correct one - + You must authorize MO2 in Settings -> Nexus to use the Nexus API. - + You've exceeded the Nexus API rate limit and requests are now being throttled. Your next batch of requests will be available in approximately %1 minutes and %2 seconds. - + Aborting download: Either you clicked on a premium-only link and your account is not premium, or the download link was generated by a different account than the one stored in Mod Organizer. - + empty response - + invalid response - OrganizerCore + NexusManualKeyDialog - - - Failed to write settings + + Manual Nexus API Key - - An error occurred trying to update MO settings to %1: %2 + + 1. Get your personal API key - - File is write protected + + Open Browser - - Invalid file format (probably a bug) + + 2. Enter your personal API key - - Unknown error %1 + + Paste - - An error occurred trying to write back MO settings to %1: %2 + + Clear - - + + Enter API key here + + + + + <b>Sharing this key with anyone could get your Nexus account banned. You can always revoke this key from your account on the Nexus website.</b> + + + + + OrganizerCore + + + + Failed to write settings + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + Download started - + Download failed - - - - + + + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + The mod was not installed completely. - + Executable not found: %1 - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Steam: Access Denied - - MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely necessary. + + An error occured trying to update MO settings to %1: %2 + + + + + An error occured trying to write back MO settings to %1: %2 + + + + + file not found: %1 + + + + + failed to generate preview for %1 + + + + + Sorry + + + + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + + + + + File '%1' not found. + + + + + Failed to generate preview for %1 + + + + + MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely neccessary. Restart MO as administrator? - + Error - + Windows Event Log Error - + The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. Continue launching %1? - + Blacklisted Executable - + The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. Continue launching %1? - + No profile set - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -4762,63 +4981,63 @@ Continue? - + &Delete - + &Rename - + &Open - + &New Folder - + mod not found: %1 - + Failed to delete "%1" - - - - + + + + Confirm - - - Are you sure you want to delete "%1"? + + + Are sure you want to delete "%1"? - - - Are you sure you want to delete the selected files? + + + Are sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" @@ -5021,16 +5240,21 @@ p, li { white-space: pre-wrap; } - - + + Fix - + No guided fix + + + (There are no notifications) + + Profile @@ -5390,7 +5614,7 @@ p, li { white-space: pre-wrap; } - + Error @@ -5415,14 +5639,14 @@ p, li { white-space: pre-wrap; } - - + + failed to create directory "%1" - - + + failed to copy "%1" to "%2" @@ -5502,7 +5726,7 @@ p, li { white-space: pre-wrap; } - + invalid 7-zip32.dll: %1 @@ -5528,7 +5752,8 @@ p, li { white-space: pre-wrap; } - Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untouched. + Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched. + Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untouched. @@ -5712,7 +5937,7 @@ If the folder was still in use, restart MO and try again. - + Failed to create "%1". Your user account probably lacks permission. @@ -5776,34 +6001,34 @@ If the folder was still in use, restart MO and try again. - - + + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5839,20 +6064,30 @@ If the folder was still in use, restart MO and try again. - + Failed to start "%1" - + Waiting - + Please press OK once you're logged into steam. + + + Select binary + + + + + Binary + + failed to initialize plugin %1: %2 @@ -5885,12 +6120,12 @@ If the folder was still in use, restart MO and try again. - + Script Extender - + Proxy DLL @@ -5907,12 +6142,19 @@ If the folder was still in use, restart MO and try again. This process requires elevation to run. -This is a potential security risk so I highly advise you to investigate if +This is a potential security risk so I highly advice you to investigate if "%1" can be installed to work without elevation. Restart Mod Organizer as an elevated process? You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually. + This process requires elevation to run. +This is a potential security risk so I highly advise you to investigate if +"%1" +can be installed to work without elevation. + +Restart Mod Organizer as an elevated process? +You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually. @@ -5921,6 +6163,16 @@ You will be asked if you want to allow helper.exe to make changes to the system. failed to spawn "%1": %2 + + + This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. + + + + + Loading... + + QueryOverwriteDialog @@ -6032,63 +6284,63 @@ You will be asked if you want to allow helper.exe to make changes to the system. SelfUpdater - + archive.dll not loaded: "%1" - + New update available (%1) - + Do you want to install update? All your mods and setup will be left untouched. Select Show Details option to see the full change-log. - + Install - + Download failed - + Failed to find correct download, please try again later. - + Update - + Download in progress - + Download failed: %1 - + Failed to install update: %1 - - Failed to start %1: %2 + + Failed to start %1 - + Error @@ -6096,45 +6348,39 @@ Select Show Details option to see the full change-log. Settings - + Failed - - Sorry, failed to start the helper application - - - - - + + attempt to store setting for unknown plugin "%1" - - + Error - - Failed to retrieve a Nexus API key! Please try again. A browser window should open asking you to authorize. + + Failed to start the helper application - + Restart Mod Organizer? - + In order to finish configuration changes, MO must be restarted. Restart it now? - + Failed to create "%1", you may not have the necessary permission. path remains unchanged. @@ -6377,7 +6623,8 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - Important: All directories have to be writable! + Important: All directories have to be writeable! + Important: All directories have to be writable! @@ -6406,158 +6653,168 @@ p, li { white-space: pre-wrap; } - + + Manually enter the API key and try to login + + + + + Enter API Key Manually + + + + Clear the stored Nexus API key and force reauthorization. - - Revoke Nexus Authorization + + Disconnect from Nexus - + Remove cache and cookies. - + Clear Cache - + Disable automatic internet features - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use HTTP Proxy (Uses System Settings) - + Endorsement Integration - - + + <html><head/><body><p>By default, a counter is displayed under the mod list. This informs the user of their remaining API requests. The Nexus API becomes unusable once these API requests run out. Checking this option will hide that counter.</p></body></html> - + Hide API Request Counter - + Associate with "Download with manager" links - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Steam - + Username - + Password - + If you save your steam user ID and password here, they will be used when logging into steam. Note, however, your password will be stored unencrypted, so make sure your computer is secure. - + Plugins - + Author: - + Version: - + Description: - + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + Workarounds - + Steam App ID - + The Steam AppID for your game - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } @@ -6573,17 +6830,17 @@ p, li { white-space: pre-wrap; } - + Load Mechanism - + Select loading mechanism. See help for details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. @@ -6594,28 +6851,28 @@ If you use the Steam version of Oblivion the default will NOT work. In this case - + Enforces that inactive ESPs and ESMs are never loaded. - + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + Hide inactive ESPs/ESMs - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. @@ -6623,66 +6880,66 @@ If you disable this feature, MO will only display official DLCs this way. Please - + Display mods installed outside MO - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - - + + Disable this to prevent the GUI from being locked when running an executable. This may result in abnormal behavior. - + Lock GUI when running executable - + Enable parsing of Archives. This is an Experimental Feature. Has negative effects on performance and known incorrectness. - + <html><head/><body><p>By default, MO will parse archive files (BSA, BA2) to calculate conflicts between the contents of the archive files and other loose files. This process has a noticeable cost in performance.</p><p>This feature should not be confused with the archive management feature offered by MO1. MO2 will only show conflicts with archives and will NOT load them into the game or program.</p><p>If you disable this feature, MO will only display conflicts between loose files.</p></body></html> - + Enable parsing of Archives (Experimental Feature) - - + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + Back-date BSAs - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended @@ -6691,48 +6948,48 @@ programs you are intentionally running. - + Add executables to the blacklist to prevent them from accessing the virtual file system. This is useful to prevent unintended programs from being hooked. Hooking unintended programs may affect the execution of these programs or the programs you are intentionally running. - + Configure Executables Blacklist - - + + Resets the window geometries for all windows. This can be useful if a window becomes too small or too large, if a column becomes too thin or too wide, and in similar situations. - + Reset Window Geometries - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Diagnostics - + Max Dumps To Keep - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. - + Maximum number of crash dumps to keep on disk. Use 0 for unlimited. Set "Crash Dumps" above to None to disable crash dump collection. @@ -6740,12 +6997,12 @@ programs you are intentionally running. - + Hint: right click link and copy link location - + Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a> and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders. @@ -6755,17 +7012,17 @@ programs you are intentionally running. - + Crash Dumps - + Decides which type of crash dumps are collected when injected processes crash. - + Decides which type of crash dumps are collected when injected processes crash. "None" Disables the generation of crash dumps by MO. @@ -6776,80 +7033,81 @@ programs you are intentionally running. - + None - + Mini (recommended) - + Data - + Full - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Debug - + Info (recommended) - + Warning - + + Error - + Confirm - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? - + Executables Blacklist - + Enter one executable per line to be blacklisted from the virtual file system. Mods and other virtualized files will not be visible to these executables and any executables launched by them. @@ -6860,50 +7118,55 @@ Example: - + Select base directory - + Select download directory - + Select mod directory - + Select cache directory - + Select profiles directory - + Select overwrite directory - + Select game executable - + Confirm? - + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + + + Failed to retrieve a Nexus API key! Please try again. A browser window should open asking you to authorize. + + SimpleInstallDialog @@ -7191,7 +7454,8 @@ On Windows XP: - <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwritten by another. You could as well disable it. + <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwrtten by another. You could as well disable it. + <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwritten by another. You could as well disable it. @@ -7397,7 +7661,8 @@ There IS a notification now but you may want to hold off on clearing it until af - A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the individual mod. To do so, right-click the mod and select "Information". + A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the indiviual mod. To do so, right-click the mod and select "Information". + A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the individual mod. To do so, right-click the mod and select "Information". @@ -7431,7 +7696,8 @@ It's important you always start the game from inside MO, otherwise the mods - We may revisit this screen in later tutorials. + We may re-visit this screen in later tutorials. + We may revisit this screen in later tutorials. -- cgit v1.3.1 From f74c3419543058e6a97943f421bb74261ae1b10c Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 19 Jun 2019 15:31:52 +0200 Subject: * Made the loadingbar a little bigger and more centered in the statusbar. * Changed themes to remove the ugly separator line between widgets on the statusbar. --- src/organizer_en.ts | 4 ++-- src/statusbar.cpp | 15 +++++++++++++-- src/stylesheets/Night Eyes.qss | 1 + src/stylesheets/Paper Automata.qss | 2 ++ src/stylesheets/Paper Dark by 6788.qss | 2 ++ src/stylesheets/Paper Light by 6788.qss | 2 ++ src/stylesheets/Parchment v1.1 by Bob.qss | 2 ++ src/stylesheets/Transparent-Style-101-Green.qss | 3 +++ src/stylesheets/Transparent-Style-BOS.qss | 3 +++ src/stylesheets/Transparent-Style-Skyrim.qss | 3 +++ src/stylesheets/dark.qss | 2 ++ src/stylesheets/dracula.qss | 2 ++ src/stylesheets/skyrim.qss | 2 ++ src/stylesheets/vs15 Dark-Green.qss | 2 ++ src/stylesheets/vs15 Dark-Orange.qss | 2 ++ src/stylesheets/vs15 Dark-Purple.qss | 2 ++ src/stylesheets/vs15 Dark-Red.qss | 2 ++ src/stylesheets/vs15 Dark-Yellow.qss | 2 ++ src/stylesheets/vs15 Dark.qss | 2 ++ 19 files changed, 51 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index ae71eefa..283f2d3c 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -6164,12 +6164,12 @@ You will be asked if you want to allow helper.exe to make changes to the system. - + This tracks the number of queued Nexus API requests, as well as the remaining daily and hourly requests. The Nexus API limits you to a pool of requests per day and requests per hour. It is dynamically updated every time a request is completed. If you run out of requests, you will be unable to queue downloads, check updates, parse mod info, or even log in. Both pools must be consumed before this happens. - + Loading... diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 01449202..8ad5bea1 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -9,15 +9,26 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : m_update(new StatusBarAction(ui->actionUpdate)), m_api(new QLabel) { + QWidget* spacer1 = new QWidget; + spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + spacer1->setHidden(true); + spacer1->setVisible(true); + m_bar->addPermanentWidget(spacer1, 0); m_bar->addPermanentWidget(m_progress); + QWidget* spacer2 = new QWidget; + spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + spacer2->setHidden(true); + spacer2->setVisible(true); + m_bar->addPermanentWidget(spacer2,0); m_bar->addPermanentWidget(m_notifications); m_bar->addPermanentWidget(m_update); m_bar->addPermanentWidget(m_api); + m_progress->setTextVisible(true); m_progress->setRange(0, 100); - m_progress->setMaximumWidth(150); - m_progress->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + m_progress->setMaximumWidth(300); + m_progress->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_update->set(false); m_notifications->set(false); diff --git a/src/stylesheets/Night Eyes.qss b/src/stylesheets/Night Eyes.qss index f2f193c4..142acb6a 100644 --- a/src/stylesheets/Night Eyes.qss +++ b/src/stylesheets/Night Eyes.qss @@ -481,6 +481,7 @@ QToolTip border: 0px; } +QStatusBar::item {border: None;} /* Progress Bars (Downloads) -------------------------------------------------- */ diff --git a/src/stylesheets/Paper Automata.qss b/src/stylesheets/Paper Automata.qss index c9830331..7c0a604e 100644 --- a/src/stylesheets/Paper Automata.qss +++ b/src/stylesheets/Paper Automata.qss @@ -681,6 +681,8 @@ QToolTip { border: 2px solid #CDC8B0; } +QStatusBar::item {border: None;} + /* Progress Bars (Downloads) */ QProgressBar { diff --git a/src/stylesheets/Paper Dark by 6788.qss b/src/stylesheets/Paper Dark by 6788.qss index cde25733..77086c15 100644 --- a/src/stylesheets/Paper Dark by 6788.qss +++ b/src/stylesheets/Paper Dark by 6788.qss @@ -683,6 +683,8 @@ QToolTip { border-radius: 6px; } +QStatusBar::item {border: None;} + /* Progress Bars (Downloads) */ QProgressBar { diff --git a/src/stylesheets/Paper Light by 6788.qss b/src/stylesheets/Paper Light by 6788.qss index 54af277a..2b08bcd1 100644 --- a/src/stylesheets/Paper Light by 6788.qss +++ b/src/stylesheets/Paper Light by 6788.qss @@ -685,6 +685,8 @@ QToolTip { border-radius: 6px; } +QStatusBar::item {border: None;} + /* Progress Bars (Downloads) */ QProgressBar { diff --git a/src/stylesheets/Parchment v1.1 by Bob.qss b/src/stylesheets/Parchment v1.1 by Bob.qss index 45c1d57b..61c2f84d 100644 --- a/src/stylesheets/Parchment v1.1 by Bob.qss +++ b/src/stylesheets/Parchment v1.1 by Bob.qss @@ -491,6 +491,8 @@ QToolTip { border: 0px; } +QStatusBar::item {border: None;} + /* Progress Bars (Downloads) */ QProgressBar { diff --git a/src/stylesheets/Transparent-Style-101-Green.qss b/src/stylesheets/Transparent-Style-101-Green.qss index 7dbf6157..a4ed2623 100644 --- a/src/stylesheets/Transparent-Style-101-Green.qss +++ b/src/stylesheets/Transparent-Style-101-Green.qss @@ -461,6 +461,9 @@ background-color:#121212; color:#C0C0C0; } + +QStatusBar::item {border: None;} + /* **************************** */ /* Handles Web, Nexus info tab */ /* **************************** */ diff --git a/src/stylesheets/Transparent-Style-BOS.qss b/src/stylesheets/Transparent-Style-BOS.qss index 2c1eb3de..efad0859 100644 --- a/src/stylesheets/Transparent-Style-BOS.qss +++ b/src/stylesheets/Transparent-Style-BOS.qss @@ -673,6 +673,9 @@ QAbstractSpinBox::down-arrow{ border-width:1px; border-style:solid; } + +QStatusBar::item {border: None;} + /* Font size */ QLabel,QMenu,QPushButton,QAbstractSpinBox,QGroupBox,QCheckBox,QRadioButton{ color: #cccccc; diff --git a/src/stylesheets/Transparent-Style-Skyrim.qss b/src/stylesheets/Transparent-Style-Skyrim.qss index e95dcfc5..89e36c74 100644 --- a/src/stylesheets/Transparent-Style-Skyrim.qss +++ b/src/stylesheets/Transparent-Style-Skyrim.qss @@ -644,6 +644,9 @@ QAbstractSpinBox::down-arrow{ border-width:1px; border-style:solid; } + +QStatusBar::item {border: None;} + /* **************************** */ /* Handles Web, Nexus info tab */ /* **************************** */ diff --git a/src/stylesheets/dark.qss b/src/stylesheets/dark.qss index 1ad534a1..22cd598c 100644 --- a/src/stylesheets/dark.qss +++ b/src/stylesheets/dark.qss @@ -252,6 +252,8 @@ QMenuBar::item:selected { border-color: #3EA0CA; } +QStatusBar::item {border: None;} + QProgressBar { border: 2px solid grey; diff --git a/src/stylesheets/dracula.qss b/src/stylesheets/dracula.qss index ac0119b0..2a7fbf9e 100644 --- a/src/stylesheets/dracula.qss +++ b/src/stylesheets/dracula.qss @@ -418,6 +418,8 @@ DownloadListWidget::item:selected { padding: 0px; } +QStatusBar::item {border: None;} + QProgressBar { border: 2px solid grey; diff --git a/src/stylesheets/skyrim.qss b/src/stylesheets/skyrim.qss index d69fa173..2da5154d 100644 --- a/src/stylesheets/skyrim.qss +++ b/src/stylesheets/skyrim.qss @@ -537,6 +537,8 @@ SaveGameInfoWidget { background-color: #121212; color: #C0C0C0; } +QStatusBar::item {border: None;} + /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { background-color: transparent; diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 5edcab9c..9c57d96e 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -590,6 +590,8 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } +QStatusBar::item {border: None;} + /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { background-color: #E6E6E6; diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index 5d3bcd94..bf7647b2 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -591,6 +591,8 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } + QStatusBar::item {border: None;} + /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { background-color: #E6E6E6; diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index 0ace4f23..1c9cb2b8 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -591,6 +591,8 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } + QStatusBar::item {border: None;} + /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { background-color: #E6E6E6; diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 3a0a645c..73a90b15 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -591,6 +591,8 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } + QStatusBar::item {border: None;} + /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { background-color: #E6E6E6; diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index b833c37e..1d43091d 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -591,6 +591,8 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } + QStatusBar::item {border: None;} + /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { background-color: #E6E6E6; diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index 9a571ab9..dbeaaf28 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -590,6 +590,8 @@ SaveGameInfoWidget { border-width: 1px; padding: 2px; } +QStatusBar::item {border: None;} + /* Progress Bars (Downloads) #QProgressBar */ QProgressBar { background-color: #E6E6E6; -- cgit v1.3.1 From 6cef9e5a7efc12e0686188b91da6a12595c92a80 Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 19 Jun 2019 16:16:10 +0200 Subject: Removed old debugline. --- src/spawn.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/spawn.cpp b/src/spawn.cpp index e1de5c0f..d7d5efc2 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -94,7 +94,6 @@ static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, PROCESS_INFORMATION pi; BOOL success = FALSE; if (hooked) { - qDebug() << "Creating process hooked: <" << QString::fromWCharArray(commandLine, static_cast(length)) <<">"; success = ::CreateProcessHooked(nullptr, commandLine, nullptr, nullptr, // no special process or thread attributes -- cgit v1.3.1 From 45ddf061c4a84db6a1b1e1c413cefc925185a08c Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 19 Jun 2019 16:43:31 +0200 Subject: Disabled toolbar executable shortcut action after click to avoid starting it twice with a doubleclick. --- src/mainwindow.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4ba5e6ad..1d737541 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1519,6 +1519,7 @@ void MainWindow::startExeAction() return; } + action->setEnabled(false); const Executable& exe = *itor; auto& profile = *m_OrganizerCore.currentProfile(); @@ -1537,6 +1538,8 @@ void MainWindow::startExeAction() exe.steamAppID(), customOverwrite, forcedLibraries); + action->setEnabled(true); + } -- cgit v1.3.1 From 5276c3c2443f7b95adafe9eefcd23c5d3dd313a6 Mon Sep 17 00:00:00 2001 From: Al Date: Thu, 20 Jun 2019 00:40:01 +0200 Subject: * Changed export to CSV to sort by priority. * Made status a default choice. * Changed status column to +/- instead of Enabled/Disabled. --- src/mainwindow.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1d737541..e383006b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -4717,6 +4717,7 @@ void MainWindow::exportModListCSV() mod_Name->setChecked(true); QCheckBox *mod_Note = new QCheckBox(tr("Notes_column")); QCheckBox *mod_Status = new QCheckBox(tr("Mod_Status")); + mod_Status->setChecked(true); QCheckBox *primary_Category = new QCheckBox(tr("Primary_Category")); QCheckBox *nexus_ID = new QCheckBox(tr("Nexus_ID")); QCheckBox *mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL")); @@ -4764,10 +4765,10 @@ void MainWindow::exportModListCSV() std::vector > fields; if (mod_Priority->isChecked()) fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); + if (mod_Status->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); if (mod_Name->isChecked()) fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); - if (mod_Status->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); if (mod_Note->isChecked()) fields.push_back(std::make_pair(QString("#Note"), CSVBuilder::TYPE_STRING)); if (primary_Category->isChecked()) @@ -4787,9 +4788,10 @@ void MainWindow::exportModListCSV() builder.writeHeader(); - for (unsigned int i = 0; i < numMods; ++i) { - ModInfo::Ptr info = ModInfo::getByIndex(i); - bool enabled = m_OrganizerCore.currentProfile()->modEnabled(i); + auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); + for (auto& iter : indexesByPriority) { + ModInfo::Ptr info = ModInfo::getByIndex(iter.second); + bool enabled = m_OrganizerCore.currentProfile()->modEnabled(iter.second); if ((selectedRowID == 1) && !enabled) { continue; } @@ -4800,11 +4802,11 @@ void MainWindow::exportModListCSV() if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { if (mod_Priority->isChecked()) - builder.setRowField("#Mod_Priority", QString("%1").arg(m_OrganizerCore.currentProfile()->getModPriority(i), 4, 10, QChar('0'))); + builder.setRowField("#Mod_Priority", QString("%1").arg(iter.first, 4, 10, QChar('0'))); + if (mod_Status->isChecked()) + builder.setRowField("#Mod_Status", (enabled) ? "+" : "-"); if (mod_Name->isChecked()) builder.setRowField("#Mod_Name", info->name()); - if (mod_Status->isChecked()) - builder.setRowField("#Mod_Status", (enabled)? "Enabled" : "Disabled"); if (mod_Note->isChecked()) builder.setRowField("#Note", QString("%1").arg(info->comments().remove(','))); if (primary_Category->isChecked()) -- cgit v1.3.1 From c216442eb4368004055246222b7224769c37db82 Mon Sep 17 00:00:00 2001 From: Al Date: Thu, 20 Jun 2019 00:44:19 +0200 Subject: Modified Menu Bar: * Removed Edit and Copy Log as it was useless. * Moved Visit Nexus to Files. * Moved Profiles to Tools. * Renamed Tools submenu to "Tool Plugins". --- src/mainwindow.ui | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 98743b7e..0ee74239 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1358,7 +1358,7 @@ p, li { white-space: pre-wrap; } - + @@ -1366,9 +1366,9 @@ p, li { white-space: pre-wrap; } &Tools - - + + @@ -1379,14 +1379,9 @@ p, li { white-space: pre-wrap; } + - - - &Edit - - - &View @@ -1408,6 +1403,7 @@ p, li { white-space: pre-wrap; } + @@ -1416,7 +1412,6 @@ p, li { white-space: pre-wrap; } - @@ -1491,7 +1486,7 @@ p, li { white-space: pre-wrap; } :/MO/gui/plugins:/MO/gui/plugins - &Tools + &Tool Plugins &Tools -- cgit v1.3.1 From 724fad7cb62a0ee4913ed41bde45c7564183c7af Mon Sep 17 00:00:00 2001 From: Matte A Date: Sun, 23 Jun 2019 04:38:36 +0200 Subject: Correcting minor spelling mistakes in the UI + add contributor --- src/aboutdialog.ui | 4 ++-- src/instancemanager.cpp | 2 +- src/mainwindow.cpp | 4 ++-- src/mainwindow.ui | 4 ++-- src/moapplication.cpp | 4 ++-- src/modinfodialog.cpp | 8 ++++---- src/modinfodialog.ui | 8 ++++---- src/organizercore.cpp | 6 +++--- src/overwriteinfodialog.cpp | 8 ++++---- src/settingsdialog.ui | 4 ++-- src/spawn.cpp | 2 +- src/tutorials/tutorial_conflictresolution_main.js | 2 +- src/tutorials/tutorial_firststeps_main.js | 2 +- src/tutorials/tutorial_firststeps_modinfo.js | 2 +- 14 files changed, 30 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 86b95c83..1b3f0ad2 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -337,7 +337,7 @@ - Jax (Swedish) + Jax, Nubbie (Swedish) @@ -361,7 +361,7 @@ - Other Supporters && Contributors + Other Supporters & Contributors diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 5b57827a..b5130e8b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -102,7 +102,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const SelectionDialog selection( QString("

    %1


    %2") .arg(QObject::tr("Choose Instance to Delete")) - .arg(QObject::tr("Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")), + .arg(QObject::tr("Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")), nullptr); for (const QString &instance : instanceList) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e383006b..da7c721d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6796,7 +6796,7 @@ void MainWindow::on_bossButton_clicked() } if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); + QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occurred"), errorMessages.c_str(), QMessageBox::Ok, this); warn->setModal(false); warn->show(); } @@ -6899,7 +6899,7 @@ void MainWindow::on_saveModsButton_clicked() m_OrganizerCore.currentProfile()->writeModlistNow(true); QDateTime now = QDateTime::currentDateTime(); if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) { - MessageDialog::showMessage(tr("Backup of modlist created"), this); + MessageDialog::showMessage(tr("Backup of mod list created"), this); } } diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0ee74239..6a816262 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -221,8 +221,8 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html>
    diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 3a791827..5652833a 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -116,12 +116,12 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) } catch (const std::exception &e) { qCritical("uncaught exception in handler (object %s, eventtype %d): %s", receiver->objectName().toUtf8().constData(), event->type(), e.what()); - reportError(tr("an error occured: %1").arg(e.what())); + reportError(tr("an error occurred: %1").arg(e.what())); return false; } catch (...) { qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", receiver->objectName().toUtf8().constData(), event->type()); - reportError(tr("an error occured")); + reportError(tr("an error occurred")); return false; } } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2be8e481..188ea956 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1670,13 +1670,13 @@ void ModInfoDialog::delete_activated() } else if (selection->selectedRows().count() == 1) { QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } @@ -1695,12 +1695,12 @@ void ModInfoDialog::deleteTriggered() return; } else if (m_FileSelection.count() == 1) { QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e009a599..eed4e31f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -136,7 +136,7 @@ This is a list of ini tweaks (ini modifications that can be toggled). - This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod whether the tweaks are really optional. @@ -383,7 +383,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e ESPs in the data directory and thus visible to the game. - These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. + These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. @@ -897,7 +897,7 @@ text-align: left; <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer2 on Nexus. Why not go there now and endorse us?</p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> @@ -1067,7 +1067,7 @@ p, li { white-space: pre-wrap; } - Web page URL (only used if invalid NexusID) : + Web page URL (only used if invalid Nexus ID) : diff --git a/src/organizercore.cpp b/src/organizercore.cpp index c724e57f..2cad9ce8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -391,7 +391,7 @@ void OrganizerCore::storeSettings() if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occured trying to update MO settings to %1: %2") + tr("An error occurred trying to update MO settings to %1: %2") .arg(iniFile, windowsErrorString(::GetLastError()))); return; } @@ -418,7 +418,7 @@ void OrganizerCore::storeSettings() : tr("Unknown error %1").arg(result); QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occured trying to write back MO settings to %1: %2") + tr("An error occurred trying to write back MO settings to %1: %2") .arg(writeTarget, reason)); } } @@ -1570,7 +1570,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, tr("MO was denied access to the Steam process. This normally indicates that " "Steam is being run as administrator while MO is not. This can cause issues " "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely neccessary.\n\n" + "absolutely necessary.\n\n" "Restart MO as administrator?"), QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); if (result == QDialogButtonBox::Yes) { diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 9b3e55df..0a884ac9 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -162,13 +162,13 @@ void OverwriteInfoDialog::delete_activated() } else if (selection->selectedRows().count() == 1) { QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } @@ -187,12 +187,12 @@ void OverwriteInfoDialog::deleteTriggered() return; } else if (m_FileSelection.count() == 1) { QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 08c8d9f2..faaf1653 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -441,7 +441,7 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - Important: All directories have to be writeable! + Important: All directories have to be writable! @@ -1297,7 +1297,7 @@ programs you are intentionally running. Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. diff --git a/src/spawn.cpp b/src/spawn.cpp index d7d5efc2..f77da35f 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -150,7 +150,7 @@ HANDLE startBinary(const QFileInfo &binary, if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { if (QMessageBox::question(QApplication::activeModalWidget(), QObject::tr("Elevation required"), QObject::tr("This process requires elevation to run.\n" - "This is a potential security risk so I highly advice you to investigate if\n" + "This is a potential security risk so I highly advise you to investigate if\n" "\"%1\"\n" "can be installed to work without elevation.\n\n" "Restart Mod Organizer as an elevated process?\n" diff --git a/src/tutorials/tutorial_conflictresolution_main.js b/src/tutorials/tutorial_conflictresolution_main.js index 3c782af2..404afe5d 100644 --- a/src/tutorials/tutorial_conflictresolution_main.js +++ b/src/tutorials/tutorial_conflictresolution_main.js @@ -56,7 +56,7 @@ function getTutorialSteps() { waitForClick() }, function() { - tutorial.text = qsTr(" indicates that the mod is completely overwrtten by another. You could as well disable it."); + tutorial.text = qsTr(" indicates that the mod is completely overwritten by another. You could as well disable it."); waitForClick() }, function() { diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js index ee97766b..35d1aa11 100644 --- a/src/tutorials/tutorial_firststeps_main.js +++ b/src/tutorials/tutorial_firststeps_main.js @@ -137,7 +137,7 @@ function getTutorialSteps() function() { tutorial.text = qsTr("A single mod may contain zero, one or multiple esps. Some or all may be optional. " - + "If in doubt, please consult the documentation of the indiviual mod. " + + "If in doubt, please consult the documentation of the individual mod. " + "To do so, right-click the mod and select \"Information\".") highlightItem("modList", true) manager.activateTutorial("ModInfoDialog", "tutorial_firststeps_modinfo.js") diff --git a/src/tutorials/tutorial_firststeps_modinfo.js b/src/tutorials/tutorial_firststeps_modinfo.js index 50c38345..1ed0dab7 100644 --- a/src/tutorials/tutorial_firststeps_modinfo.js +++ b/src/tutorials/tutorial_firststeps_modinfo.js @@ -16,7 +16,7 @@ function getTutorialSteps() }, function() { unhighlight() - tutorial.text = qsTr("We may re-visit this screen in later tutorials.") + tutorial.text = qsTr("We may revisit this screen in later tutorials.") waitForClick() } ] -- cgit v1.3.1 From c3b57c90d1884afa6ec50c62216fdaaff483d06b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 04:35:00 -0400 Subject: refactored FormatMessage() into formatSystemMessage() fixes trying to load a wchar_t* with qCritical(), which merely prints the address --- src/settings.cpp | 61 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/settings.cpp b/src/settings.cpp index 53dd32dc..b45912d4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -81,6 +81,38 @@ private: int m_SortRole; }; + +QString formatSystemMessage(DWORD id) +{ + wchar_t* message = nullptr; + + const auto ret = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + id, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message), + 0, NULL); + + QString s; + const QString idString = QString("0x%1").arg(id, 0, 16); + + if (ret == 0 || !message) { + s = idString; + } else { + s = QString("%1 (%2)") + .arg(QString::fromStdWString(message).trimmed()) + .arg(idString); + } + + LocalFree(message); + + return s; +} + + Settings *Settings::s_Instance = nullptr; @@ -217,12 +249,11 @@ QString Settings::deObfuscate(const QString key) result = QString::fromWCharArray(charData, creds->CredentialBlobSize / sizeof(wchar_t)); CredFree(creds); } else { - if (GetLastError() != ERROR_NOT_FOUND) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Retrieving encrypted data failed:" << buffer; + const auto e = GetLastError(); + if (e != ERROR_NOT_FOUND) { + qCritical().nospace() + << "Retrieving encrypted data failed: " + << formatSystemMessage(e); } } delete[] keyData; @@ -366,13 +397,8 @@ bool Settings::getNexusApiKey(QString &apiKey) const bool Settings::setNexusApiKey(const QString& apiKey) { if (!obfuscate("APIKEY", apiKey)) { - wchar_t buffer[256]; - - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - - qCritical() << "Storing API key failed:" << buffer; + const auto e = GetLastError(); + qCritical().nospace() << "Storing API key failed: " << formatSystemMessage(e); return false; } @@ -487,11 +513,10 @@ void Settings::setSteamLogin(QString username, QString password) m_Settings.setValue("Settings/steam_username", username); } if (!obfuscate("steam_password", password)) { - wchar_t buffer[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, (sizeof(buffer) / sizeof(wchar_t)), NULL); - qCritical() << "Storing or deleting password failed:" << buffer; + const auto e = GetLastError(); + qCritical().nospace() + << "Storing or deleting password failed: " + << formatSystemMessage(e); } } -- cgit v1.3.1 From eab0ec298b81138c4c602259ebf930f583113b95 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 15:26:04 -0400 Subject: refactored preloadSsl() into preloadDll() removed old HGID check moved formatSystemMessage() to uibase added Environment class, lists loaded modules, logged at startup --- src/main.cpp | 70 ++++++----- src/settings.cpp | 31 ----- src/shared/util.cpp | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/shared/util.h | 50 +++++++- 4 files changed, 418 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 6f304944..e1a6b853 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -411,27 +411,39 @@ void setupPath() ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); } -static void preloadSsl() +void preloadDll(const QString& filename) { - QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath()); - if (GetModuleHandleA("libeay32.dll")) - qWarning("libeay32.dll already loaded?!"); - else { - QString libeay32 = appPath + "\\libeay32.dll"; - if (!QFile::exists(libeay32)) - qWarning("libeay32.dll not found: %s", qUtf8Printable(libeay32)); - else if (!LoadLibraryW(libeay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qUtf8Printable(libeay32), GetLastError()); + qDebug().nospace() << "preloading " << filename; + + if (!GetModuleHandleW(filename.toStdWString().c_str())) { + // already loaded, this can happen when "restarting" MO by switching + // instances, for example + return; } - if (GetModuleHandleA("ssleay32.dll")) - qWarning("ssleay32.dll already loaded?!"); - else { - QString ssleay32 = appPath + "\\ssleay32.dll"; - if (!QFile::exists(ssleay32)) - qWarning("ssleay32.dll not found: %s", qUtf8Printable(ssleay32)); - else if (!LoadLibraryW(ssleay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qUtf8Printable(ssleay32), GetLastError()); + + const auto appPath = QDir::toNativeSeparators( + QCoreApplication::applicationDirPath()); + + const auto dllPath = appPath + "\\" + filename; + + if (!QFile::exists(dllPath)) { + qWarning().nospace() << dllPath << "not found"; + return; } + + if (!LoadLibraryW(dllPath.toStdWString().c_str())) { + const auto e = GetLastError(); + + qWarning().nospace() + << "failed to load " << dllPath << ": " + << formatSystemMessage(e); + } +} + +void preloadSsl() +{ + preloadDll("libeay32.dll"); + preloadDll("ssleay32.dll"); } static QString getVersionDisplayString() @@ -443,15 +455,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, const QString &splashPath) { - qDebug("Starting Mod Organizer version %s revision %s", qUtf8Printable(getVersionDisplayString()), -#if defined(HGID) - HGID -#elif defined(GITID) - GITID -#else - "unknown" -#endif - ); + qDebug().nospace() + << "Starting Mod Organizer version " + << getVersionDisplayString() << " revision " << GITID; #if !defined(QT_NO_SSL) preloadSsl(); @@ -460,6 +466,16 @@ int runApplication(MOApplication &application, SingleInstance &instance, qDebug("non-ssl build"); #endif + { + Environment env; + + for (const auto& m : env.loadedModules()) { + qInfo().nospace().noquote() << " . " << m.toString(); + } + + return 0; + } + QString dataPath = application.property("dataPath").toString(); qDebug("data path: %s", qUtf8Printable(dataPath)); diff --git a/src/settings.cpp b/src/settings.cpp index b45912d4..59d3369a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -82,37 +82,6 @@ private: }; -QString formatSystemMessage(DWORD id) -{ - wchar_t* message = nullptr; - - const auto ret = FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - id, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message), - 0, NULL); - - QString s; - const QString idString = QString("0x%1").arg(id, 0, 16); - - if (ret == 0 || !message) { - s = idString; - } else { - s = QString("%1 (%2)") - .arg(QString::fromStdWString(message).trimmed()) - .arg(idString); - } - - LocalFree(message); - - return s; -} - - Settings *Settings::s_Instance = nullptr; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index ed7c434e..16b373db 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "util.h" #include "windows_error.h" #include "error_report.h" +#include #include #include @@ -29,6 +30,8 @@ along with Mod Organizer. If not, see . #include #include +using MOBase::formatSystemMessage; + namespace MOShared { @@ -253,4 +256,328 @@ MOBase::VersionInfo createVersionInfo() } +struct HandleCloser +{ + using pointer = HANDLE; + void operator()(HANDLE h) + { + if (h != INVALID_HANDLE_VALUE) { + ::CloseHandle(h); + } + } +}; + + +Environment::Environment() +{ + getLoadedModules(); +} + +const std::vector& Environment::loadedModules() +{ + return m_modules; +} + +void Environment::getLoadedModules() +{ + std::unique_ptr snapshot(CreateToolhelp32Snapshot( + TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); + + if (snapshot.get() == INVALID_HANDLE_VALUE) + { + const auto e = GetLastError(); + + qCritical().nospace() + << "CreateToolhelp32Snapshot() failed, " + << formatSystemMessage(e); + + return; + } + + // Set the size of the structure before using it. + MODULEENTRY32 me = {}; + me.dwSize = sizeof(me); + + // Retrieve information about the first module, + // and exit if unsuccessful + if (!Module32First(snapshot.get(), &me)) + { + const auto e = GetLastError(); + + qCritical().nospace() + << "Module32First() failed, " << formatSystemMessage(e); + + return; + } + + // Now walk the module list of the process, + // and display information about each module + qInfo() << "modules loaded in process:"; + + for (;;) + { + const auto path = QString::fromWCharArray(me.szExePath); + + m_modules.push_back(Module(path, me.modBaseSize)); + + if (!Module32Next(snapshot.get(), &me)) { + const auto e = GetLastError(); + + if (e != ERROR_NO_MORE_FILES) { + qCritical() << "Module32Next() failed, " << formatSystemMessage(e); + } + + break; + } + } +} + + +Environment::Module::Module(QString path, std::size_t fileSize) + : m_path(std::move(path)), m_fileSize(fileSize) +{ + const auto fi = getFileInfo(); + + m_version = getVersion(fi.ffi); + m_timestamp = getTimestamp(fi.ffi); + m_versionString = fi.fileDescription; +} + +const QString& Environment::Module::path() const +{ + return m_path; +} + +std::size_t Environment::Module::fileSize() const +{ + return m_fileSize; +} + +const QString& Environment::Module::version() const +{ + return m_version; +} + +const QString& Environment::Module::versionString() const +{ + return m_versionString; +} + +QString Environment::Module::timestampString() const +{ + if (!m_timestamp.isValid()) { + return "(no timestamp)"; + } + + return m_timestamp.toString(Qt::DateFormat::ISODate); +} + +QString Environment::Module::toString() const +{ + QStringList sl; + + sl.push_back(m_path); + sl.push_back(QString("%1 B").arg(m_fileSize)); + + if (m_version.isEmpty() && m_versionString.isEmpty()) { + sl.push_back("(no version)"); + } else { + if (!m_version.isEmpty()) { + sl.push_back(m_version); + } + + if (m_versionString != m_version) { + sl.push_back(versionString()); + } + } + + if (m_timestamp.isValid()) { + sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); + } else { + sl.push_back("(no timestamp)"); + } + + return sl.join(", "); +} + +Environment::Module::FileInfo Environment::Module::getFileInfo() const +{ + const auto wspath = m_path.toStdWString(); + + DWORD dummy = 0; + const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); + + if (size == 0) { + const auto e = GetLastError(); + + if (e == ERROR_RESOURCE_TYPE_NOT_FOUND) { + // not an error, no version information built into that module + return {}; + } + + qCritical().nospace().noquote() + << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " + << formatSystemMessage(e); + + return {}; + } + + auto buffer = std::make_unique(size); + + if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { + const auto e = GetLastError(); + + qCritical().nospace().noquote() + << "GetFileVersionInfoW() failed on '" << m_path << "', " + << formatSystemMessage(e); + + return {}; + } + + + FileInfo fi; + fi.ffi = getFixedFileInfo(buffer.get()); + fi.fileDescription = getFileDescription(buffer.get()); + + return fi; +} + +VS_FIXEDFILEINFO Environment::Module::getFixedFileInfo(std::byte* buffer) const +{ + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no fixed file info + return {}; + } + + const auto* fi = reinterpret_cast(valuePointer); + + if (fi->dwSignature != 0xfeef04bd) { + qCritical().nospace().noquote() + << "bad file info signature 0x" << hex << fi->dwSignature << " for " + << "'" << m_path << "'"; + + return {}; + } + + return *fi; +} + +QString Environment::Module::getFileDescription(std::byte* buffer) const +{ + struct LANGANDCODEPAGE + { + WORD wLanguage; + WORD wCodePage; + }; + + void* valuePointer = nullptr; + unsigned int valueSize = 0; + + auto ret = VerQueryValueW( + buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + qCritical().nospace().noquote() + << "VerQueryValueW() for translations failed on '" << m_path << "'"; + + return {}; + } + + const auto count = valueSize / sizeof(LANGANDCODEPAGE); + if (count == 0) { + return {}; + } + + const auto* lcp = reinterpret_cast(valuePointer); + + const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") + .arg(lcp->wLanguage, 4, 16, QChar('0')) + .arg(lcp->wCodePage, 4, 16, QChar('0')); + + ret = VerQueryValueW( + buffer, subBlock.toStdWString().c_str(), &valuePointer, &valueSize); + + if (!ret || !valuePointer || valueSize == 0) { + // not an error, no file version + return {}; + } + + // valueSize includes the null terminator + return QString::fromWCharArray( + reinterpret_cast(valuePointer), valueSize - 1); +} + +QString Environment::Module::getVersion(const VS_FIXEDFILEINFO& fi) const +{ + if (fi.dwSignature == 0) { + return {}; + } + + const DWORD major = (fi.dwFileVersionMS >> 16 ) & 0xffff; + const DWORD minor = (fi.dwFileVersionMS >> 0 ) & 0xffff; + const DWORD maintenance = (fi.dwFileVersionLS >> 16 ) & 0xffff; + const DWORD build = (fi.dwFileVersionLS >> 0 ) & 0xffff; + + if (major == 0 && minor == 0 && maintenance == 0 && build == 0) { + return {}; + } + + return QString("%1.%2.%3.%4") + .arg(major).arg(minor).arg(maintenance).arg(build); +} + +QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +{ + FILETIME ft = {}; + + if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + std::unique_ptr h(CreateFileW( + m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); + + if (h.get() == INVALID_HANDLE_VALUE) { + const auto e = GetLastError(); + + qCritical() + << "can't open file '" << m_path << "' for timestamp, " + << formatSystemMessage(e); + + return {}; + } + + if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { + const auto e = GetLastError(); + qCritical() + << "can't get file time for '" << m_path << "', " + << formatSystemMessage(e); + + return {}; + } + } else { + ft.dwHighDateTime = fi.dwFileDateMS; + ft.dwLowDateTime = fi.dwFileDateLS; + } + + + SYSTEMTIME utc = {}; + if (!FileTimeToSystemTime(&ft, &utc)) { + qCritical() + << "FileTimeToSystemTime() failed on timestamp " + << "high=0x" << hex << ft.dwHighDateTime << " " + << "low=0x" << hex << ft.dwLowDateTime << " for " + << "'" << m_path << "'"; + + return {}; + } + + return QDateTime( + QDate(utc.wYear, utc.wMonth, utc.wDay), + QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 1fdfb089..5d88481c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -46,8 +46,54 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); -VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName); -std::wstring GetFileVersionString(const std::wstring &fileName); +class Environment +{ +public: + class Module + { + public: + explicit Module(QString path, std::size_t fileSize); + + const QString& path() const; + std::size_t fileSize() const; + const QString& version() const; + const QString& versionString() const; + QString timestampString() const; + + QString toString() const; + + private: + struct FileInfo + { + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; + + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + + FileInfo getFileInfo() const; + + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + QString getFileDescription(std::byte* buffer) const; + }; + + Environment(); + + const std::vector& loadedModules(); + +private: + std::vector m_modules; + + void getLoadedModules(); +}; + MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From 7152b8e97b060e34e5a5bfa8f36c94722007d6aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 16:13:55 -0400 Subject: lower case paths, sorted list, md5 for some files --- src/shared/util.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++------- src/shared/util.h | 4 ++++ 2 files changed, 54 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 16b373db..772d3ca5 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -287,7 +287,7 @@ void Environment::getLoadedModules() { const auto e = GetLastError(); - qCritical().nospace() + qCritical().nospace().noquote() << "CreateToolhelp32Snapshot() failed, " << formatSystemMessage(e); @@ -304,7 +304,7 @@ void Environment::getLoadedModules() { const auto e = GetLastError(); - qCritical().nospace() + qCritical().nospace().noquote() << "Module32First() failed, " << formatSystemMessage(e); return; @@ -324,12 +324,17 @@ void Environment::getLoadedModules() const auto e = GetLastError(); if (e != ERROR_NO_MORE_FILES) { - qCritical() << "Module32Next() failed, " << formatSystemMessage(e); + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessage(e); } break; } } + + std::sort(m_modules.begin(), m_modules.end(), [](auto&& a, auto&& b) { + return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); + }); } @@ -341,6 +346,7 @@ Environment::Module::Module(QString path, std::size_t fileSize) m_version = getVersion(fi.ffi); m_timestamp = getTimestamp(fi.ffi); m_versionString = fi.fileDescription; + m_md5 = getMD5(); } const QString& Environment::Module::path() const @@ -348,6 +354,11 @@ const QString& Environment::Module::path() const return m_path; } +QString Environment::Module::displayPath() const +{ + return QDir::fromNativeSeparators(m_path.toLower()); +} + std::size_t Environment::Module::fileSize() const { return m_fileSize; @@ -376,7 +387,7 @@ QString Environment::Module::toString() const { QStringList sl; - sl.push_back(m_path); + sl.push_back(displayPath()); sl.push_back(QString("%1 B").arg(m_fileSize)); if (m_version.isEmpty() && m_versionString.isEmpty()) { @@ -397,6 +408,10 @@ QString Environment::Module::toString() const sl.push_back("(no timestamp)"); } + if (!m_md5.isEmpty()) { + sl.push_back(m_md5); + } + return sl.join(", "); } @@ -543,7 +558,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const if (h.get() == INVALID_HANDLE_VALUE) { const auto e = GetLastError(); - qCritical() + qCritical().nospace().noquote() << "can't open file '" << m_path << "' for timestamp, " << formatSystemMessage(e); @@ -552,7 +567,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { const auto e = GetLastError(); - qCritical() + qCritical().nospace().noquote() << "can't get file time for '" << m_path << "', " << formatSystemMessage(e); @@ -566,7 +581,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const SYSTEMTIME utc = {}; if (!FileTimeToSystemTime(&ft, &utc)) { - qCritical() + qCritical().nospace().noquote() << "FileTimeToSystemTime() failed on timestamp " << "high=0x" << hex << ft.dwHighDateTime << " " << "low=0x" << hex << ft.dwLowDateTime << " for " @@ -580,4 +595,32 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); } +QString Environment::Module::getMD5() const +{ + if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { + // don't calculate md5 for system files, it's not really relevant and + // it takes a while + return {}; + } + + QFile f(m_path); + + if (!f.open(QFile::ReadOnly)) { + qCritical().nospace().noquote() + << "failed to open file '" << m_path << "' for md5"; + + return {}; + } + + QCryptographicHash hash(QCryptographicHash::Md5); + if (!hash.addData(&f)) { + qCritical().nospace().noquote() + << "failed to calculate md5 for '" << m_path << "'"; + + return {}; + } + + return hash.result().toHex(); +} + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 5d88481c..46bdea78 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -55,6 +55,8 @@ public: explicit Module(QString path, std::size_t fileSize); const QString& path() const; + QString displayPath() const; + std::size_t fileSize() const; const QString& version() const; const QString& versionString() const; @@ -74,11 +76,13 @@ public: QString m_version; QDateTime m_timestamp; QString m_versionString; + QString m_md5; FileInfo getFileInfo() const; QString getVersion(const VS_FIXEDFILEINFO& fi) const; QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + QString getMD5() const; VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; QString getFileDescription(std::byte* buffer) const; -- cgit v1.3.1 From fb6512b72ebf86d5273744774388deb14c8a21ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 18:16:00 -0400 Subject: log windows version --- src/main.cpp | 6 +- src/shared/util.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/shared/util.h | 94 +++++++++++++++-------- 3 files changed, 264 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index e1a6b853..75148c4b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -467,8 +467,12 @@ int runApplication(MOApplication &application, SingleInstance &instance, #endif { - Environment env; + env::Environment env; + qInfo().nospace().noquote() + << "windows: " << env.windowsVersion().toString(); + + qInfo() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { qInfo().nospace().noquote() << " . " << m.toString(); } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 772d3ca5..9337ebf6 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -256,9 +256,13 @@ MOBase::VersionInfo createVersionInfo() } +namespace env +{ + struct HandleCloser { using pointer = HANDLE; + void operator()(HANDLE h) { if (h != INVALID_HANDLE_VALUE) { @@ -267,17 +271,34 @@ struct HandleCloser } }; +struct LibraryFreer +{ + using pointer = HINSTANCE; + + void operator()(HINSTANCE h) + { + if (h != 0) { + ::FreeLibrary(h); + } + } +}; + Environment::Environment() { getLoadedModules(); } -const std::vector& Environment::loadedModules() +const std::vector& Environment::loadedModules() { return m_modules; } +const WindowsVersion& Environment::windowsVersion() const +{ + return m_windows; +} + void Environment::getLoadedModules() { std::unique_ptr snapshot(CreateToolhelp32Snapshot( @@ -312,7 +333,6 @@ void Environment::getLoadedModules() // Now walk the module list of the process, // and display information about each module - qInfo() << "modules loaded in process:"; for (;;) { @@ -338,7 +358,7 @@ void Environment::getLoadedModules() } -Environment::Module::Module(QString path, std::size_t fileSize) +Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) { const auto fi = getFileInfo(); @@ -349,32 +369,32 @@ Environment::Module::Module(QString path, std::size_t fileSize) m_md5 = getMD5(); } -const QString& Environment::Module::path() const +const QString& Module::path() const { return m_path; } -QString Environment::Module::displayPath() const +QString Module::displayPath() const { return QDir::fromNativeSeparators(m_path.toLower()); } -std::size_t Environment::Module::fileSize() const +std::size_t Module::fileSize() const { return m_fileSize; } -const QString& Environment::Module::version() const +const QString& Module::version() const { return m_version; } -const QString& Environment::Module::versionString() const +const QString& Module::versionString() const { return m_versionString; } -QString Environment::Module::timestampString() const +QString Module::timestampString() const { if (!m_timestamp.isValid()) { return "(no timestamp)"; @@ -383,7 +403,7 @@ QString Environment::Module::timestampString() const return m_timestamp.toString(Qt::DateFormat::ISODate); } -QString Environment::Module::toString() const +QString Module::toString() const { QStringList sl; @@ -415,7 +435,7 @@ QString Environment::Module::toString() const return sl.join(", "); } -Environment::Module::FileInfo Environment::Module::getFileInfo() const +Module::FileInfo Module::getFileInfo() const { const auto wspath = m_path.toStdWString(); @@ -457,7 +477,7 @@ Environment::Module::FileInfo Environment::Module::getFileInfo() const return fi; } -VS_FIXEDFILEINFO Environment::Module::getFixedFileInfo(std::byte* buffer) const +VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const { void* valuePointer = nullptr; unsigned int valueSize = 0; @@ -482,7 +502,7 @@ VS_FIXEDFILEINFO Environment::Module::getFixedFileInfo(std::byte* buffer) const return *fi; } -QString Environment::Module::getFileDescription(std::byte* buffer) const +QString Module::getFileDescription(std::byte* buffer) const { struct LANGANDCODEPAGE { @@ -527,7 +547,7 @@ QString Environment::Module::getFileDescription(std::byte* buffer) const reinterpret_cast(valuePointer), valueSize - 1); } -QString Environment::Module::getVersion(const VS_FIXEDFILEINFO& fi) const +QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const { if (fi.dwSignature == 0) { return {}; @@ -546,7 +566,7 @@ QString Environment::Module::getVersion(const VS_FIXEDFILEINFO& fi) const .arg(major).arg(minor).arg(maintenance).arg(build); } -QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const +QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const { FILETIME ft = {}; @@ -595,7 +615,7 @@ QDateTime Environment::Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const QTime(utc.wHour, utc.wMinute, utc.wSecond, utc.wMilliseconds)); } -QString Environment::Module::getMD5() const +QString Module::getMD5() const { if (m_path.contains("\\windows\\", Qt::CaseInsensitive)) { // don't calculate md5 for system files, it's not really relevant and @@ -623,4 +643,163 @@ QString Environment::Module::getMD5() const return hash.result().toHex(); } + +WindowsVersion::WindowsVersion() : + m_realMajor(0), m_realMinor(0), m_realBuild(0), + m_major(0), m_minor(0), m_build(0), m_UBR(0) +{ + getVersion(); + getRelease(); + getElevated(); +} + +QString WindowsVersion::toString() const +{ + QStringList sl; + + const QString version = QString("%1.%2.%3") + .arg(m_major).arg(m_minor).arg(m_build); + + const QString realVersion = QString("%1.%2.%3") + .arg(m_realMajor).arg(m_realMinor).arg(m_realBuild); + + sl.push_back("version: " + version); + + if (m_realMajor != m_major || m_realMinor != m_minor || m_realBuild != m_build) { + sl.push_back("real version: " + realVersion); + } + + if (!m_buildLab.isEmpty()) { + sl.push_back(m_buildLab); + } + + if (!m_productName.isEmpty()) { + sl.push_back(m_productName); + } + + if (!m_releaseID.isEmpty()) { + sl.push_back("build " + m_releaseID); + } + + if (m_UBR != 0) { + sl.push_back(QString("%1").arg(m_UBR)); + } + + QString elevated = "?"; + if (m_elevated.has_value()) { + elevated = (*m_elevated ? "yes" : "no"); + } + + sl.push_back("elevated: " + elevated); + + return sl.join(", "); +} + +void WindowsVersion::getVersion() +{ + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } + + getRealVersion(ntdll.get()); + getReportedVersion(ntdll.get()); +} + +void WindowsVersion::getRealVersion(HINSTANCE ntdll) +{ + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (RtlGetNtVersionNumbers) { + DWORD build = 0; + RtlGetNtVersionNumbers(&m_realMajor, &m_realMinor, &build); + + m_realBuild = 0x0fffffff & build; + } +} + +void WindowsVersion::getReportedVersion(HINSTANCE ntdll) +{ + using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); + + auto* RtlGetVersion = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetVersion")); + + if (!RtlGetVersion) { + qCritical() << "RtlGetVersion() not found in ntdll.dll"; + return; + } + + OSVERSIONINFOEX vi = {}; + vi.dwOSVersionInfoSize = sizeof(vi); + + RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); + + m_major = vi.dwMajorVersion; + m_minor = vi.dwMinorVersion; + m_build = vi.dwBuildNumber; +} + +void WindowsVersion::getRelease() +{ + QSettings settings( + R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", + QSettings::NativeFormat); + + m_buildLab = settings.value("BuildLabEx", "").toString(); + if (m_buildLab.isEmpty()) { + m_buildLab = settings.value("BuildLab", "").toString(); + if (m_buildLab.isEmpty()) { + m_buildLab = settings.value("BuildBranch", "").toString(); + } + } + + m_productName = settings.value("ProductName", "").toString(); + m_releaseID = settings.value("ReleaseId", "").toString(); + m_UBR = settings.value("UBR", 0).toUInt(); +} + +void WindowsVersion::getElevated() +{ + std::unique_ptr token; + + { + HANDLE rawToken = 0; + + if (!OpenProcessToken(GetCurrentProcess( ), TOKEN_QUERY, &rawToken)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "OpenProcessToken() failed: " << formatSystemMessage(e); + + return; + } + + token.reset(rawToken); + } + + TOKEN_ELEVATION e = {}; + DWORD size = sizeof(TOKEN_ELEVATION); + + if (!GetTokenInformation(token.get(), TokenElevation, &e, sizeof(e), &size)) { + const auto e = GetLastError(); + + qCritical() + << "while trying to check if process is elevated, " + << "GetTokenInformation() failed: " << formatSystemMessage(e); + + return; + } + + m_elevated = (e.TokenIsElevated != 0); +} + +} // namespace env + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 46bdea78..deaf6fcc 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -22,6 +22,8 @@ along with Mod Organizer. If not, see . #include +#include + #define WIN32_LEAN_AND_MEAN #include @@ -46,58 +48,90 @@ std::wstring ToLower(const std::wstring &text); bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); -class Environment + +namespace env +{ + +class Module { public: - class Module + explicit Module(QString path, std::size_t fileSize); + + const QString& path() const; + QString displayPath() const; + + std::size_t fileSize() const; + const QString& version() const; + const QString& versionString() const; + QString timestampString() const; + + QString toString() const; + +private: + struct FileInfo { - public: - explicit Module(QString path, std::size_t fileSize); + VS_FIXEDFILEINFO ffi; + QString fileDescription; + }; - const QString& path() const; - QString displayPath() const; + QString m_path; + std::size_t m_fileSize; + QString m_version; + QDateTime m_timestamp; + QString m_versionString; + QString m_md5; - std::size_t fileSize() const; - const QString& version() const; - const QString& versionString() const; - QString timestampString() const; + FileInfo getFileInfo() const; - QString toString() const; + QString getVersion(const VS_FIXEDFILEINFO& fi) const; + QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + QString getMD5() const; - private: - struct FileInfo - { - VS_FIXEDFILEINFO ffi; - QString fileDescription; - }; + VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + QString getFileDescription(std::byte* buffer) const; +}; - QString m_path; - std::size_t m_fileSize; - QString m_version; - QDateTime m_timestamp; - QString m_versionString; - QString m_md5; - FileInfo getFileInfo() const; +class WindowsVersion +{ +public: + WindowsVersion(); - QString getVersion(const VS_FIXEDFILEINFO& fi) const; - QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; - QString getMD5() const; + QString toString() const; + +private: + DWORD m_realMajor, m_realMinor, m_realBuild; + DWORD m_major, m_minor, m_build; + QString m_buildLab, m_productName, m_releaseID; + DWORD m_UBR; + std::optional m_elevated; + + void getVersion(); + void getRealVersion(HINSTANCE ntdll); + void getReportedVersion(HINSTANCE ntdll); + void getRelease(); + void getElevated(); +}; - VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; - QString getFileDescription(std::byte* buffer) const; - }; +class Environment +{ +public: Environment(); const std::vector& loadedModules(); + const WindowsVersion& windowsVersion() const; private: std::vector m_modules; + WindowsVersion m_windows; void getLoadedModules(); }; +} // namespace env + + MOBase::VersionInfo createVersionInfo(); } // namespace MOShared -- cgit v1.3.1 From 813ab32a2d16106f2799fba4e85c768a5b1c4850 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 18:58:15 -0400 Subject: added a warning when running in compatibility mode --- src/main.cpp | 6 ++++-- src/shared/util.cpp | 14 +++++++++++++- src/shared/util.h | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 75148c4b..e43ec6d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -472,12 +472,14 @@ int runApplication(MOApplication &application, SingleInstance &instance, qInfo().nospace().noquote() << "windows: " << env.windowsVersion().toString(); + if (env.windowsVersion().compatibilityMode()) { + qWarning() << "MO seems to be running in compatibility mode"; + } + qInfo() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { qInfo().nospace().noquote() << " . " << m.toString(); } - - return 0; } QString dataPath = application.property("dataPath").toString(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 9337ebf6..33094eff 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -653,6 +653,18 @@ WindowsVersion::WindowsVersion() : getElevated(); } +bool WindowsVersion::compatibilityMode() const +{ + if (m_realMajor == 0 && m_realMinor == 0 && m_realBuild == 0) { + return false; + } + + return + m_realMajor != m_major || + m_realMinor != m_minor || + m_realBuild != m_build; +} + QString WindowsVersion::toString() const { QStringList sl; @@ -665,7 +677,7 @@ QString WindowsVersion::toString() const sl.push_back("version: " + version); - if (m_realMajor != m_major || m_realMinor != m_minor || m_realBuild != m_build) { + if (compatibilityMode()) { sl.push_back("real version: " + realVersion); } diff --git a/src/shared/util.h b/src/shared/util.h index deaf6fcc..232a97bb 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -97,6 +97,7 @@ class WindowsVersion public: WindowsVersion(); + bool compatibilityMode() const; QString toString() const; private: -- cgit v1.3.1 From 1aa174f1438d7b1c1a9dab47d69e912726578a06 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 29 Jun 2019 20:02:10 -0400 Subject: comments, some refactoring switched to qDebug() --- src/main.cpp | 10 +- src/shared/util.cpp | 259 +++++++++++++++++++++++++++++++++++----------------- src/shared/util.h | 159 +++++++++++++++++++++++++++++--- 3 files changed, 326 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index e43ec6d0..6c3e40be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -469,16 +469,16 @@ int runApplication(MOApplication &application, SingleInstance &instance, { env::Environment env; - qInfo().nospace().noquote() - << "windows: " << env.windowsVersion().toString(); + qDebug().nospace().noquote() + << "windows: " << env.windowsInfo().toString(); - if (env.windowsVersion().compatibilityMode()) { + if (env.windowsInfo().compatibilityMode()) { qWarning() << "MO seems to be running in compatibility mode"; } - qInfo() << "modules loaded in process:"; + qDebug() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { - qInfo().nospace().noquote() << " . " << m.toString(); + qDebug().nospace().noquote() << " . " << m.toString(); } } diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 33094eff..70adb791 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -294,7 +294,7 @@ const std::vector& Environment::loadedModules() return m_modules; } -const WindowsVersion& Environment::windowsVersion() const +const WindowsInfo& Environment::windowsInfo() const { return m_windows; } @@ -315,12 +315,10 @@ void Environment::getLoadedModules() return; } - // Set the size of the structure before using it. MODULEENTRY32 me = {}; me.dwSize = sizeof(me); - // Retrieve information about the first module, - // and exit if unsuccessful + // first module, this shouldn't fail because there's at least the executable if (!Module32First(snapshot.get(), &me)) { const auto e = GetLastError(); @@ -331,27 +329,29 @@ void Environment::getLoadedModules() return; } - // Now walk the module list of the process, - // and display information about each module - for (;;) { const auto path = QString::fromWCharArray(me.szExePath); m_modules.push_back(Module(path, me.modBaseSize)); + // next module if (!Module32Next(snapshot.get(), &me)) { const auto e = GetLastError(); - if (e != ERROR_NO_MORE_FILES) { - qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessage(e); + if (e == ERROR_NO_MORE_FILES) { + // not an error + break; } + qCritical().nospace().noquote() + << "Module32Next() failed, " << formatSystemMessage(e); + break; } } + // sorting by display name std::sort(m_modules.begin(), m_modules.end(), [](auto&& a, auto&& b) { return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); }); @@ -394,6 +394,16 @@ const QString& Module::versionString() const return m_versionString; } +const QDateTime& Module::timestamp() const +{ + return m_timestamp; +} + +const QString& Module::md5() const +{ + return m_md5; +} + QString Module::timestampString() const { if (!m_timestamp.isValid()) { @@ -407,9 +417,11 @@ QString Module::toString() const { QStringList sl; + // file size sl.push_back(displayPath()); sl.push_back(QString("%1 B").arg(m_fileSize)); + // version if (m_version.isEmpty() && m_versionString.isEmpty()) { sl.push_back("(no version)"); } else { @@ -417,17 +429,19 @@ QString Module::toString() const sl.push_back(m_version); } - if (m_versionString != m_version) { + if (!m_versionString.isEmpty() && m_versionString != m_version) { sl.push_back(versionString()); } } + // timestamp if (m_timestamp.isValid()) { sl.push_back(m_timestamp.toString(Qt::DateFormat::ISODate)); } else { sl.push_back("(no timestamp)"); } + // md5 if (!m_md5.isEmpty()) { sl.push_back(m_md5); } @@ -439,6 +453,7 @@ Module::FileInfo Module::getFileInfo() const { const auto wspath = m_path.toStdWString(); + // getting version info size DWORD dummy = 0; const DWORD size = GetFileVersionInfoSizeW(wspath.c_str(), &dummy); @@ -457,6 +472,7 @@ Module::FileInfo Module::getFileInfo() const return {}; } + // getting version info auto buffer = std::make_unique(size); if (!GetFileVersionInfoW(wspath.c_str(), 0, size, buffer.get())) { @@ -469,6 +485,8 @@ Module::FileInfo Module::getFileInfo() const return {}; } + // the version info has two major parts: a fixed version and a localizable + // set of strings FileInfo fi; fi.ffi = getFixedFileInfo(buffer.get()); @@ -482,6 +500,7 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const void* valuePointer = nullptr; unsigned int valueSize = 0; + // the fixed version info is in the root const auto ret = VerQueryValueW(buffer, L"\\", &valuePointer, &valueSize); if (!ret || !valuePointer || valueSize == 0) { @@ -491,6 +510,7 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const const auto* fi = reinterpret_cast(valuePointer); + // signature is always 0xfeef04bd if (fi->dwSignature != 0xfeef04bd) { qCritical().nospace().noquote() << "bad file info signature 0x" << hex << fi->dwSignature << " for " @@ -513,6 +533,7 @@ QString Module::getFileDescription(std::byte* buffer) const void* valuePointer = nullptr; unsigned int valueSize = 0; + // getting list of available languages auto ret = VerQueryValueW( buffer, L"\\VarFileInfo\\Translation", &valuePointer, &valueSize); @@ -523,11 +544,13 @@ QString Module::getFileDescription(std::byte* buffer) const return {}; } + // number of languages const auto count = valueSize / sizeof(LANGANDCODEPAGE); if (count == 0) { return {}; } + // using the first language in the list to get FileVersion const auto* lcp = reinterpret_cast(valuePointer); const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") @@ -571,6 +594,10 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const FILETIME ft = {}; if (fi.dwSignature == 0 || (fi.dwFileDateMS == 0 && fi.dwFileDateLS == 0)) { + // if the file info is invalid or doesn't have a date, use the creation + // time on the file + + // opening the file std::unique_ptr h(CreateFileW( m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); @@ -585,6 +612,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const return {}; } + // getting the file time if (!GetFileTime(h.get(), &ft, nullptr, nullptr)) { const auto e = GetLastError(); qCritical().nospace().noquote() @@ -594,12 +622,15 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const return {}; } } else { + // use the time from the file info ft.dwHighDateTime = fi.dwFileDateMS; ft.dwLowDateTime = fi.dwFileDateLS; } + // converting to SYSTEMTIME SYSTEMTIME utc = {}; + if (!FileTimeToSystemTime(&ft, &utc)) { qCritical().nospace().noquote() << "FileTimeToSystemTime() failed on timestamp " @@ -623,6 +654,7 @@ QString Module::getMD5() const return {}; } + // opening the file QFile f(m_path); if (!f.open(QFile::ReadOnly)) { @@ -632,6 +664,7 @@ QString Module::getMD5() const return {}; } + // hashing QCryptographicHash hash(QCryptographicHash::Md5); if (!hash.addData(&f)) { qCritical().nospace().noquote() @@ -644,59 +677,97 @@ QString Module::getMD5() const } -WindowsVersion::WindowsVersion() : - m_realMajor(0), m_realMinor(0), m_realBuild(0), - m_major(0), m_minor(0), m_build(0), m_UBR(0) +WindowsInfo::WindowsInfo() { - getVersion(); - getRelease(); - getElevated(); + // loading ntdll.dll, the functions will be found with GetProcAddress() + std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); + + if (!ntdll) { + qCritical() << "failed to load ntdll.dll while getting version"; + return; + } else { + m_reported = getReportedVersion(ntdll.get()); + m_real = getRealVersion(ntdll.get()); + } + + m_release = getRelease(); + m_elevated = getElevated(); } -bool WindowsVersion::compatibilityMode() const +bool WindowsInfo::compatibilityMode() const { - if (m_realMajor == 0 && m_realMinor == 0 && m_realBuild == 0) { + if (m_real == Version()) { + // don't know the real version, can't guess compatibility mode return false; } - return - m_realMajor != m_major || - m_realMinor != m_minor || - m_realBuild != m_build; + return (m_real != m_reported); } -QString WindowsVersion::toString() const +const WindowsInfo::Version& WindowsInfo::reportedVersion() const { - QStringList sl; + return m_reported; +} + +const WindowsInfo::Version& WindowsInfo::realVersion() const +{ + return m_real; +} + +const WindowsInfo::Release& WindowsInfo::release() const +{ + return m_release; +} + +std::optional WindowsInfo::isElevated() const +{ + return m_elevated; +} - const QString version = QString("%1.%2.%3") - .arg(m_major).arg(m_minor).arg(m_build); +QString WindowsInfo::toString() const +{ + QStringList sl; - const QString realVersion = QString("%1.%2.%3") - .arg(m_realMajor).arg(m_realMinor).arg(m_realBuild); + const QString reported = m_reported.toString(); + const QString real = m_real.toString(); - sl.push_back("version: " + version); + // version + sl.push_back("version: " + reported); + // real version if different if (compatibilityMode()) { - sl.push_back("real version: " + realVersion); + sl.push_back("real version: " + real); } - if (!m_buildLab.isEmpty()) { - sl.push_back(m_buildLab); + // build.UBR, such as 17763.557 + if (m_release.UBR != 0) { + DWORD build = 0; + + if (compatibilityMode()) { + build = m_real.build; + } else { + build = m_reported.build; + } + + sl.push_back(QString("%1.%2").arg(build).arg(m_release.UBR)); } - if (!m_productName.isEmpty()) { - sl.push_back(m_productName); + // release ID + if (!m_release.ID.isEmpty()) { + sl.push_back("release " + m_release.ID); } - if (!m_releaseID.isEmpty()) { - sl.push_back("build " + m_releaseID); + // buildlab string + if (!m_release.buildLab.isEmpty()) { + sl.push_back(m_release.buildLab); } - if (m_UBR != 0) { - sl.push_back(QString("%1").arg(m_UBR)); + // product name + if (!m_release.productName.isEmpty()) { + sl.push_back(m_release.productName); } + // elevated QString elevated = "?"; if (m_elevated.has_value()) { elevated = (*m_elevated ? "yes" : "no"); @@ -707,36 +778,14 @@ QString WindowsVersion::toString() const return sl.join(", "); } -void WindowsVersion::getVersion() -{ - std::unique_ptr ntdll(LoadLibraryW(L"ntdll.dll")); - - if (!ntdll) { - qCritical() << "failed to load ntdll.dll while getting version"; - return; - } - - getRealVersion(ntdll.get()); - getReportedVersion(ntdll.get()); -} - -void WindowsVersion::getRealVersion(HINSTANCE ntdll) +WindowsInfo::Version WindowsInfo::getReportedVersion(HINSTANCE ntdll) const { - using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); - - auto* RtlGetNtVersionNumbers = reinterpret_cast( - GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + // windows has been deprecating pretty much all the functions having to do + // with getting version information because apparently, people keep misusing + // them for feature detection + // + // there's still RtlGetVersion() though - if (RtlGetNtVersionNumbers) { - DWORD build = 0; - RtlGetNtVersionNumbers(&m_realMajor, &m_realMinor, &build); - - m_realBuild = 0x0fffffff & build; - } -} - -void WindowsVersion::getReportedVersion(HINSTANCE ntdll) -{ using RtlGetVersionType = NTSTATUS (NTAPI)(PRTL_OSVERSIONINFOW); auto* RtlGetVersion = reinterpret_cast( @@ -744,39 +793,81 @@ void WindowsVersion::getReportedVersion(HINSTANCE ntdll) if (!RtlGetVersion) { qCritical() << "RtlGetVersion() not found in ntdll.dll"; - return; + return {}; } OSVERSIONINFOEX vi = {}; vi.dwOSVersionInfoSize = sizeof(vi); + // this apparently never fails RtlGetVersion((RTL_OSVERSIONINFOW*)&vi); - m_major = vi.dwMajorVersion; - m_minor = vi.dwMinorVersion; - m_build = vi.dwBuildNumber; + return {vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber}; +} + +WindowsInfo::Version WindowsInfo::getRealVersion(HINSTANCE ntdll) const +{ + // getting the actual windows version is more difficult because all the + // functions are lying when running in compatibility mode + // + // RtlGetNtVersionNumbers() is an undocumented function that seems to work + // fine, but it might not in the future + + using RtlGetNtVersionNumbersType = void (NTAPI)(DWORD*, DWORD*, DWORD*); + + auto* RtlGetNtVersionNumbers = reinterpret_cast( + GetProcAddress(ntdll, "RtlGetNtVersionNumbers")); + + if (!RtlGetNtVersionNumbers) { + qCritical() << "RtlGetNtVersionNumbers not found in ntdll.dll"; + return {}; + } + + DWORD major=0, minor=0, build=0; + RtlGetNtVersionNumbers(&major, &minor, &build); + + // for whatever reason, the build number has 0xf0000000 set + build = 0x0fffffff & build; + + return {major, minor, build}; } -void WindowsVersion::getRelease() +WindowsInfo::Release WindowsInfo::getRelease() const { + // there are several interesting items in the registry, but most of them + // are undocumented, not always available, and localizable + // + // most of them are used to provide as much information as possible in case + // any of the other versions fail to work + QSettings settings( R"(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion)", QSettings::NativeFormat); - m_buildLab = settings.value("BuildLabEx", "").toString(); - if (m_buildLab.isEmpty()) { - m_buildLab = settings.value("BuildLab", "").toString(); - if (m_buildLab.isEmpty()) { - m_buildLab = settings.value("BuildBranch", "").toString(); + Release r; + + // buildlab seems to be an internal name from the build system + r.buildLab = settings.value("BuildLabEx", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildLab", "").toString(); + if (r.buildLab.isEmpty()) { + r.buildLab = settings.value("BuildBranch", "").toString(); } } - m_productName = settings.value("ProductName", "").toString(); - m_releaseID = settings.value("ReleaseId", "").toString(); - m_UBR = settings.value("UBR", 0).toUInt(); + // localized name of windows, such as "Windows 10 Pro" + r.productName = settings.value("ProductName", "").toString(); + + // release ID, such as 1803 + r.ID = settings.value("ReleaseId", "").toString(); + + // some other build number, shown in winver.exe + r.UBR = settings.value("UBR", 0).toUInt(); + + return r; } -void WindowsVersion::getElevated() +std::optional WindowsInfo::getElevated() const { std::unique_ptr token; @@ -790,7 +881,7 @@ void WindowsVersion::getElevated() << "while trying to check if process is elevated, " << "OpenProcessToken() failed: " << formatSystemMessage(e); - return; + return {}; } token.reset(rawToken); @@ -806,10 +897,10 @@ void WindowsVersion::getElevated() << "while trying to check if process is elevated, " << "GetTokenInformation() failed: " << formatSystemMessage(e); - return; + return {}; } - m_elevated = (e.TokenIsElevated != 0); + return (e.TokenIsElevated != 0); } } // namespace env diff --git a/src/shared/util.h b/src/shared/util.h index 232a97bb..3ec677f4 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -52,22 +52,55 @@ bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); namespace env { +// represents one module +// class Module { public: explicit Module(QString path, std::size_t fileSize); + // returns the module's path + // const QString& path() const; + + // returns the module's path in lowercase and using forward slashes + // QString displayPath() const; + // returns the size in bytes, may be 0 + // std::size_t fileSize() const; + + // returns the x.x.x.x version embedded from the version info, may be empty + // const QString& version() const; + + // returns the FileVersion entry from the resource file, returns + // "(no version)" if not available + // const QString& versionString() const; + + // returns the build date from the version info, or the creation time of the + // file on the filesystem, may be empty + // + const QDateTime& timestamp() const; + + // returns the md5 of the file, may be empty for system files + // + const QString& md5() const; + + // converts timestamp() to a string for display, returns "(no timestamp)" if + // not available + // QString timestampString() const; + // returns a string with all the above information on one line + // QString toString() const; private: + // contains the information from the version resource + // struct FileInfo { VS_FIXEDFILEINFO ffi; @@ -81,51 +114,151 @@ private: QString m_versionString; QString m_md5; + // returns information from the version resource + // FileInfo getFileInfo() const; + // uses VS_FIXEDFILEINFO to build the version string + // QString getVersion(const VS_FIXEDFILEINFO& fi) const; + + // uses the file date from VS_FIXEDFILEINFO if available, or gets the + // creation date on the file + // QDateTime getTimestamp(const VS_FIXEDFILEINFO& fi) const; + + // returns the md5 hash unless the path contains "\windows\" + // QString getMD5() const; + // gets VS_FIXEDFILEINFO from the file version info buffer + // VS_FIXEDFILEINFO getFixedFileInfo(std::byte* buffer) const; + + // gets FileVersion from the file version info buffer + // QString getFileDescription(std::byte* buffer) const; }; -class WindowsVersion +// a variety of information on windows +// +class WindowsInfo { public: - WindowsVersion(); + struct Version + { + DWORD major=0, minor=0, build=0; + + QString toString() const + { + return QString("%1.%2.%3").arg(major).arg(minor).arg(build); + } + + friend bool operator==(const Version& a, const Version& b) + { + return + a.major == b.major && + a.minor == b.minor && + a.build == b.build; + } + + friend bool operator!=(const Version& a, const Version& b) + { + return !(a == b); + } + }; + + struct Release + { + // the BuildLab entry from the registry, may be empty + QString buildLab; + + // product name such as "Windows 10 Pro", may not be in English, may be + // empty + QString productName; + + // release ID such as 1809, may be mepty + QString ID; + // some sub-build number, undocumented, may be empty + DWORD UBR; + + Release() + : UBR(0) + { + } + }; + + + WindowsInfo(); + + // tries to guess whether this process is running in compatibility mode + // bool compatibilityMode() const; + + // returns the Windows version, may not correspond to the actual version + // if the process is running in compatibility mode + // + const Version& reportedVersion() const; + + // tries to guess the real Windows version that's running, can be empty + // + const Version& realVersion() const; + + // various information about the current release + // + const Release& release() const; + + // whether this process is running as administrator, may be empty if the + // information is not available + std::optional isElevated() const; + + // returns a string with all the above information on one line + // QString toString() const; private: - DWORD m_realMajor, m_realMinor, m_realBuild; - DWORD m_major, m_minor, m_build; - QString m_buildLab, m_productName, m_releaseID; - DWORD m_UBR; + Version m_reported, m_real; + Release m_release; std::optional m_elevated; - void getVersion(); - void getRealVersion(HINSTANCE ntdll); - void getReportedVersion(HINSTANCE ntdll); - void getRelease(); - void getElevated(); + // uses RtlGetVersion() to get the version number as reported by Windows + // + Version getReportedVersion(HINSTANCE ntdll) const; + + // uses RtlGetNtVersionNumbers() to get the real version number + // + Version getRealVersion(HINSTANCE ntdll) const; + + // gets various information from the registry + // + Release getRelease() const; + + // gets whether the process is elevated + // + std::optional getElevated() const; }; +// represents the process's environment +// class Environment { public: Environment(); + // list of loaded modules in the current process + // const std::vector& loadedModules(); - const WindowsVersion& windowsVersion() const; + + // information about the operating system + // + const WindowsInfo& windowsInfo() const; private: std::vector m_modules; - WindowsVersion m_windows; + WindowsInfo m_windows; void getLoadedModules(); }; -- cgit v1.3.1 From 5a74b02442302fb484b1db68cf0ce1736af4911c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 03:41:32 -0400 Subject: security products --- src/main.cpp | 7 ++ src/shared/util.cpp | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/shared/util.h | 21 ++++ 3 files changed, 308 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 6c3e40be..518d31a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -476,10 +476,17 @@ int runApplication(MOApplication &application, SingleInstance &instance, qWarning() << "MO seems to be running in compatibility mode"; } + qDebug().nospace().noquote() << "security features:"; + for (const auto& sf : env.securityFeatures()) { + qDebug().nospace().noquote() << " . " << sf.toString(); + } + qDebug() << "modules loaded in process:"; for (const auto& m : env.loadedModules()) { qDebug().nospace().noquote() << " . " << m.toString(); } + + return 0; } QString dataPath = application.property("dataPath").toString(); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 70adb791..aa2e8d0f 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -30,6 +30,11 @@ along with Mod Organizer. If not, see . #include #include +#include +#include +#include +#pragma comment(lib, "Wbemuuid.lib") + using MOBase::formatSystemMessage; namespace MOShared { @@ -283,10 +288,153 @@ struct LibraryFreer } }; +struct COMReleaser +{ + void operator()(IUnknown* p) + { + if (p) { + p->Release(); + } + } +}; + + +class WMI +{ +public: + class failed {}; + + WMI(const std::string& ns) + { + try + { + createLocator(); + createService(ns); + setSecurity(); + } + catch(failed&) + { + } + } + + template + void query(const std::string& q, F&& f) + { + if (!m_locator || !m_service) { + return; + } + + auto enumerator = getEnumerator(q); + + for (;;) + { + std::unique_ptr object; + + { + IWbemClassObject* rawObject = nullptr; + ULONG count = 0; + auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); + + if (count == 0) { + break; + } + + object.reset(rawObject); + } + + f(object.get()); + } + } + + std::unique_ptr getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret)) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessage(ret); + + return {}; + } + + return std::unique_ptr(rawEnumerator); + } + +private: + std::unique_ptr m_locator; + std::unique_ptr m_service; + + void createLocator() + { + void* rawLocator = nullptr; + + const auto ret = CoCreateInstance( + CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, &rawLocator); + + if (FAILED(ret)) { + qCritical() + << "CoCreateInstance for WbemLocator failed, " + << formatSystemMessage(ret); + + throw failed(); + } + + m_locator.reset(static_cast(rawLocator)); + } + + void createService(const std::string& ns) + { + IWbemServices* rawService = nullptr; + + const auto res = m_locator->ConnectServer( + _bstr_t(ns.c_str()), + nullptr, nullptr, nullptr, 0, nullptr, nullptr, + &rawService); + + if (FAILED(res)) { + qCritical() + << "locator->ConnectServer() failed for namespace " + << "'" << QString::fromStdString(ns) << "', " + << formatSystemMessage(res); + + throw failed(); + } + + m_service.reset(rawService); + } + + void setSecurity() + { + auto ret = CoSetProxyBlanket( + m_service.get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, 0, EOAC_NONE); + + if (FAILED(ret)) + { + qCritical() + << "CoSetProxyBlanket() failed, " << formatSystemMessage(ret); + + throw failed(); + } + } +}; + Environment::Environment() { getLoadedModules(); + getSecurityFeatures(); } const std::vector& Environment::loadedModules() @@ -299,6 +447,11 @@ const WindowsInfo& Environment::windowsInfo() const return m_windows; } +const std::vector& Environment::securityFeatures() const +{ + return m_security; +} + void Environment::getLoadedModules() { std::unique_ptr snapshot(CreateToolhelp32Snapshot( @@ -357,6 +510,59 @@ void Environment::getLoadedModules() }); } +void Environment::getSecurityFeatures() +{ + WMI wmi("root\\SecurityCenter2"); + std::map map; + + auto handleProduct = [&](auto* o) { + VARIANT prop; + + auto ret = o->Get(L"displayName", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() << "failed to get displayName, " << formatSystemMessage(ret); + return; + } + + const std::wstring name = prop.bstrVal; + VariantClear(&prop); + + ret = o->Get(L"productState", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() << "failed to get productState, " << formatSystemMessage(ret); + return; + } + + const DWORD state = prop.ulVal; + VariantClear(&prop); + + ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); + if (FAILED(ret)) { + qCritical() << "failed to get instanceGuid, " << formatSystemMessage(ret); + return; + } + + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); + VariantClear(&prop); + + + auto itor = map.find(guid); + + if (itor == map.end()) { + map.insert({ + guid, SecurityFeature(QString::fromStdWString(name), state)}); + } + }; + + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + + for (auto&& p : map) { + m_security.push_back(p.second); + } +} + Module::Module(QString path, std::size_t fileSize) : m_path(std::move(path)), m_fileSize(fileSize) @@ -508,7 +714,7 @@ VS_FIXEDFILEINFO Module::getFixedFileInfo(std::byte* buffer) const return {}; } - const auto* fi = reinterpret_cast(valuePointer); + const auto* fi = static_cast(valuePointer); // signature is always 0xfeef04bd if (fi->dwSignature != 0xfeef04bd) { @@ -551,7 +757,7 @@ QString Module::getFileDescription(std::byte* buffer) const } // using the first language in the list to get FileVersion - const auto* lcp = reinterpret_cast(valuePointer); + const auto* lcp = static_cast(valuePointer); const auto subBlock = QString("\\StringFileInfo\\%1%2\\FileVersion") .arg(lcp->wLanguage, 4, 16, QChar('0')) @@ -567,7 +773,7 @@ QString Module::getFileDescription(std::byte* buffer) const // valueSize includes the null terminator return QString::fromWCharArray( - reinterpret_cast(valuePointer), valueSize - 1); + static_cast(valuePointer), valueSize - 1); } QString Module::getVersion(const VS_FIXEDFILEINFO& fi) const @@ -903,6 +1109,77 @@ std::optional WindowsInfo::getElevated() const return (e.TokenIsElevated != 0); } + +SecurityFeature::SecurityFeature(QString name, DWORD state) + : m_name(std::move(name)), m_state(state) +{ +} + +const QString& SecurityFeature::name() const +{ + return m_name; +} + +QString SecurityFeature::toString() const +{ + QString s; + + s += m_name + " "; + + const auto provider = (m_state >> 16) & 0xff; + const auto scanner = (m_state >> 8) & 0xff; + const auto definitions = m_state & 0xff; + + QStringList ps; + if (provider & WSC_SECURITY_PROVIDER_FIREWALL) { + ps.push_back("firewall"); + } + + if (provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + ps.push_back("autoupdate"); + } + + if (provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + ps.push_back("antivirus"); + } + + if (provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + ps.push_back("antispyware"); + } + + if (provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + ps.push_back("settings"); + } + + if (provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + ps.push_back("uac"); + } + + if (provider & WSC_SECURITY_PROVIDER_SERVICE) { + ps.push_back("service"); + } + + if (ps.empty()) { + s += "(doesn't provide anything)"; + } else { + s += "(" + ps.join("|") + ")"; + } + + if (scanner & 0x10) { + s += ", active"; + } else { + s += ", inactive"; + } + + if (definitions == 0) { + s += ", definitions up to date"; + } else { + s += ", definitions outdated"; + } + + return s; +} + } // namespace env } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 3ec677f4..6b842f8c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -241,6 +241,21 @@ private: }; +class SecurityFeature +{ +public: + SecurityFeature(QString name, DWORD state); + + const QString& name() const; + + QString toString() const; + +private: + QString m_name; + DWORD m_state; +}; + + // represents the process's environment // class Environment @@ -256,11 +271,17 @@ public: // const WindowsInfo& windowsInfo() const; + // information about the installed antivirus + // + const std::vector& securityFeatures() const; + private: std::vector m_modules; WindowsInfo m_windows; + std::vector m_security; void getLoadedModules(); + void getSecurityFeatures(); }; } // namespace env -- cgit v1.3.1 From dfe8093c1ad16e1611c12e88d52d1ac38371d3f6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 08:42:58 -0400 Subject: added --crashdump to generate dumps of a running MO process added dump_running_process.bat to start another instance of MO with that flag --- CMakeLists.txt | 2 + dump_running_process.bat | 2 + src/main.cpp | 45 +++++- src/settings.cpp | 10 +- src/shared/util.cpp | 392 ++++++++++++++++++++++++++++++++++++++++++++--- src/shared/util.h | 17 ++ 6 files changed, 443 insertions(+), 25 deletions(-) create mode 100644 dump_running_process.bat (limited to 'src') diff --git a/CMakeLists.txt b/CMakeLists.txt index ced097e1..94c76373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,3 +29,5 @@ if(NOT EXISTS ${vcxproj_user_file}) "\n" "\n") endif() + +INSTALL(FILES dump_running_process.bat DESTINATION bin) diff --git a/dump_running_process.bat b/dump_running_process.bat new file mode 100644 index 00000000..4697fe5e --- /dev/null +++ b/dump_running_process.bat @@ -0,0 +1,2 @@ +pushd "%~dp0" +start ModOrganizer.exe --crashdump diff --git a/src/main.cpp b/src/main.cpp index 518d31a0..f74a9cf4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -485,8 +485,6 @@ int runApplication(MOApplication &application, SingleInstance &instance, for (const auto& m : env.loadedModules()) { qDebug().nospace().noquote() << " . " << m.toString(); } - - return 0; } QString dataPath = application.property("dataPath").toString(); @@ -682,9 +680,52 @@ int runApplication(MOApplication &application, SingleInstance &instance, } } +int doCoreDump(env::CoreDumpTypes type) +{ + // open a console + AllocConsole(); + + // redirect stdin, stdout and stderr to it + FILE* in=nullptr; + FILE* out=nullptr; + FILE* err=nullptr; + freopen_s(&in, "CONIN$", "r", stdin); + freopen_s(&out, "CONOUT$", "w", stdout); + freopen_s(&err, "CONOUT$", "w", stderr); + + // dump + const auto b = env::coredumpOther(type); + if (!b) { + std::wcerr << L"\n>>>> a minidump file was not written\n\n"; + } + + std::wcerr << L"Press enter to continue..."; + std::wcin.get(); + + // close redirected handles + std::fclose(err); + std::fclose(out); + std::fclose(in); + + // close console + FreeConsole(); + + return (b ? 0 : 1); +} int main(int argc, char *argv[]) { + // handle --crashdump first + for (int i=1; i. #include #include #include -#include #include +#include + +#include #include #include @@ -36,6 +38,8 @@ along with Mod Organizer. If not, see . #pragma comment(lib, "Wbemuuid.lib") using MOBase::formatSystemMessage; +using MOBase::formatSystemMessageQ; +namespace fs = std::filesystem; namespace MOShared { @@ -276,6 +280,9 @@ struct HandleCloser } }; +using HandlePtr = std::unique_ptr; + + struct LibraryFreer { using pointer = HINSTANCE; @@ -362,7 +369,7 @@ public: { qCritical() << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessage(ret); + << formatSystemMessageQ(ret); return {}; } @@ -385,7 +392,7 @@ private: if (FAILED(ret)) { qCritical() << "CoCreateInstance for WbemLocator failed, " - << formatSystemMessage(ret); + << formatSystemMessageQ(ret); throw failed(); } @@ -406,7 +413,7 @@ private: qCritical() << "locator->ConnectServer() failed for namespace " << "'" << QString::fromStdString(ns) << "', " - << formatSystemMessage(res); + << formatSystemMessageQ(res); throw failed(); } @@ -423,7 +430,7 @@ private: if (FAILED(ret)) { qCritical() - << "CoSetProxyBlanket() failed, " << formatSystemMessage(ret); + << "CoSetProxyBlanket() failed, " << formatSystemMessageQ(ret); throw failed(); } @@ -454,7 +461,7 @@ const std::vector& Environment::securityFeatures() const void Environment::getLoadedModules() { - std::unique_ptr snapshot(CreateToolhelp32Snapshot( + HandlePtr snapshot(CreateToolhelp32Snapshot( TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); if (snapshot.get() == INVALID_HANDLE_VALUE) @@ -463,7 +470,7 @@ void Environment::getLoadedModules() qCritical().nospace().noquote() << "CreateToolhelp32Snapshot() failed, " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return; } @@ -477,7 +484,7 @@ void Environment::getLoadedModules() const auto e = GetLastError(); qCritical().nospace().noquote() - << "Module32First() failed, " << formatSystemMessage(e); + << "Module32First() failed, " << formatSystemMessageQ(e); return; } @@ -498,7 +505,7 @@ void Environment::getLoadedModules() } qCritical().nospace().noquote() - << "Module32Next() failed, " << formatSystemMessage(e); + << "Module32Next() failed, " << formatSystemMessageQ(e); break; } @@ -520,7 +527,7 @@ void Environment::getSecurityFeatures() auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get displayName, " << formatSystemMessage(ret); + qCritical() << "failed to get displayName, " << formatSystemMessageQ(ret); return; } @@ -529,7 +536,10 @@ void Environment::getSecurityFeatures() ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get productState, " << formatSystemMessage(ret); + qCritical() + << "failed to get productState, " + << formatSystemMessageQ(ret); + return; } @@ -538,7 +548,10 @@ void Environment::getSecurityFeatures() ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get instanceGuid, " << formatSystemMessage(ret); + qCritical() + << "failed to get instanceGuid, " + << formatSystemMessageQ(ret); + return; } @@ -673,7 +686,7 @@ Module::FileInfo Module::getFileInfo() const qCritical().nospace().noquote() << "GetFileVersionInfoSizeW() failed on '" << m_path << "', " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -686,7 +699,7 @@ Module::FileInfo Module::getFileInfo() const qCritical().nospace().noquote() << "GetFileVersionInfoW() failed on '" << m_path << "', " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -804,7 +817,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const // time on the file // opening the file - std::unique_ptr h(CreateFileW( + HandlePtr h(CreateFileW( m_path.toStdWString().c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)); @@ -813,7 +826,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const qCritical().nospace().noquote() << "can't open file '" << m_path << "' for timestamp, " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -823,7 +836,7 @@ QDateTime Module::getTimestamp(const VS_FIXEDFILEINFO& fi) const const auto e = GetLastError(); qCritical().nospace().noquote() << "can't get file time for '" << m_path << "', " - << formatSystemMessage(e); + << formatSystemMessageQ(e); return {}; } @@ -1075,7 +1088,7 @@ WindowsInfo::Release WindowsInfo::getRelease() const std::optional WindowsInfo::getElevated() const { - std::unique_ptr token; + HandlePtr token; { HANDLE rawToken = 0; @@ -1085,7 +1098,7 @@ std::optional WindowsInfo::getElevated() const qCritical() << "while trying to check if process is elevated, " - << "OpenProcessToken() failed: " << formatSystemMessage(e); + << "OpenProcessToken() failed: " << formatSystemMessageQ(e); return {}; } @@ -1101,7 +1114,7 @@ std::optional WindowsInfo::getElevated() const qCritical() << "while trying to check if process is elevated, " - << "GetTokenInformation() failed: " << formatSystemMessage(e); + << "GetTokenInformation() failed: " << formatSystemMessageQ(e); return {}; } @@ -1180,6 +1193,345 @@ QString SecurityFeature::toString() const return s; } + +struct Process +{ + std::wstring filename; + DWORD pid; + + Process(std::wstring f, DWORD id) + : filename(std::move(f)), pid(id) + { + } +}; + +std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) +{ + DWORD bufferSize = MAX_PATH; + + for (int tries=0; tries<10; ++tries) + { + auto buffer = std::make_unique(bufferSize + 1); + std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); + + DWORD writtenSize = 0; + + if (process == INVALID_HANDLE_VALUE) { + // query this process + writtenSize = GetModuleFileNameW(0, buffer.get(), bufferSize); + } else { + // query another process + writtenSize = GetModuleBaseNameW(process, 0, buffer.get(), bufferSize); + } + + if (writtenSize == 0) { + const auto e = GetLastError(); + std::wcerr << formatSystemMessage(e) << L"\n"; + break; + } else if (writtenSize >= bufferSize) { + // buffer is too small, try again + bufferSize *= 2; + } else { + // if GetModuleFileName() works, `writtenSize` does not include the null + // terminator + const std::wstring s(buffer.get(), writtenSize); + const fs::path path(s); + + return path.filename().native(); + } + } + + + std::wstring what; + if (process == INVALID_HANDLE_VALUE) { + what = L"the current process"; + } else { + what = L"pid " + std::to_wstring(reinterpret_cast(process)); + } + + std::wcerr << L"failed to get filename for " << what << L"\n"; + return {}; +} + +std::vector runningProcessesIds() +{ + // initial size of 300 processes, unlikely to be more than that + std::size_t size = 300; + + for (int tries=0; tries<10; ++tries) { + auto ids = std::make_unique(size); + std::fill(ids.get(), ids.get() + size, 0); + + DWORD bytesGiven = static_cast(size * sizeof(ids[0])); + DWORD bytesWritten = 0; + + if (!EnumProcesses(ids.get(), bytesGiven, &bytesWritten)) + { + const auto e = GetLastError(); + + std::wcerr + << L"failed to enumerate processes, " + << formatSystemMessage(e) << L"\n"; + + return {}; + } + + if (bytesWritten == bytesGiven) { + size *= 2; + continue; + } + + const auto count = bytesWritten / sizeof(ids[0]); + return std::vector(ids.get(), ids.get() + count); + } + + std::cerr << L"too many processes to enumerate"; + return {}; +} + +std::vector runningProcesses() +{ + const auto pids = runningProcessesIds(); + std::vector v; + + for (const auto& pid : pids) { + if (pid == 0) { + // the idle process seems to be picked up by EnumProcesses() + continue; + } + + HandlePtr h(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!h) { + const auto e = GetLastError(); + + if (e != ERROR_ACCESS_DENIED) { + // don't log access denied, will happen a lot when not elevated + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + } + + continue; + } + + auto filename = processFilename(h.get()); + if (!filename.empty()) { + v.emplace_back(std::move(filename), pid); + } + } + + return v; +} + +DWORD findOtherPid() +{ + const std::wstring defaultName = L"ModOrganizer.exe"; + + std::wclog << L"looking for the other process...\n"; + + const auto thisPid = GetCurrentProcessId(); + std::wclog << L"this process id is " << thisPid << L"\n"; + + auto filename = processFilename(); + if (filename.empty()) { + std::wcerr + << L"can't get current process filename, defaulting to " + << defaultName << L"\n"; + + filename = defaultName; + } else { + std::wclog << L"this process filename is " << filename << L"\n"; + } + + const auto processes = runningProcesses(); + std::wclog << L"there are " << processes.size() << L" processes running\n"; + + for (const auto& p : processes) { + if (p.filename == filename) { + if (p.pid != thisPid) { + return p.pid; + } + } + } + + std::wclog + << L"no process with this filename\n" + << L"MO may not be running, or it may be running as administrator\n" + << L"you can try running this again as administrator\n"; + + return 0; +} + + +std::wstring tempDir() +{ + const DWORD bufferSize = MAX_PATH + 1; + wchar_t buffer[bufferSize + 1] = {}; + + const auto written = GetTempPathW(bufferSize, buffer); + if (written == 0) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to get temp path, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + // `written` does not include the null terminator + return std::wstring(buffer, buffer + written); +} + +HandlePtr tempFile(const std::wstring dir) +{ + const auto now = std::time(0); + const auto tm = std::gmtime(&now); + + std::wostringstream oss; + oss + << L"ModOrganizer-" + << std::setw(4) << (1900 + tm->tm_year) + << std::setw(2) << std::setfill(L'0') << (tm->tm_mon + 1) + << std::setw(2) << std::setfill(L'0') << tm->tm_mday << "T" + << std::setw(2) << std::setfill(L'0') << tm->tm_hour + << std::setw(2) << std::setfill(L'0') << tm->tm_min + << std::setw(2) << std::setfill(L'0') << tm->tm_sec; + + const std::wstring prefix = oss.str(); + const std::wstring ext = L".dmp"; + + std::wstring path = dir + L"\\" + prefix + ext; + for (int i=0; i<100; ++i) { + std::wclog << L"trying file '" << path << L"'\n"; + + HandlePtr h (CreateFileW( + path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr)); + + if (h.get() != INVALID_HANDLE_VALUE) { + return h; + } + + const auto e = GetLastError(); + if (e != ERROR_FILE_EXISTS) { + // probably no write access + std::wcerr + << L"failed to create dump file, " << formatSystemMessage(e) << L"\n"; + + return {}; + } + + path = dir + L"\\" + prefix + L"-" + std::to_wstring(i + 1) + ext; + } + + std::wcerr << L"can't create dump file, ran out of filenames\n"; + return {}; +} + +HandlePtr dumpFile() +{ + // try the current directory + HandlePtr h = tempFile(L"."); + if (h.get() != INVALID_HANDLE_VALUE) { + return h; + } + + std::wclog << L"cannot write dump file in current directory\n"; + + // try the temp directory + const auto dir = tempDir(); + + if (dir.empty()) { + std::wclog << L"can't get the temp directory\n"; + } else { + h = tempFile(dir.c_str()); + if (h.get() != INVALID_HANDLE_VALUE) { + return h; + } + } + + std::wcerr << L"nowhere to write the dump file\n"; + return {}; +} + +bool createMiniDump(HANDLE process, CoreDumpTypes type) +{ + const DWORD pid = GetProcessId(process); + + const HandlePtr file = dumpFile(); + if (!file) { + return false; + } + + auto flags = _MINIDUMP_TYPE( + MiniDumpNormal | + MiniDumpWithHandleData | + MiniDumpWithUnloadedModules | + MiniDumpWithProcessThreadData); + + if (type == CoreDumpTypes::Data) { + std::wclog << L"writing minidump with data\n"; + flags = _MINIDUMP_TYPE(flags | MiniDumpWithDataSegs); + } else if (type == CoreDumpTypes::Full) { + std::wclog << L"writing full minidump\n"; + flags = _MINIDUMP_TYPE(flags | MiniDumpWithFullMemory); + } else { + std::wclog << L"writing mini minidump\n"; + } + + const auto ret = MiniDumpWriteDump( + process, pid, file.get(), flags, nullptr, nullptr, nullptr); + + if (!ret) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to write mini dump, " << formatSystemMessage(e) << L"\n"; + + return false; + } + + std::wclog << L"minidump written correctly\n"; + return true; +} + + +bool coredump(CoreDumpTypes type) +{ + std::wclog << L"creating minidump for the current process\n"; + return createMiniDump(GetCurrentProcess(), type); +} + +bool coredumpOther(CoreDumpTypes type) +{ + std::wclog << L"creating minidump for an running process\n"; + + const auto pid = findOtherPid(); + if (pid == 0) { + std::wcerr << L"no other process found\n"; + return false; + } + + std::wclog << L"found other process with pid " << pid << L"\n"; + + HandlePtr handle(OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid)); + + if (!handle) { + const auto e = GetLastError(); + + std::wcerr + << L"failed to open process " << pid << L", " + << formatSystemMessage(e) << L"\n"; + + return false; + } + + return createMiniDump(handle.get(), type); +} + } // namespace env } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 6b842f8c..2296651c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -284,6 +284,23 @@ private: void getSecurityFeatures(); }; + +enum class CoreDumpTypes +{ + Mini = 1, + Data, + Full +}; + +// creates a minidump file for the given process +// +bool coredump(CoreDumpTypes type); + +// finds another process with the same name as this one and creates a minidump +// file for it +// +bool coredumpOther(CoreDumpTypes type); + } // namespace env -- cgit v1.3.1 From 5e27a96a27a351701182d493c64de698aaac4ae8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 08:55:24 -0400 Subject: a few comments --- src/shared/util.cpp | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index b14a02b1..36800d5b 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -1205,11 +1205,16 @@ struct Process } }; +// returns the filename of the given process or the current one +// std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) { + // double the buffer size 10 times + const int MaxTries = 10; + DWORD bufferSize = MAX_PATH; - for (int tries=0; tries<10; ++tries) + for (int tries=0; tries(bufferSize + 1); std::fill(buffer.get(), buffer.get() + bufferSize + 1, 0); @@ -1225,6 +1230,7 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) } if (writtenSize == 0) { + // hard failure const auto e = GetLastError(); std::wcerr << formatSystemMessage(e) << L"\n"; break; @@ -1241,6 +1247,7 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) } } + // something failed or the path is way too long to make sense std::wstring what; if (process == INVALID_HANDLE_VALUE) { @@ -1255,10 +1262,13 @@ std::wstring processFilename(HANDLE process=INVALID_HANDLE_VALUE) std::vector runningProcessesIds() { + // double the buffer size 10 times + const int MaxTries = 10; + // initial size of 300 processes, unlikely to be more than that std::size_t size = 300; - for (int tries=0; tries<10; ++tries) { + for (int tries=0; tries(size); std::fill(ids.get(), ids.get() + size, 0); @@ -1277,6 +1287,8 @@ std::vector runningProcessesIds() } if (bytesWritten == bytesGiven) { + // no way to distinguish between an exact fit and not enough space, + // just try again size *= 2; continue; } @@ -1296,7 +1308,7 @@ std::vector runningProcesses() for (const auto& pid : pids) { if (pid == 0) { - // the idle process seems to be picked up by EnumProcesses() + // the idle process has pid 0 and seems to be picked up by EnumProcesses() continue; } @@ -1307,7 +1319,8 @@ std::vector runningProcesses() const auto e = GetLastError(); if (e != ERROR_ACCESS_DENIED) { - // don't log access denied, will happen a lot when not elevated + // don't log access denied, will happen a lot for system processes, even + // when elevated std::wcerr << L"failed to open process " << pid << L", " << formatSystemMessage(e) << L"\n"; @@ -1331,9 +1344,12 @@ DWORD findOtherPid() std::wclog << L"looking for the other process...\n"; + // used to skip the current process below const auto thisPid = GetCurrentProcessId(); std::wclog << L"this process id is " << thisPid << L"\n"; + // getting the filename for this process, assumes the other process has the + // smae one auto filename = processFilename(); if (filename.empty()) { std::wcerr @@ -1345,9 +1361,12 @@ DWORD findOtherPid() std::wclog << L"this process filename is " << filename << L"\n"; } + // getting all running processes const auto processes = runningProcesses(); std::wclog << L"there are " << processes.size() << L" processes running\n"; + // going through processes, trying to find one with the same name and a + // different pid than this process has for (const auto& p : processes) { if (p.filename == filename) { if (p.pid != thisPid) { @@ -1364,7 +1383,6 @@ DWORD findOtherPid() return 0; } - std::wstring tempDir() { const DWORD bufferSize = MAX_PATH + 1; @@ -1386,9 +1404,15 @@ std::wstring tempDir() HandlePtr tempFile(const std::wstring dir) { + // maximum tries of incrementing the counter + const int MaxTries = 100; + + // UTC time and date will be in the filename const auto now = std::time(0); const auto tm = std::gmtime(&now); + // "ModOrganizer-YYYYMMDDThhmmss.dmp", with a possible "-i" appended, where + // i can go until MaxTries std::wostringstream oss; oss << L"ModOrganizer-" @@ -1402,8 +1426,10 @@ HandlePtr tempFile(const std::wstring dir) const std::wstring prefix = oss.str(); const std::wstring ext = L".dmp"; + // first path to try, without counter in it std::wstring path = dir + L"\\" + prefix + ext; - for (int i=0; i<100; ++i) { + + for (int i=0; i Date: Mon, 1 Jul 2019 09:55:42 -0400 Subject: show supported games in error dialog when browsing for a binary --- src/main.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index f74a9cf4..f08e5ad3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -371,8 +371,25 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett } else if(possibleGames.count() == 1) { return selectGame(settings, gameDir, possibleGames[0]); } else { - reportError(gameConfigured ? QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.").arg(gameName).arg(gamePath) - : QObject::tr("No game identified in \"%1\". The directory is required to contain the game binary.").arg(gamePath)); + if (gameConfigured) { + reportError(QObject::tr("%1 not identified in \"%2\". The directory is required to contain the game binary.").arg(gameName).arg(gamePath)); + } else { + QString supportedGames; + + for (IPluginGame * const game : plugins.plugins()) { + supportedGames += "
  • " + game->gameName() + "
  • "; + } + + QString text = QObject::tr( + "No game identified in \"%1\". The directory is required to " + "contain the game binary.

    " + "These are the games supported by Mod Organizer:" + "
      %2
    ") + .arg(gamePath) + .arg(supportedGames); + + reportError(text); + } } } } -- cgit v1.3.1 From 7c1ce768ef38dc194583bd3c522596bd124890f2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 12:38:35 -0400 Subject: now handles windows firewall, which apparently doens't report itself to wmi some more error checking --- src/main.cpp | 6 +- src/shared/util.cpp | 294 ++++++++++++++++++++++++++++++++++++++-------------- src/shared/util.h | 26 +++-- 3 files changed, 239 insertions(+), 87 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index f08e5ad3..c6c87a64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -493,9 +493,9 @@ int runApplication(MOApplication &application, SingleInstance &instance, qWarning() << "MO seems to be running in compatibility mode"; } - qDebug().nospace().noquote() << "security features:"; - for (const auto& sf : env.securityFeatures()) { - qDebug().nospace().noquote() << " . " << sf.toString(); + qDebug().nospace().noquote() << "security products:"; + for (const auto& sp : env.securityProducts()) { + qDebug().nospace().noquote() << " . " << sp.toString(); } qDebug() << "modules loaded in process:"; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 36800d5b..072cee2d 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -35,6 +35,8 @@ along with Mod Organizer. If not, see . #include #include #include +#include + #pragma comment(lib, "Wbemuuid.lib") using MOBase::formatSystemMessage; @@ -305,6 +307,9 @@ struct COMReleaser } }; +template +using COMPtr = std::unique_ptr; + class WMI { @@ -332,17 +337,26 @@ public: } auto enumerator = getEnumerator(q); + if (!enumerator) { + return; + } for (;;) { - std::unique_ptr object; + COMPtr object; { IWbemClassObject* rawObject = nullptr; ULONG count = 0; auto ret = enumerator->Next(WBEM_INFINITE, 1, &rawObject, &count); - if (count == 0) { + if (count == 0 || !rawObject) { + break; + } + + if (FAILED(ret)) { + qCritical() + << "enumerator->next() failed, " << formatSystemMessageQ(ret); break; } @@ -353,33 +367,9 @@ public: } } - std::unique_ptr getEnumerator( - const std::string& query) - { - IEnumWbemClassObject* rawEnumerator = NULL; - - auto ret = m_service->ExecQuery( - bstr_t("WQL"), - bstr_t(query.c_str()), - WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, - NULL, - &rawEnumerator); - - if (FAILED(ret)) - { - qCritical() - << "query '" << QString::fromStdString(query) << "' failed, " - << formatSystemMessageQ(ret); - - return {}; - } - - return std::unique_ptr(rawEnumerator); - } - private: - std::unique_ptr m_locator; - std::unique_ptr m_service; + COMPtr m_locator; + COMPtr m_service; void createLocator() { @@ -389,7 +379,7 @@ private: CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, IID_IWbemLocator, &rawLocator); - if (FAILED(ret)) { + if (FAILED(ret) || !rawLocator) { qCritical() << "CoCreateInstance for WbemLocator failed, " << formatSystemMessageQ(ret); @@ -409,7 +399,7 @@ private: nullptr, nullptr, nullptr, 0, nullptr, nullptr, &rawService); - if (FAILED(res)) { + if (FAILED(res) || !rawService) { qCritical() << "locator->ConnectServer() failed for namespace " << "'" << QString::fromStdString(ns) << "', " @@ -435,13 +425,37 @@ private: throw failed(); } } + + COMPtr getEnumerator( + const std::string& query) + { + IEnumWbemClassObject* rawEnumerator = NULL; + + auto ret = m_service->ExecQuery( + bstr_t("WQL"), + bstr_t(query.c_str()), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &rawEnumerator); + + if (FAILED(ret) || !rawEnumerator) + { + qCritical() + << "query '" << QString::fromStdString(query) << "' failed, " + << formatSystemMessageQ(ret); + + return {}; + } + + return COMPtr(rawEnumerator); + } }; Environment::Environment() { - getLoadedModules(); - getSecurityFeatures(); + m_modules = getLoadedModules(); + m_security = getSecurityProducts(); } const std::vector& Environment::loadedModules() @@ -454,12 +468,12 @@ const WindowsInfo& Environment::windowsInfo() const return m_windows; } -const std::vector& Environment::securityFeatures() const +const std::vector& Environment::securityProducts() const { return m_security; } -void Environment::getLoadedModules() +std::vector Environment::getLoadedModules() const { HandlePtr snapshot(CreateToolhelp32Snapshot( TH32CS_SNAPMODULE32 | TH32CS_SNAPMODULE, GetCurrentProcessId())); @@ -472,7 +486,7 @@ void Environment::getLoadedModules() << "CreateToolhelp32Snapshot() failed, " << formatSystemMessageQ(e); - return; + return {}; } MODULEENTRY32 me = {}; @@ -486,54 +500,88 @@ void Environment::getLoadedModules() qCritical().nospace().noquote() << "Module32First() failed, " << formatSystemMessageQ(e); - return; + return {}; } + std::vector v; + for (;;) { const auto path = QString::fromWCharArray(me.szExePath); - - m_modules.push_back(Module(path, me.modBaseSize)); + if (!path.isEmpty()) { + v.push_back(Module(path, me.modBaseSize)); + } // next module if (!Module32Next(snapshot.get(), &me)) { const auto e = GetLastError(); - if (e == ERROR_NO_MORE_FILES) { - // not an error - break; - } - + // no more modules is not an error + if (e != ERROR_NO_MORE_FILES) { qCritical().nospace().noquote() << "Module32Next() failed, " << formatSystemMessageQ(e); + } break; } } // sorting by display name - std::sort(m_modules.begin(), m_modules.end(), [](auto&& a, auto&& b) { + std::sort(v.begin(), v.end(), [](auto&& a, auto&& b) { return (a.displayPath().compare(b.displayPath(), Qt::CaseInsensitive) < 0); }); + + return v; +} + +std::vector Environment::getSecurityProducts() const +{ + std::vector v; + + { + auto fromWMI = getSecurityProductsFromWMI(); + v.insert( + v.end(), + std::make_move_iterator(fromWMI.begin()), + std::make_move_iterator(fromWMI.end())); + } + + if (auto p=getWindowsFirewall()) { + v.push_back(std::move(*p)); + } + + return v; } -void Environment::getSecurityFeatures() +std::vector Environment::getSecurityProductsFromWMI() const { - WMI wmi("root\\SecurityCenter2"); - std::map map; + // some products may be present in multiple queries, such as a product marked + // as both antivirus and antispyware, but they'll have the same GUID, so use + // that to avoid duplicating entries + std::map map; auto handleProduct = [&](auto* o) { VARIANT prop; + // display name auto ret = o->Get(L"displayName", 0, &prop, 0, 0); if (FAILED(ret)) { - qCritical() << "failed to get displayName, " << formatSystemMessageQ(ret); + qCritical() + << "failed to get displayName, " + << formatSystemMessageQ(ret); + + return; + } + + if (prop.vt != VT_BSTR) { + qCritical() << "displayName is a " << prop.vt << ", not a bstr"; return; } const std::wstring name = prop.bstrVal; VariantClear(&prop); + // product state ret = o->Get(L"productState", 0, &prop, 0, 0); if (FAILED(ret)) { qCritical() @@ -543,9 +591,21 @@ void Environment::getSecurityFeatures() return; } - const DWORD state = prop.ulVal; + if (prop.vt != VT_UI4 && prop.vt != VT_I4) { + qCritical() << "productState is a " << prop.vt << ", is not a VT_UI4"; + return; + } + + DWORD state = 0; + if (prop.vt == VT_I4) { + state = prop.lVal; + } else { + state = prop.ulVal; + } + VariantClear(&prop); + // guid ret = o->Get(L"instanceGuid", 0, &prop, 0, 0); if (FAILED(ret)) { qCritical() @@ -555,25 +615,94 @@ void Environment::getSecurityFeatures() return; } + if (prop.vt != VT_BSTR) { + qCritical() << "instanceGuid is a " << prop.vt << ", is not a bstr"; + return; + } + const QUuid guid(QString::fromWCharArray(prop.bstrVal)); VariantClear(&prop); + const auto provider = static_cast((state >> 16) & 0xff); + const auto scanner = (state >> 8) & 0xff; + const auto definitions = state & 0xff; - auto itor = map.find(guid); + const bool active = ((scanner & 0x10) != 0); + const bool upToDate = (definitions == 0); - if (itor == map.end()) { - map.insert({ - guid, SecurityFeature(QString::fromStdWString(name), state)}); - } + map.insert({ + guid, + {QString::fromStdWString(name), provider, active, upToDate}}); }; - wmi.query("select * from AntivirusProduct", handleProduct); - wmi.query("select * from FirewallProduct", handleProduct); - wmi.query("select * from AntiSpywareProduct", handleProduct); + { + WMI wmi("root\\SecurityCenter2"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + { + WMI wmi("root\\SecurityCenter"); + wmi.query("select * from AntivirusProduct", handleProduct); + wmi.query("select * from FirewallProduct", handleProduct); + wmi.query("select * from AntiSpywareProduct", handleProduct); + } + + std::vector v; for (auto&& p : map) { - m_security.push_back(p.second); + v.push_back(p.second); } + + return v; +} + +std::optional Environment::getWindowsFirewall() const +{ + HRESULT hr = 0; + + COMPtr policy; + + { + void* rawPolicy = nullptr; + + hr = CoCreateInstance( + __uuidof(NetFwPolicy2), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(INetFwPolicy2), &rawPolicy); + + if (FAILED(hr) || !rawPolicy) { + qCritical() + << "CoCreateInstance for NetFwPolicy2 failed, " + << formatSystemMessage(hr); + + return {}; + } + + policy.reset(static_cast(rawPolicy)); + } + + VARIANT_BOOL enabledVariant; + + if (policy) { + hr = policy->get_FirewallEnabled(NET_FW_PROFILE2_PUBLIC, &enabledVariant); + if (FAILED(hr)) + { + qCritical() + << "get_FirewallEnabled failed, " + << formatSystemMessage(hr); + + return {}; + } + } + + const auto enabled = (enabledVariant != VARIANT_FALSE); + if (!enabled) { + return {}; + } + + return SecurityProduct( + "Windows Firewall", WSC_SECURITY_PROVIDER_FIREWALL, true, true); } @@ -1123,52 +1252,67 @@ std::optional WindowsInfo::getElevated() const } -SecurityFeature::SecurityFeature(QString name, DWORD state) - : m_name(std::move(name)), m_state(state) +SecurityProduct::SecurityProduct( + QString name, int provider, + bool active, bool upToDate) : + m_name(std::move(name)), m_provider(provider), + m_active(active), m_upToDate(upToDate) { } -const QString& SecurityFeature::name() const +const QString& SecurityProduct::name() const { return m_name; } -QString SecurityFeature::toString() const +int SecurityProduct::provider() const +{ + return m_provider; +} + +bool SecurityProduct::active() const +{ + return m_active; +} + +bool SecurityProduct::upToDate() const +{ + return m_upToDate; +} + +QString SecurityProduct::toString() const { QString s; s += m_name + " "; - const auto provider = (m_state >> 16) & 0xff; - const auto scanner = (m_state >> 8) & 0xff; - const auto definitions = m_state & 0xff; QStringList ps; - if (provider & WSC_SECURITY_PROVIDER_FIREWALL) { + if (m_provider & WSC_SECURITY_PROVIDER_FIREWALL) { ps.push_back("firewall"); } - if (provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { + if (m_provider & WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS) { ps.push_back("autoupdate"); } - if (provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { + if (m_provider & WSC_SECURITY_PROVIDER_ANTIVIRUS) { ps.push_back("antivirus"); } - if (provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { + if (m_provider & WSC_SECURITY_PROVIDER_ANTISPYWARE) { ps.push_back("antispyware"); } - if (provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { + if (m_provider & WSC_SECURITY_PROVIDER_INTERNET_SETTINGS) { ps.push_back("settings"); } - if (provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { + if (m_provider & WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL) { ps.push_back("uac"); } - if (provider & WSC_SECURITY_PROVIDER_SERVICE) { + if (m_provider & WSC_SECURITY_PROVIDER_SERVICE) { ps.push_back("service"); } @@ -1178,15 +1322,13 @@ QString SecurityFeature::toString() const s += "(" + ps.join("|") + ")"; } - if (scanner & 0x10) { + if (m_active) { s += ", active"; } else { s += ", inactive"; } - if (definitions == 0) { - s += ", definitions up to date"; - } else { + if (!m_upToDate) { s += ", definitions outdated"; } diff --git a/src/shared/util.h b/src/shared/util.h index 2296651c..e0da8a99 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -241,18 +241,25 @@ private: }; -class SecurityFeature +class SecurityProduct { public: - SecurityFeature(QString name, DWORD state); + SecurityProduct( + QString name, int provider, + bool active, bool upToDate); const QString& name() const; + int provider() const; + bool active() const; + bool upToDate() const; QString toString() const; private: QString m_name; - DWORD m_state; + int m_provider; + bool m_active; + bool m_upToDate; }; @@ -271,17 +278,20 @@ public: // const WindowsInfo& windowsInfo() const; - // information about the installed antivirus + // information about the installed security products // - const std::vector& securityFeatures() const; + const std::vector& securityProducts() const; private: std::vector m_modules; WindowsInfo m_windows; - std::vector m_security; + std::vector m_security; - void getLoadedModules(); - void getSecurityFeatures(); + std::vector getLoadedModules() const; + std::vector getSecurityProducts() const; + + std::vector getSecurityProductsFromWMI() const; + std::optional getWindowsFirewall() const; }; -- cgit v1.3.1 From ffebd4c1016265eeebab67d7f22d8f5bfd67703e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 14:51:08 -0400 Subject: a few more comments --- src/shared/util.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src') diff --git a/src/shared/util.h b/src/shared/util.h index e0da8a99..4df1d13c 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -241,6 +241,8 @@ private: }; +// represents a security product, such as an antivirus or a firewall +// class SecurityProduct { public: @@ -248,11 +250,24 @@ public: QString name, int provider, bool active, bool upToDate); + // display name of the product + // const QString& name() const; + + // a bunch of _WSC_SECURITY_PROVIDER flags + // int provider() const; + + // whether the product is active + // bool active() const; + + // whether its definitions are up-to-date + // bool upToDate() const; + // string representation of the above + // QString toString() const; private: -- cgit v1.3.1 From 67bc0d83277b36397766d97218d5ed9b4e25640a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 20:21:07 -0400 Subject: clean up the list widgets and thumnails as well as recheck the tabs when refreshing after a change --- src/modinfodialog.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 188ea956..44934544 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -579,9 +579,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); - ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); - ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } initINITweaks(); @@ -1074,6 +1071,21 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( void ModInfoDialog::refreshFiles() { + // clearing + ui->textFileList->clear(); + ui->iniTweaksList->clear(); + ui->iniFileList->clear(); + ui->inactiveESPList->clear(); + ui->activeESPList->clear(); + ui->imageLabel->setPixmap({}); + + while (ui->thumbnailArea->count() > 0) { + auto* item = ui->thumbnailArea->takeAt(0); + delete item->widget(); + delete item; + } + + if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -1124,6 +1136,10 @@ void ModInfoDialog::refreshFiles() } } } + + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); + ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); + ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) @@ -1189,15 +1205,14 @@ void ModInfoDialog::openTab(int tab) void ModInfoDialog::thumbnailClicked(const QString &fileName) { - QLabel *imageLabel = findChild("imageLabel"); - imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + ui->imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); QImage image(fileName); if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(imageLabel->geometry().width()); + image = image.scaledToWidth(ui->imageLabel->geometry().width()); } else { - image = image.scaledToHeight(imageLabel->geometry().height()); + image = image.scaledToHeight(ui->imageLabel->geometry().height()); } - imageLabel->setPixmap(QPixmap::fromImage(image)); + ui->imageLabel->setPixmap(QPixmap::fromImage(image)); } bool ModInfoDialog::allowNavigateFromTXT() -- cgit v1.3.1 From 59e0c4aa34fec9048d064705863c3269aacd86b5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 17:58:23 -0400 Subject: moved filerenamer to its own .cpp/.h pair --- src/CMakeLists.txt | 3 + src/filerenamer.cpp | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/filerenamer.h | 140 +++++++++++++++++++++++++++++++++++++ src/modinfodialog.cpp | 187 ------------------------------------------------- src/modinfodialog.h | 135 +----------------------------------- 5 files changed, 333 insertions(+), 321 deletions(-) create mode 100644 src/filerenamer.cpp create mode 100644 src/filerenamer.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 645a5a73..f654f9b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,6 +118,7 @@ SET(organizer_SRCS filterwidget.cpp statusbar.cpp apiuseraccount.cpp + filerenamer.cpp shared/windows_error.cpp shared/error_report.cpp @@ -217,6 +218,7 @@ SET(organizer_HDRS filterwidget.h statusbar.h apiuseraccount.h + filerenamer.h shared/windows_error.h shared/error_report.h @@ -402,6 +404,7 @@ set(utilities set(widgets descriptionpage genericicondelegate + filerenamer filterwidget icondelegate lcdnumber diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp new file mode 100644 index 00000000..c5c6782b --- /dev/null +++ b/src/filerenamer.cpp @@ -0,0 +1,189 @@ +#include "filerenamer.h" +#include +#include + +FileRenamer::FileRenamer(QWidget* parent, QFlags flags) + : m_parent(parent), m_flags(flags) +{ + // sanity check for flags + if ((m_flags & (HIDE|UNHIDE)) == 0) { + qCritical("renameFile() missing hide flag"); + // doesn't really matter, it's just for text + m_flags = HIDE; + } +} + +FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) +{ + qDebug().nospace() << "renaming " << oldName << " to " << newName; + + if (QFileInfo(newName).exists()) { + qDebug().nospace() << newName << " already exists"; + + // target file already exists, confirm replacement + auto answer = confirmReplace(newName); + + switch (answer) { + case DECISION_SKIP: { + // user wants to skip this file + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + case DECISION_REPLACE: { + qDebug().nospace() << "removing " << newName; + // user wants to replace the file, so remove it + if (!QFile(newName).remove()) { + qWarning().nospace() << "failed to remove " << newName; + // removal failed, warn the user and allow canceling + if (!removeFailed(newName)) { + qDebug().nospace() << "canceling " << oldName; + // user wants to cancel + return RESULT_CANCEL; + } + + // ignore this file and continue on + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + break; + } + + case DECISION_CANCEL: // fall-through + default: { + // user wants to stop + qDebug().nospace() << "canceling"; + return RESULT_CANCEL; + } + } + } + + // target either didn't exist or was removed correctly + + if (!QFile::rename(oldName, newName)) { + qWarning().nospace() << "failed to rename " << oldName << " to " << newName; + + // renaming failed, warn the user and allow canceling + if (!renameFailed(oldName, newName)) { + // user wants to cancel + qDebug().nospace() << "canceling"; + return RESULT_CANCEL; + } + + // ignore this file and continue on + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + // everything worked + qDebug().nospace() << "successfully renamed " << oldName << " to " << newName; + return RESULT_OK; +} + +FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) +{ + if (m_flags & REPLACE_ALL) { + // user wants to silently replace all + qDebug().nospace() << "user has selected replace all"; + return DECISION_REPLACE; + } + else if (m_flags & REPLACE_NONE) { + // user wants to silently skip all + qDebug().nospace() << "user has selected replace none"; + return DECISION_SKIP; + } + + QString text; + + if (m_flags & HIDE) { + text = QObject::tr("The hidden file \"%1\" already exists. Replace it?").arg(newName); + } + else if (m_flags & UNHIDE) { + text = QObject::tr("The visible file \"%1\" already exists. Replace it?").arg(newName); + } + + auto buttons = QMessageBox::Yes | QMessageBox::No; + if (m_flags & MULTIPLE) { + // only show these buttons when there are multiple files to replace + buttons |= QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel; + } + + const auto answer = QMessageBox::question( + m_parent, QObject::tr("Replace file?"), text, buttons); + + switch (answer) { + case QMessageBox::Yes: + qDebug().nospace() << "user wants to replace"; + return DECISION_REPLACE; + + case QMessageBox::No: + qDebug().nospace() << "user wants to skip"; + return DECISION_SKIP; + + case QMessageBox::YesToAll: + qDebug().nospace() << "user wants to replace all"; + // remember the answer + m_flags |= REPLACE_ALL; + return DECISION_REPLACE; + + case QMessageBox::NoToAll: + qDebug().nospace() << "user wants to replace none"; + // remember the answer + m_flags |= REPLACE_NONE; + return DECISION_SKIP; + + case QMessageBox::Cancel: // fall-through + default: + qDebug().nospace() << "user wants to cancel"; + return DECISION_CANCEL; + } +} + +bool FileRenamer::removeFailed(const QString& name) +{ + QMessageBox::StandardButtons buttons = QMessageBox::Ok; + if (m_flags & MULTIPLE) { + // only show cancel for multiple files + buttons |= QMessageBox::Cancel; + } + + const auto answer = QMessageBox::critical( + m_parent, QObject::tr("File operation failed"), + QObject::tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(name), + buttons); + + if (answer == QMessageBox::Cancel) { + // user wants to stop + qDebug().nospace() << "user wants to cancel"; + return false; + } + + // skip this one and continue + qDebug().nospace() << "user wants to skip"; + return true; +} + +bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) +{ + QMessageBox::StandardButtons buttons = QMessageBox::Ok; + if (m_flags & MULTIPLE) { + // only show cancel for multiple files + buttons |= QMessageBox::Cancel; + } + + const auto answer = QMessageBox::critical( + m_parent, QObject::tr("File operation failed"), + QObject::tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName)), + buttons); + + if (answer == QMessageBox::Cancel) { + // user wants to stop + qDebug().nospace() << "user wants to cancel"; + return false; + } + + // skip this one and continue + qDebug().nospace() << "user wants to skip"; + return true; +} diff --git a/src/filerenamer.h b/src/filerenamer.h new file mode 100644 index 00000000..cd57244c --- /dev/null +++ b/src/filerenamer.h @@ -0,0 +1,140 @@ +#ifndef FILERENAMER_H +#define FILERENAMER_H + +#include + +/** +* Renames individual files and handles dialog boxes to confirm replacements and +* failures with the user +**/ +class FileRenamer +{ +public: + /** + * controls appearance and replacement behaviour; if RENAME_REPLACE_ALL and + * RENAME_REPLACE_NONE are not provided, the user will have the option to + * choose on the first replacement + **/ + enum RenameFlags + { + /** + * this renamer will be used on multiple files, so display additional + * buttons to replace all and for canceling + **/ + MULTIPLE = 0x01, + + /** + * customizes some of the text shown on dialog to mention that files are + * being hidden + **/ + HIDE = 0x02, + + /** + * customizes some of the text shown on dialog to mention that files are + * being unhidden + **/ + UNHIDE = 0x04, + + /** + * silently replaces all existing files + **/ + REPLACE_ALL = 0x08, + + /** + * silently skips all existing files + **/ + REPLACE_NONE = 0x10, + }; + + + /** result of a single rename + * + **/ + enum RenameResults + { + /** + * the user skipped this file + */ + RESULT_SKIP, + + /** + * the file was successfully renamed + */ + RESULT_OK, + + /** + * the user wants to cancel + */ + RESULT_CANCEL + }; + + + /** + * @param parent Parent widget for dialog boxes + **/ + FileRenamer(QWidget* parent, QFlags flags); + + /** + * renames the given file + * @param oldName current filename + * @param newName new filename + * @return whether the file was renamed, skipped or the user wants to cancel + **/ + RenameResults rename(const QString& oldName, const QString& newName); + +private: + /** + *user's decision when replacing + **/ + enum RenameDecision + { + /** + * replace the file + **/ + DECISION_REPLACE, + + /** + * skip the file + **/ + DECISION_SKIP, + + /** + * cancel the whole thing + **/ + DECISION_CANCEL + }; + + /** + * parent widget for dialog boxes + **/ + QWidget* m_parent; + + /** + * flags + **/ + QFlags m_flags; + + /** + * asks the user to replace an existing file, may return early if the user + * has already selected to replace all/none + * @return whether to replace, skip or cancel + **/ + RenameDecision confirmReplace(const QString& newName); + + /** + * removal of a file failed, ask the user to continue or cancel + * @param name The name of the file that failed to be removed + * @return true to continue, false to stop + **/ + bool removeFailed(const QString& name); + + /** + * renaming a file failed, ask the user to continue or cancel + * @param oldName current filename + * @param newName new filename + * @return true to continue, false to stop + **/ + bool renameFailed(const QString& oldName, const QString& newName); +}; + +#endif // FILERENAMER_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 44934544..f553a7be 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -78,193 +78,6 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS const int max_scan_for_context_menu = 50; -FileRenamer::FileRenamer(QWidget* parent, QFlags flags) - : m_parent(parent), m_flags(flags) -{ - // sanity check for flags - if ((m_flags & (HIDE|UNHIDE)) == 0) { - qCritical("renameFile() missing hide flag"); - // doesn't really matter, it's just for text - m_flags = HIDE; - } -} - -FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) -{ - qDebug().nospace() << "renaming " << oldName << " to " << newName; - - if (QFileInfo(newName).exists()) { - qDebug().nospace() << newName << " already exists"; - - // target file already exists, confirm replacement - auto answer = confirmReplace(newName); - - switch (answer) { - case DECISION_SKIP: { - // user wants to skip this file - qDebug().nospace() << "skipping " << oldName; - return RESULT_SKIP; - } - - case DECISION_REPLACE: { - qDebug().nospace() << "removing " << newName; - // user wants to replace the file, so remove it - if (!QFile(newName).remove()) { - qWarning().nospace() << "failed to remove " << newName; - // removal failed, warn the user and allow canceling - if (!removeFailed(newName)) { - qDebug().nospace() << "canceling " << oldName; - // user wants to cancel - return RESULT_CANCEL; - } - - // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; - return RESULT_SKIP; - } - - break; - } - - case DECISION_CANCEL: // fall-through - default: { - // user wants to stop - qDebug().nospace() << "canceling"; - return RESULT_CANCEL; - } - } - } - - // target either didn't exist or was removed correctly - - if (!QFile::rename(oldName, newName)) { - qWarning().nospace() << "failed to rename " << oldName << " to " << newName; - - // renaming failed, warn the user and allow canceling - if (!renameFailed(oldName, newName)) { - // user wants to cancel - qDebug().nospace() << "canceling"; - return RESULT_CANCEL; - } - - // ignore this file and continue on - qDebug().nospace() << "skipping " << oldName; - return RESULT_SKIP; - } - - // everything worked - qDebug().nospace() << "successfully renamed " << oldName << " to " << newName; - return RESULT_OK; -} - -FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) -{ - if (m_flags & REPLACE_ALL) { - // user wants to silently replace all - qDebug().nospace() << "user has selected replace all"; - return DECISION_REPLACE; - } - else if (m_flags & REPLACE_NONE) { - // user wants to silently skip all - qDebug().nospace() << "user has selected replace none"; - return DECISION_SKIP; - } - - QString text; - - if (m_flags & HIDE) { - text = QObject::tr("The hidden file \"%1\" already exists. Replace it?").arg(newName); - } - else if (m_flags & UNHIDE) { - text = QObject::tr("The visible file \"%1\" already exists. Replace it?").arg(newName); - } - - auto buttons = QMessageBox::Yes | QMessageBox::No; - if (m_flags & MULTIPLE) { - // only show these buttons when there are multiple files to replace - buttons |= QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel; - } - - const auto answer = QMessageBox::question( - m_parent, QObject::tr("Replace file?"), text, buttons); - - switch (answer) { - case QMessageBox::Yes: - qDebug().nospace() << "user wants to replace"; - return DECISION_REPLACE; - - case QMessageBox::No: - qDebug().nospace() << "user wants to skip"; - return DECISION_SKIP; - - case QMessageBox::YesToAll: - qDebug().nospace() << "user wants to replace all"; - // remember the answer - m_flags |= REPLACE_ALL; - return DECISION_REPLACE; - - case QMessageBox::NoToAll: - qDebug().nospace() << "user wants to replace none"; - // remember the answer - m_flags |= REPLACE_NONE; - return DECISION_SKIP; - - case QMessageBox::Cancel: // fall-through - default: - qDebug().nospace() << "user wants to cancel"; - return DECISION_CANCEL; - } -} - -bool FileRenamer::removeFailed(const QString& name) -{ - QMessageBox::StandardButtons buttons = QMessageBox::Ok; - if (m_flags & MULTIPLE) { - // only show cancel for multiple files - buttons |= QMessageBox::Cancel; - } - - const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(name), - buttons); - - if (answer == QMessageBox::Cancel) { - // user wants to stop - qDebug().nospace() << "user wants to cancel"; - return false; - } - - // skip this one and continue - qDebug().nospace() << "user wants to skip"; - return true; -} - -bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) -{ - QMessageBox::StandardButtons buttons = QMessageBox::Ok; - if (m_flags & MULTIPLE) { - // only show cancel for multiple files - buttons |= QMessageBox::Cancel; - } - - const auto answer = QMessageBox::critical( - m_parent, QObject::tr("File operation failed"), - QObject::tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName)), - buttons); - - if (answer == QMessageBox::Cancel) { - // user wants to stop - qDebug().nospace() << "user wants to cancel"; - return false; - } - - // skip this one and continue - qDebug().nospace() << "user wants to skip"; - return true; -} - - ExpanderWidget::ExpanderWidget() : m_button(nullptr), m_content(nullptr), opened_(false) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index c4f65d8a..b3ce3d07 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include "plugincontainer.h" #include "organizercore.h" #include "filterwidget.h" +#include "filerenamer.h" #include #include @@ -49,140 +50,6 @@ class QFileSystemModel; class QTreeView; class CategoryFactory; -/** -* Renames individual files and handles dialog boxes to confirm replacements and -* failures with the user -**/ -class FileRenamer -{ -public: - /** - * controls appearance and replacement behaviour; if RENAME_REPLACE_ALL and - * RENAME_REPLACE_NONE are not provided, the user will have the option to - * choose on the first replacement - **/ - enum RenameFlags - { - /** - * this renamer will be used on multiple files, so display additional - * buttons to replace all and for canceling - **/ - MULTIPLE = 0x01, - - /** - * customizes some of the text shown on dialog to mention that files are - * being hidden - **/ - HIDE = 0x02, - - /** - * customizes some of the text shown on dialog to mention that files are - * being unhidden - **/ - UNHIDE = 0x04, - - /** - * silently replaces all existing files - **/ - REPLACE_ALL = 0x08, - - /** - * silently skips all existing files - **/ - REPLACE_NONE = 0x10, - }; - - - /** result of a single rename - * - **/ - enum RenameResults - { - /** - * the user skipped this file - */ - RESULT_SKIP, - - /** - * the file was successfully renamed - */ - RESULT_OK, - - /** - * the user wants to cancel - */ - RESULT_CANCEL - }; - - - /** - * @param parent Parent widget for dialog boxes - **/ - FileRenamer(QWidget* parent, QFlags flags); - - /** - * renames the given file - * @param oldName current filename - * @param newName new filename - * @return whether the file was renamed, skipped or the user wants to cancel - **/ - RenameResults rename(const QString& oldName, const QString& newName); - -private: - /** - *user's decision when replacing - **/ - enum RenameDecision - { - /** - * replace the file - **/ - DECISION_REPLACE, - - /** - * skip the file - **/ - DECISION_SKIP, - - /** - * cancel the whole thing - **/ - DECISION_CANCEL - }; - - /** - * parent widget for dialog boxes - **/ - QWidget* m_parent; - - /** - * flags - **/ - QFlags m_flags; - - /** - * asks the user to replace an existing file, may return early if the user - * has already selected to replace all/none - * @return whether to replace, skip or cancel - **/ - RenameDecision confirmReplace(const QString& newName); - - /** - * removal of a file failed, ask the user to continue or cancel - * @param name The name of the file that failed to be removed - * @return true to continue, false to stop - **/ - bool removeFailed(const QString& name); - - /** - * renaming a file failed, ask the user to continue or cancel - * @param oldName current filename - * @param newName new filename - * @return true to continue, false to stop - **/ - bool renameFailed(const QString& oldName, const QString& newName); -}; - /* Takes a QToolButton and a widget and creates an expandable widget. **/ -- cgit v1.3.1 From 6bd5bed29f3ebcc1155e615d7daa1c9cd1724efb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 18:54:19 -0400 Subject: added initial toolbar, splitter moved most of the text editor stuff to a new TextEditor class --- src/CMakeLists.txt | 3 ++ src/modinfodialog.cpp | 77 ++++++++++++++++-------------- src/modinfodialog.h | 10 +++- src/modinfodialog.ui | 130 +++++++++++++++++++++++++++++++++----------------- src/texteditor.cpp | 76 +++++++++++++++++++++++++++++ src/texteditor.h | 36 ++++++++++++++ 6 files changed, 249 insertions(+), 83 deletions(-) create mode 100644 src/texteditor.cpp create mode 100644 src/texteditor.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f654f9b4..f75ab1e0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -119,6 +119,7 @@ SET(organizer_SRCS statusbar.cpp apiuseraccount.cpp filerenamer.cpp + texteditor.cpp shared/windows_error.cpp shared/error_report.cpp @@ -219,6 +220,7 @@ SET(organizer_HDRS statusbar.h apiuseraccount.h filerenamer.h + texteditor.h shared/windows_error.h shared/error_report.h @@ -414,6 +416,7 @@ set(widgets modidlineedit noeditdelegate qtgroupingproxy + texteditor viewmarkingscrollbar ) diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f553a7be..db5b5fd0 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -36,6 +36,7 @@ along with Mod Organizer. If not, see . #include "pluginlistsortproxy.h" #include "previewgenerator.h" #include "previewdialog.h" +#include "texteditor.h" #include #include @@ -358,6 +359,17 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } + m_textFileEditor.reset(new TextEditor(ui->textFileView)); + + connect( + m_textFileEditor.get(), &TextEditor::changed, + [&](bool b){ onTextFileChanged(b); }); + + ui->tabTextSplitter->setSizes({200, 1}); + ui->tabTextSplitter->setStretchFactor(0, 0); + ui->tabTextSplitter->setStretchFactor(1, 1); + setTextFileWordWrap(true); + // refresh everything but the conflict lists, which are done in exec() because // they depend on restoring the state to some widgets; this refresh has to be // done here because some of the checks below depend on the ui to decide which @@ -1030,9 +1042,12 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) bool ModInfoDialog::allowNavigateFromTXT() { - if (ui->saveTXTButton->isEnabled()) { - int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->textFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (m_textFileEditor->dirty()) { + const int res = QMessageBox::question( + this, tr("Save changes?"), + tr("Save changes to \"%1\"?").arg(m_textFileEditor->filename()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { return false; } else if (res == QMessageBox::Yes) { @@ -1042,7 +1057,6 @@ bool ModInfoDialog::allowNavigateFromTXT() return true; } - bool ModInfoDialog::allowNavigateFromINI() { if (ui->saveButton->isEnabled()) { @@ -1057,14 +1071,14 @@ bool ModInfoDialog::allowNavigateFromINI() return true; } - -void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +void ModInfoDialog::on_textFileList_currentItemChanged( + QListWidgetItem *current, QListWidgetItem *previous) { - QString fullPath = m_RootPath + "/" + current->text(); + const QString fullPath = m_RootPath + "/" + current->text(); - QVariant currentFile = ui->textFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation + if (fullPath == m_textFileEditor->filename()) { + // the new file is the same as the currently displayed file. May be the + // result of a cancellation return; } @@ -1075,17 +1089,12 @@ void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem *current, } } - void ModInfoDialog::openTextFile(const QString &fileName) { - QString encoding; - ui->textFileView->setText(MOBase::readFileText(fileName, &encoding)); - ui->textFileView->setProperty("currentFile", fileName); - ui->textFileView->setProperty("encoding", encoding); - ui->saveTXTButton->setEnabled(false); + m_textFileEditor->load(fileName); + ui->textFileSave->setEnabled(false); } - void ModInfoDialog::openIniFile(const QString &fileName) { QFile iniFile(fileName); @@ -1155,35 +1164,31 @@ void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem *current } - void ModInfoDialog::on_saveButton_clicked() { saveCurrentIniFile(); } - -void ModInfoDialog::on_saveTXTButton_clicked() +void ModInfoDialog::on_textFileSave_clicked() { saveCurrentTextFile(); } +void ModInfoDialog::on_textFileWordWrap_clicked() +{ + setTextFileWordWrap(!m_textFileEditor->wordWrap()); +} + +void ModInfoDialog::setTextFileWordWrap(bool b) +{ + m_textFileEditor->wordWrap(b); + ui->textFileWordWrap->setChecked(b); +} void ModInfoDialog::saveCurrentTextFile() { - QVariant fileNameVar = ui->textFileView->property("currentFile"); - QVariant encodingVar = ui->textFileView->property("encoding"); - if (fileNameVar.isValid() && encodingVar.isValid()) { - QString fileName = fileNameVar.toString(); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->textFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveTXTButton->setEnabled(false); + m_textFileEditor->save(); + ui->textFileSave->setEnabled(false); } @@ -1214,9 +1219,9 @@ void ModInfoDialog::on_iniFileView_textChanged() } -void ModInfoDialog::on_textFileView_textChanged() +void ModInfoDialog::onTextFileChanged(bool b) { - ui->saveTXTButton->setEnabled(true); + ui->textFileSave->setEnabled(b); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index b3ce3d07..85226f45 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -35,6 +35,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ namespace Ui { class QFileSystemModel; class QTreeView; class CategoryFactory; +class TextEditor; /* Takes a QToolButton and a widget and creates an expandable widget. @@ -218,14 +220,15 @@ private slots: void on_saveButton_clicked(); void on_activateESP_clicked(); void on_deactivateESP_clicked(); - void on_saveTXTButton_clicked(); + void on_textFileSave_clicked(); + void on_textFileWordWrap_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); void on_modIDEdit_editingFinished(); void on_sourceGameEdit_currentIndexChanged(int); void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); void on_iniFileView_textChanged(); - void on_textFileView_textChanged(); + void onTextFileChanged(bool b); void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); @@ -304,6 +307,7 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; FilterWidget m_advancedConflictFilter; + std::unique_ptr m_textFileEditor; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); @@ -347,6 +351,8 @@ private: std::vector createGotoActions( const QList& selection); + + void setTextFileWordWrap(bool b); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index eed4e31f..4a29f6e4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -27,49 +27,89 @@ - Textfiles + Text Files - + - - - - 192 - 16777215 - - - - A list of text-files in the mod directory. + + + Qt::Horizontal - - A list of text-files in the mod directory like readmes. + + false + + + A list of text-files in the mod directory. + + + A list of text-files in the mod directory like readmes. + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Save + + + + + + + Word wrap + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - false - - - Save - - - - - @@ -493,12 +533,12 @@ text-align: left;
    2 - - 365 - 200 + + 365 + File @@ -569,12 +609,12 @@ text-align: left; 2 - - 365 - 200 + + 365 + File @@ -1011,7 +1051,7 @@ p, li { white-space: pre-wrap; }
    - + 0 @@ -1024,7 +1064,7 @@ p, li { white-space: pre-wrap; } 200 - + about:blank diff --git a/src/texteditor.cpp b/src/texteditor.cpp new file mode 100644 index 00000000..eef74246 --- /dev/null +++ b/src/texteditor.cpp @@ -0,0 +1,76 @@ +#include "texteditor.h" +#include "utility.h" + +TextEditor::TextEditor(QPlainTextEdit* edit) + : m_edit(edit), m_dirty(false) +{ + m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + wordWrap(true); + + QObject::connect( + m_edit->document(), &QTextDocument::modificationChanged, + [&](bool b){ onChanged(b); }); +} + +bool TextEditor::load(const QString& filename) +{ + m_filename = filename; + m_edit->setPlainText(MOBase::readFileText(filename, &m_encoding)); + m_edit->document()->setModified(false); + + return true; +} + +bool TextEditor::save() +{ + if (m_filename.isEmpty() || m_encoding.isEmpty()) { + return false; + } + + QFile file(m_filename); + file.open(QIODevice::WriteOnly); + file.resize(0); + + QTextCodec* codec = QTextCodec::codecForName(m_encoding.toUtf8()); + QString data = m_edit->toPlainText().replace("\n", "\r\n"); + + file.write(codec->fromUnicode(data)); + m_edit->document()->setModified(false); + + return true; +} + +const QString& TextEditor::filename() const +{ + return m_filename; +} + +void TextEditor::wordWrap(bool b) +{ + if (b) { + m_edit->setLineWrapMode(QPlainTextEdit::WidgetWidth); + } else { + m_edit->setLineWrapMode(QPlainTextEdit::NoWrap); + } +} + +bool TextEditor::wordWrap() const +{ + return (m_edit->lineWrapMode() == QPlainTextEdit::WidgetWidth); +} + +void TextEditor::dirty(bool b) +{ + m_dirty = b; +} + +bool TextEditor::dirty() const +{ + return m_dirty; +} + +void TextEditor::onChanged(bool b) +{ + dirty(b); + emit changed(b); +} diff --git a/src/texteditor.h b/src/texteditor.h new file mode 100644 index 00000000..25a15ad7 --- /dev/null +++ b/src/texteditor.h @@ -0,0 +1,36 @@ +#ifndef MO_TEXTEDITOR_H +#define MO_TEXTEDITOR_H + +#include + +class TextEditor : public QObject +{ + Q_OBJECT + +public: + TextEditor(QPlainTextEdit* edit); + + bool load(const QString& filename); + bool save(); + + const QString& filename() const; + + void wordWrap(bool b); + bool wordWrap() const; + + bool dirty() const; + +signals: + void changed(bool b); + +private: + QPlainTextEdit* m_edit; + QString m_filename; + QString m_encoding; + bool m_dirty; + + void onChanged(bool b); + void dirty(bool b); +}; + +#endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From 9f509f9aa2a1066a223bfd23fb54862f9c988b78 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 19:36:55 -0400 Subject: TextEditor now has a dynamic toolbar --- src/modinfodialog.cpp | 31 --------------- src/modinfodialog.h | 5 --- src/modinfodialog.ui | 68 ++++----------------------------- src/texteditor.cpp | 102 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/texteditor.h | 29 +++++++++++++- 5 files changed, 132 insertions(+), 103 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index db5b5fd0..169623ad 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -361,14 +361,9 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_textFileEditor.reset(new TextEditor(ui->textFileView)); - connect( - m_textFileEditor.get(), &TextEditor::changed, - [&](bool b){ onTextFileChanged(b); }); - ui->tabTextSplitter->setSizes({200, 1}); ui->tabTextSplitter->setStretchFactor(0, 0); ui->tabTextSplitter->setStretchFactor(1, 1); - setTextFileWordWrap(true); // refresh everything but the conflict lists, which are done in exec() because // they depend on restoring the state to some widgets; this refresh has to be @@ -1092,7 +1087,6 @@ void ModInfoDialog::on_textFileList_currentItemChanged( void ModInfoDialog::openTextFile(const QString &fileName) { m_textFileEditor->load(fileName); - ui->textFileSave->setEnabled(false); } void ModInfoDialog::openIniFile(const QString &fileName) @@ -1169,26 +1163,9 @@ void ModInfoDialog::on_saveButton_clicked() saveCurrentIniFile(); } -void ModInfoDialog::on_textFileSave_clicked() -{ - saveCurrentTextFile(); -} - -void ModInfoDialog::on_textFileWordWrap_clicked() -{ - setTextFileWordWrap(!m_textFileEditor->wordWrap()); -} - -void ModInfoDialog::setTextFileWordWrap(bool b) -{ - m_textFileEditor->wordWrap(b); - ui->textFileWordWrap->setChecked(b); -} - void ModInfoDialog::saveCurrentTextFile() { m_textFileEditor->save(); - ui->textFileSave->setEnabled(false); } @@ -1211,20 +1188,12 @@ void ModInfoDialog::saveCurrentIniFile() ui->saveButton->setEnabled(false); } - void ModInfoDialog::on_iniFileView_textChanged() { QPushButton* saveButton = findChild("saveButton"); saveButton->setEnabled(true); } - -void ModInfoDialog::onTextFileChanged(bool b) -{ - ui->textFileSave->setEnabled(b); -} - - void ModInfoDialog::on_activateESP_clicked() { QListWidget *activeESPList = findChild("activeESPList"); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 85226f45..42ef990d 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -220,15 +220,12 @@ private slots: void on_saveButton_clicked(); void on_activateESP_clicked(); void on_deactivateESP_clicked(); - void on_textFileSave_clicked(); - void on_textFileWordWrap_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); void on_modIDEdit_editingFinished(); void on_sourceGameEdit_currentIndexChanged(int); void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); void on_iniFileView_textChanged(); - void onTextFileChanged(bool b); void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); @@ -351,8 +348,6 @@ private: std::vector createGotoActions( const QList& selection); - - void setTextFileWordWrap(bool b); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 4a29f6e4..513506a7 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -46,67 +46,13 @@ A list of text-files in the mod directory like readmes. - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Save - - - - - - - Word wrap - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/src/texteditor.cpp b/src/texteditor.cpp index eef74246..9de2b9c1 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -1,15 +1,18 @@ #include "texteditor.h" #include "utility.h" +#include TextEditor::TextEditor(QPlainTextEdit* edit) - : m_edit(edit), m_dirty(false) + : m_edit(edit), m_toolbar(*this), m_dirty(false) { + setupToolbar(); + m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); QObject::connect( m_edit->document(), &QTextDocument::modificationChanged, - [&](bool b){ onChanged(b); }); + [&](bool b){ onModified(b); }); } bool TextEditor::load(const QString& filename) @@ -69,8 +72,99 @@ bool TextEditor::dirty() const return m_dirty; } -void TextEditor::onChanged(bool b) +void TextEditor::onModified(bool b) { dirty(b); - emit changed(b); + emit modified(b); +} + +void TextEditor::setupToolbar() +{ + auto* widget = wrapEditWidget(); + if (!widget) { + return; + } + + auto* layout = new QVBoxLayout(widget); + + // adding toolbar and edit + layout->addWidget(m_toolbar.widget()); + layout->addWidget(m_edit); + + // make the edit stretch + layout->setStretch(0, 0); + layout->setStretch(1, 1); + + // visuals + layout->setContentsMargins(0, 0, 0, 0); + widget->show(); +} + +QWidget* TextEditor::wrapEditWidget() +{ + auto widget = std::make_unique(); + + // wrapping the QPlainTextEdit into a new widget so the toolbar can be + // displayed above it + + if (auto* parentLayout=m_edit->parentWidget()->layout()) { + // the edit's parent has a regular layout, replace the edit by the new + // widget and delete the QLayoutItem that's returned as it's not needed + delete parentLayout->replaceWidget(m_edit, widget.get()); + } else if (auto* splitter=qobject_cast(m_edit->parentWidget())) { + // the edit's parent is a QSplitter, which doesn't have a layout; replace + // the edit by using its index in the splitter + splitter->replaceWidget(splitter->indexOf(m_edit), widget.get()); + } else { + // unknown parent + qCritical("TextEditor: cannot wrap edit widget to display a toolbar"); + return nullptr; + } + + return widget.release(); +} + + +TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : + m_editor(editor), + m_widget(new QWidget), + m_save(new QAction(QObject::tr("&Save"))), + m_wordWrap(new QAction(QObject::tr("&Word wrap"))) +{ + QObject::connect(m_save, &QAction::triggered, [&]{ onSave(); }); + QObject::connect(m_wordWrap, &QAction::triggered, [&]{ onWordWrap(); }); + + auto* layout = new QHBoxLayout(m_widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->setAlignment(Qt::AlignLeft); + + auto* b = new QToolButton; + b->setDefaultAction(m_save); + layout->addWidget(b); + + b = new QToolButton; + b->setDefaultAction(m_wordWrap); + layout->addWidget(b); + + QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); +} + +QWidget* TextEditorToolbar::widget() +{ + return m_widget; +} + +void TextEditorToolbar::onTextModified(bool b) +{ + m_save->setEnabled(b); +} + +void TextEditorToolbar::onSave() +{ + m_editor.save(); +} + +void TextEditorToolbar::onWordWrap() +{ + m_editor.wordWrap(!m_editor.wordWrap()); } diff --git a/src/texteditor.h b/src/texteditor.h index 25a15ad7..10f6c4de 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -3,6 +3,27 @@ #include +class TextEditor; + +class TextEditorToolbar +{ +public: + TextEditorToolbar(TextEditor& editor); + + QWidget* widget(); + +private: + TextEditor& m_editor; + QWidget* m_widget; + QAction* m_save; + QAction* m_wordWrap; + + void onTextModified(bool b); + void onSave(); + void onWordWrap(); +}; + + class TextEditor : public QObject { Q_OBJECT @@ -21,16 +42,20 @@ public: bool dirty() const; signals: - void changed(bool b); + void modified(bool b); private: QPlainTextEdit* m_edit; + TextEditorToolbar m_toolbar; QString m_filename; QString m_encoding; bool m_dirty; - void onChanged(bool b); + void onModified(bool b); void dirty(bool b); + + void setupToolbar(); + QWidget* wrapEditWidget(); }; #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From fc8d5c1708aa126bb3301079d15787943a0f47c9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 19:43:36 -0400 Subject: checkable wordwrap on toolbar --- src/texteditor.cpp | 25 ++++++++++++++++--------- src/texteditor.h | 5 +++-- 2 files changed, 19 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 9de2b9c1..56ccfd2c 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -10,6 +10,8 @@ TextEditor::TextEditor(QPlainTextEdit* edit) m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); + emit modified(false); + QObject::connect( m_edit->document(), &QTextDocument::modificationChanged, [&](bool b){ onModified(b); }); @@ -55,6 +57,13 @@ void TextEditor::wordWrap(bool b) } else { m_edit->setLineWrapMode(QPlainTextEdit::NoWrap); } + + emit wordWrapChanged(b); +} + +void TextEditor::toggleWordWrap() +{ + wordWrap(!wordWrap()); } bool TextEditor::wordWrap() const @@ -131,8 +140,10 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_save(new QAction(QObject::tr("&Save"))), m_wordWrap(new QAction(QObject::tr("&Word wrap"))) { - QObject::connect(m_save, &QAction::triggered, [&]{ onSave(); }); - QObject::connect(m_wordWrap, &QAction::triggered, [&]{ onWordWrap(); }); + QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); + + m_wordWrap->setCheckable(true); + QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); auto* layout = new QHBoxLayout(m_widget); layout->setContentsMargins(0, 0, 0, 0); @@ -147,6 +158,7 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : layout->addWidget(b); QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); + QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); } QWidget* TextEditorToolbar::widget() @@ -159,12 +171,7 @@ void TextEditorToolbar::onTextModified(bool b) m_save->setEnabled(b); } -void TextEditorToolbar::onSave() -{ - m_editor.save(); -} - -void TextEditorToolbar::onWordWrap() +void TextEditorToolbar::onWordWrap(bool b) { - m_editor.wordWrap(!m_editor.wordWrap()); + m_wordWrap->setChecked(b); } diff --git a/src/texteditor.h b/src/texteditor.h index 10f6c4de..c196b7a4 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -19,8 +19,7 @@ private: QAction* m_wordWrap; void onTextModified(bool b); - void onSave(); - void onWordWrap(); + void onWordWrap(bool b); }; @@ -37,12 +36,14 @@ public: const QString& filename() const; void wordWrap(bool b); + void toggleWordWrap(); bool wordWrap() const; bool dirty() const; signals: void modified(bool b); + void wordWrapChanged(bool b); private: QPlainTextEdit* m_edit; -- cgit v1.3.1 From c79cf72d2250631110e8605ced6f34dda0378dc0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 21:00:04 -0400 Subject: line numbers, which required inheriting from QPlainTextEdit instead --- src/modinfodialog.cpp | 12 ++-- src/modinfodialog.h | 1 - src/modinfodialog.ui | 14 ++--- src/texteditor.cpp | 148 +++++++++++++++++++++++++++++++++++++++++++------- src/texteditor.h | 33 +++++++++-- 5 files changed, 170 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 169623ad..743f76b0 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -359,7 +359,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - m_textFileEditor.reset(new TextEditor(ui->textFileView)); + ui->textFileView->setupToolbar(); ui->tabTextSplitter->setSizes({200, 1}); ui->tabTextSplitter->setStretchFactor(0, 0); @@ -1037,10 +1037,10 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) bool ModInfoDialog::allowNavigateFromTXT() { - if (m_textFileEditor->dirty()) { + if (ui->textFileView->dirty()) { const int res = QMessageBox::question( this, tr("Save changes?"), - tr("Save changes to \"%1\"?").arg(m_textFileEditor->filename()), + tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (res == QMessageBox::Cancel) { @@ -1071,7 +1071,7 @@ void ModInfoDialog::on_textFileList_currentItemChanged( { const QString fullPath = m_RootPath + "/" + current->text(); - if (fullPath == m_textFileEditor->filename()) { + if (fullPath == ui->textFileView->filename()) { // the new file is the same as the currently displayed file. May be the // result of a cancellation return; @@ -1086,7 +1086,7 @@ void ModInfoDialog::on_textFileList_currentItemChanged( void ModInfoDialog::openTextFile(const QString &fileName) { - m_textFileEditor->load(fileName); + ui->textFileView->load(fileName); } void ModInfoDialog::openIniFile(const QString &fileName) @@ -1165,7 +1165,7 @@ void ModInfoDialog::on_saveButton_clicked() void ModInfoDialog::saveCurrentTextFile() { - m_textFileEditor->save(); + ui->textFileView->save(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 42ef990d..09924671 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -304,7 +304,6 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; FilterWidget m_advancedConflictFilter; - std::unique_ptr m_textFileEditor; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 513506a7..785fdeb4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -46,14 +46,7 @@ A list of text-files in the mod directory like readmes. - - - - - - - - + @@ -1217,6 +1210,11 @@ p, li { white-space: pre-wrap; } QLineEdit
    modidlineedit.h
    + + TextEditor + QPlainTextEdit +
    texteditor.h
    +
    diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 56ccfd2c..ecb46547 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -2,26 +2,27 @@ #include "utility.h" #include -TextEditor::TextEditor(QPlainTextEdit* edit) - : m_edit(edit), m_toolbar(*this), m_dirty(false) +TextEditor::TextEditor(QWidget* parent) : + QPlainTextEdit(parent), + m_toolbar(*this), m_lineNumbers(nullptr), m_dirty(false) { - setupToolbar(); - - m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); + m_lineNumbers = new TextEditorLineNumbers(*this); + emit modified(false); QObject::connect( - m_edit->document(), &QTextDocument::modificationChanged, + document(), &QTextDocument::modificationChanged, [&](bool b){ onModified(b); }); } bool TextEditor::load(const QString& filename) { m_filename = filename; - m_edit->setPlainText(MOBase::readFileText(filename, &m_encoding)); - m_edit->document()->setModified(false); + setPlainText(MOBase::readFileText(filename, &m_encoding)); + document()->setModified(false); return true; } @@ -37,10 +38,10 @@ bool TextEditor::save() file.resize(0); QTextCodec* codec = QTextCodec::codecForName(m_encoding.toUtf8()); - QString data = m_edit->toPlainText().replace("\n", "\r\n"); + QString data = toPlainText().replace("\n", "\r\n"); file.write(codec->fromUnicode(data)); - m_edit->document()->setModified(false); + document()->setModified(false); return true; } @@ -53,9 +54,9 @@ const QString& TextEditor::filename() const void TextEditor::wordWrap(bool b) { if (b) { - m_edit->setLineWrapMode(QPlainTextEdit::WidgetWidth); + setLineWrapMode(QPlainTextEdit::WidgetWidth); } else { - m_edit->setLineWrapMode(QPlainTextEdit::NoWrap); + setLineWrapMode(QPlainTextEdit::NoWrap); } emit wordWrapChanged(b); @@ -68,7 +69,7 @@ void TextEditor::toggleWordWrap() bool TextEditor::wordWrap() const { - return (m_edit->lineWrapMode() == QPlainTextEdit::WidgetWidth); + return (lineWrapMode() == QPlainTextEdit::WidgetWidth); } void TextEditor::dirty(bool b) @@ -98,7 +99,7 @@ void TextEditor::setupToolbar() // adding toolbar and edit layout->addWidget(m_toolbar.widget()); - layout->addWidget(m_edit); + layout->addWidget(this); // make the edit stretch layout->setStretch(0, 0); @@ -116,23 +117,132 @@ QWidget* TextEditor::wrapEditWidget() // wrapping the QPlainTextEdit into a new widget so the toolbar can be // displayed above it - if (auto* parentLayout=m_edit->parentWidget()->layout()) { + if (auto* parentLayout=parentWidget()->layout()) { // the edit's parent has a regular layout, replace the edit by the new // widget and delete the QLayoutItem that's returned as it's not needed - delete parentLayout->replaceWidget(m_edit, widget.get()); - } else if (auto* splitter=qobject_cast(m_edit->parentWidget())) { + delete parentLayout->replaceWidget(this, widget.get()); + + } else if (auto* splitter=qobject_cast(parentWidget())) { // the edit's parent is a QSplitter, which doesn't have a layout; replace // the edit by using its index in the splitter - splitter->replaceWidget(splitter->indexOf(m_edit), widget.get()); + auto index = splitter->indexOf(this); + + if (index == -1) { + qCritical( + "TextEditor: cannot wrap edit widget to display a toolbar, " + "parent is a splitter, but widget isn't in it"); + + return nullptr; + } + + splitter->replaceWidget(index, widget.get()); + } else { // unknown parent - qCritical("TextEditor: cannot wrap edit widget to display a toolbar"); + qCritical( + "TextEditor: cannot wrap edit widget to display a toolbar, " + "no parent or parent has no layout"); + return nullptr; } return widget.release(); } +void TextEditor::resizeEvent(QResizeEvent* e) +{ + QPlainTextEdit::resizeEvent(e); + + QRect cr = contentsRect(); + m_lineNumbers->setGeometry(QRect(cr.left(), cr.top(), m_lineNumbers->areaWidth(), cr.height())); +} + +void TextEditor::paintLineNumbers(QPaintEvent* e) +{ + QPainter painter(m_lineNumbers); + painter.fillRect(e->rect(), Qt::lightGray); + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int) blockBoundingRect(block).height(); + + while (block.isValid() && top <= e->rect().bottom()) { + if (block.isVisible() && bottom >= e->rect().top()) { + QString number = QString::number(blockNumber + 1); + painter.setPen(Qt::black); + + painter.drawText( + 0, top, m_lineNumbers->width(), fontMetrics().height(), + Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + ++blockNumber; + } +} + + +TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) + : QWidget(&editor), m_editor(editor) +{ + setFont(editor.font()); + + connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&]{ updateAreaWidth(); }); + connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy){ updateArea(rect, dy); }); + //connect(e, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + + updateAreaWidth(); + //highlightCurrentLine(); +} + +QSize TextEditorLineNumbers::sizeHint() const +{ + return QSize(areaWidth(), 0); +} + +void TextEditorLineNumbers::paintEvent(QPaintEvent* e) +{ + m_editor.paintLineNumbers(e); +} + +int TextEditorLineNumbers::areaWidth() const +{ + int digits = 1; + int max = qMax(1, m_editor.blockCount()); + + while (max >= 10) { + max /= 10; + ++digits; + } + + digits = std::max(3, digits); + + int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; + + return space; +} + +void TextEditorLineNumbers::updateAreaWidth() +{ + m_editor.setViewportMargins(areaWidth(), 0, 0, 0); +} + +void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) +{ + if (dy) { + scroll(0, dy); + } else { + update(0, rect.y(), width(), rect.height()); + } + + if (rect.contains(m_editor.viewport()->rect())) { + updateAreaWidth(); + } +} + TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), diff --git a/src/texteditor.h b/src/texteditor.h index c196b7a4..af542341 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -23,12 +23,33 @@ private: }; -class TextEditor : public QObject +class TextEditorLineNumbers : public QWidget +{ +public: + TextEditorLineNumbers(TextEditor& editor); + QSize sizeHint() const override; + int areaWidth() const; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + TextEditor& m_editor; + + void updateAreaWidth(); + void updateArea(const QRect &rect, int dy); +}; + + +class TextEditor : public QPlainTextEdit { Q_OBJECT + friend class TextEditorLineNumbers; public: - TextEditor(QPlainTextEdit* edit); + TextEditor(QWidget* parent=nullptr); + + void setupToolbar(); bool load(const QString& filename); bool save(); @@ -45,9 +66,12 @@ signals: void modified(bool b); void wordWrapChanged(bool b); +protected: + void resizeEvent(QResizeEvent* e) override; + private: - QPlainTextEdit* m_edit; TextEditorToolbar m_toolbar; + TextEditorLineNumbers* m_lineNumbers; QString m_filename; QString m_encoding; bool m_dirty; @@ -55,8 +79,9 @@ private: void onModified(bool b); void dirty(bool b); - void setupToolbar(); QWidget* wrapEditWidget(); + + void paintLineNumbers(QPaintEvent* e); }; #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From a2b68c3a2e17198e834684fe1719b19464559657 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 01:11:13 -0400 Subject: text and background color, simple syntax highlighter that doesn't do anything highlight current line --- src/texteditor.cpp | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/texteditor.h | 34 ++++++++++++++++ 2 files changed, 145 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/texteditor.cpp b/src/texteditor.cpp index ecb46547..7a83e461 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -4,12 +4,14 @@ TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), - m_toolbar(*this), m_lineNumbers(nullptr), m_dirty(false) + m_toolbar(*this), m_lineNumbers(nullptr), m_highlighter(nullptr), + m_dirty(false) { setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); m_lineNumbers = new TextEditorLineNumbers(*this); + m_highlighter = new TextEditorHighlighter(document()); emit modified(false); @@ -82,6 +84,48 @@ bool TextEditor::dirty() const return m_dirty; } +QString TextEditor::textColor() const +{ + return m_highlighter->textColor().name(QColor::HexArgb); +} + +void TextEditor::setTextColor(const QString& s) +{ + const QColor c(s); + if (!c.isValid()) { + qWarning() << "TextEditor: invalid text color '" << s << "'"; + return; + } + + m_highlighter->setTextColor(c); +} + +QString TextEditor::backgroundColor() const +{ + return m_highlighter->backgroundColor().name(QColor::HexArgb); +} + +void TextEditor::setBackgroundColor(const QString& s) +{ + const QColor c(s); + if (!c.isValid()) { + qWarning() << "TextEditor: invalid backgorund color '" << s << "'"; + return; + } + + if (m_highlighter->backgroundColor() == c) { + return; + } + + m_highlighter->setBackgroundColor(c); + + setStyleSheet(QString("QPlainTextEdit{ background-color: rgba(%1, %2, %3, %4); }") + .arg(c.redF() * 255) + .arg(c.greenF() * 255) + .arg(c.blueF() * 255) + .arg(c.alphaF())); +} + void TextEditor::onModified(bool b) { dirty(b); @@ -185,6 +229,50 @@ void TextEditor::paintLineNumbers(QPaintEvent* e) } +TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) : + QSyntaxHighlighter(doc), + m_background(QColor("transparent")), + m_text(QColor("black")) +{ +} + +QColor TextEditorHighlighter::backgroundColor() +{ + return m_background; +} + +void TextEditorHighlighter::setBackgroundColor(const QColor& c) +{ + m_background = c; + changed(); +} + +QColor TextEditorHighlighter::textColor() +{ + return m_text; +} + +void TextEditorHighlighter::setTextColor(const QColor& c) +{ + m_text = c; + changed(); +} + +void TextEditorHighlighter::highlightBlock(const QString& s) +{ + QTextCharFormat f; + f.setBackground(m_background); + f.setForeground(m_text); + + setFormat(0, s.size(), f); +} + +void TextEditorHighlighter::changed() +{ + rehighlight(); +} + + TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) : QWidget(&editor), m_editor(editor) { @@ -192,10 +280,10 @@ TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&]{ updateAreaWidth(); }); connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy){ updateArea(rect, dy); }); - //connect(e, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + connect(&m_editor, &QPlainTextEdit::cursorPositionChanged, [&]{ highlightCurrentLine(); }); updateAreaWidth(); - //highlightCurrentLine(); + highlightCurrentLine(); } QSize TextEditorLineNumbers::sizeHint() const @@ -211,7 +299,7 @@ void TextEditorLineNumbers::paintEvent(QPaintEvent* e) int TextEditorLineNumbers::areaWidth() const { int digits = 1; - int max = qMax(1, m_editor.blockCount()); + int max = std::max(1, m_editor.blockCount()); while (max >= 10) { max /= 10; @@ -243,6 +331,25 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } } +void TextEditorLineNumbers::highlightCurrentLine() +{ + QList extraSelections; + + if (!m_editor.isReadOnly()) { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(Qt::yellow).lighter(160); + + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = m_editor.textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + m_editor.setExtraSelections(extraSelections); +} + TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), diff --git a/src/texteditor.h b/src/texteditor.h index af542341..0ae39c60 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -23,6 +23,8 @@ private: }; +// mostly from https://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html +// class TextEditorLineNumbers : public QWidget { public: @@ -38,12 +40,37 @@ private: void updateAreaWidth(); void updateArea(const QRect &rect, int dy); + void highlightCurrentLine(); +}; + + +class TextEditorHighlighter : public QSyntaxHighlighter +{ +public: + TextEditorHighlighter(QTextDocument* doc); + + QColor backgroundColor(); + void setBackgroundColor(const QColor& c); + + QColor textColor(); + void setTextColor(const QColor& c); + +protected: + void highlightBlock(const QString& text) override; + +private: + QColor m_background, m_text; + + void changed(); }; class TextEditor : public QPlainTextEdit { Q_OBJECT + Q_PROPERTY(QString textColor READ textColor WRITE setTextColor) + Q_PROPERTY(QString backgroundColor READ backgroundColor WRITE setBackgroundColor) + friend class TextEditorLineNumbers; public: @@ -62,6 +89,12 @@ public: bool dirty() const; + QString textColor() const; + void setTextColor(const QString& s); + + QString backgroundColor() const; + void setBackgroundColor(const QString& s); + signals: void modified(bool b); void wordWrapChanged(bool b); @@ -72,6 +105,7 @@ protected: private: TextEditorToolbar m_toolbar; TextEditorLineNumbers* m_lineNumbers; + TextEditorHighlighter* m_highlighter; QString m_filename; QString m_encoding; bool m_dirty; -- cgit v1.3.1 From 0084ca6c4c1fad76dafe700aad3db4fdb5ef2750 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 02:53:52 -0400 Subject: text editor default style changed colour properties to use an actual QColor stylable highlight background color padding for line numbers --- src/texteditor.cpp | 187 +++++++++++++++++++++++++++++++++++------------------ src/texteditor.h | 48 ++++++++++---- 2 files changed, 159 insertions(+), 76 deletions(-) (limited to 'src') diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 7a83e461..ccafb37a 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -7,17 +7,53 @@ TextEditor::TextEditor(QWidget* parent) : m_toolbar(*this), m_lineNumbers(nullptr), m_highlighter(nullptr), m_dirty(false) { - setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - wordWrap(true); - m_lineNumbers = new TextEditorLineNumbers(*this); m_highlighter = new TextEditorHighlighter(document()); + setDefaultStyle(); + wordWrap(true); + emit modified(false); - QObject::connect( + connect( document(), &QTextDocument::modificationChanged, [&](bool b){ onModified(b); }); + + connect( + this, &QPlainTextEdit::cursorPositionChanged, + [&]{ highlightCurrentLine(); }); +} + +void TextEditor::setDefaultStyle() +{ + const auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + setFont(font); + m_lineNumbers->setFont(font); + + QColor textColor(Qt::black); + QColor altTextColor(Qt::darkGray); + QColor backgroundColor(Qt::white); + + { + auto w = std::make_unique(); + + if (auto* s=style()) { + s->polish(w.get()); + } + + textColor = w->palette().color(QPalette::WindowText); + altTextColor = w->palette().color(QPalette::Disabled, QPalette::WindowText); + backgroundColor = w->palette().color(QPalette::Window); + } + + setTextColor(textColor); + m_lineNumbers->setTextColor(altTextColor); + + setBackgroundColor(backgroundColor); + m_lineNumbers->setBackgroundColor(backgroundColor); + + setHighlightBackgroundColor(backgroundColor); } bool TextEditor::load(const QString& filename) @@ -84,35 +120,13 @@ bool TextEditor::dirty() const return m_dirty; } -QString TextEditor::textColor() const -{ - return m_highlighter->textColor().name(QColor::HexArgb); -} - -void TextEditor::setTextColor(const QString& s) +QColor TextEditor::backgroundColor() const { - const QColor c(s); - if (!c.isValid()) { - qWarning() << "TextEditor: invalid text color '" << s << "'"; - return; - } - - m_highlighter->setTextColor(c); + return m_highlighter->backgroundColor(); } -QString TextEditor::backgroundColor() const +void TextEditor::setBackgroundColor(const QColor& c) { - return m_highlighter->backgroundColor().name(QColor::HexArgb); -} - -void TextEditor::setBackgroundColor(const QString& s) -{ - const QColor c(s); - if (!c.isValid()) { - qWarning() << "TextEditor: invalid backgorund color '" << s << "'"; - return; - } - if (m_highlighter->backgroundColor() == c) { return; } @@ -126,6 +140,27 @@ void TextEditor::setBackgroundColor(const QString& s) .arg(c.alphaF())); } +QColor TextEditor::textColor() const +{ + return m_highlighter->textColor(); +} + +void TextEditor::setTextColor(const QColor& c) +{ + m_highlighter->setTextColor(c); +} + +QColor TextEditor::highlightBackgroundColor() const +{ + return m_highlightBackground; +} + +void TextEditor::setHighlightBackgroundColor(const QColor& c) +{ + m_highlightBackground = c; + update(); +} + void TextEditor::onModified(bool b) { dirty(b); @@ -201,10 +236,12 @@ void TextEditor::resizeEvent(QResizeEvent* e) m_lineNumbers->setGeometry(QRect(cr.left(), cr.top(), m_lineNumbers->areaWidth(), cr.height())); } -void TextEditor::paintLineNumbers(QPaintEvent* e) +void TextEditor::paintLineNumbers(QPaintEvent* e, const QColor& textColor) { + QStyleOption opt; + opt.init(m_lineNumbers); + QPainter painter(m_lineNumbers); - painter.fillRect(e->rect(), Qt::lightGray); QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); @@ -214,10 +251,10 @@ void TextEditor::paintLineNumbers(QPaintEvent* e) while (block.isValid() && top <= e->rect().bottom()) { if (block.isVisible() && bottom >= e->rect().top()) { QString number = QString::number(blockNumber + 1); - painter.setPen(Qt::black); + painter.setPen(textColor); painter.drawText( - 0, top, m_lineNumbers->width(), fontMetrics().height(), + 0, top, m_lineNumbers->width() - 3, fontMetrics().height(), Qt::AlignRight, number); } @@ -228,6 +265,25 @@ void TextEditor::paintLineNumbers(QPaintEvent* e) } } +void TextEditor::highlightCurrentLine() +{ + QList extraSelections; + + if (!isReadOnly()) { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(Qt::yellow).lighter(160); + + selection.format.setBackground(m_highlightBackground); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + setExtraSelections(extraSelections); +} + TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) : QSyntaxHighlighter(doc), @@ -236,7 +292,7 @@ TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) : { } -QColor TextEditorHighlighter::backgroundColor() +QColor TextEditorHighlighter::backgroundColor() const { return m_background; } @@ -247,7 +303,7 @@ void TextEditorHighlighter::setBackgroundColor(const QColor& c) changed(); } -QColor TextEditorHighlighter::textColor() +QColor TextEditorHighlighter::textColor() const { return m_text; } @@ -274,16 +330,14 @@ void TextEditorHighlighter::changed() TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) - : QWidget(&editor), m_editor(editor) + : QFrame(&editor), m_editor(editor) { setFont(editor.font()); connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&]{ updateAreaWidth(); }); connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy){ updateArea(rect, dy); }); - connect(&m_editor, &QPlainTextEdit::cursorPositionChanged, [&]{ highlightCurrentLine(); }); updateAreaWidth(); - highlightCurrentLine(); } QSize TextEditorLineNumbers::sizeHint() const @@ -291,11 +345,6 @@ QSize TextEditorLineNumbers::sizeHint() const return QSize(areaWidth(), 0); } -void TextEditorLineNumbers::paintEvent(QPaintEvent* e) -{ - m_editor.paintLineNumbers(e); -} - int TextEditorLineNumbers::areaWidth() const { int digits = 1; @@ -308,11 +357,42 @@ int TextEditorLineNumbers::areaWidth() const digits = std::max(3, digits); - int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; + int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits + 3; return space; } +QColor TextEditorLineNumbers::textColor() const +{ + return m_text; +} + +void TextEditorLineNumbers::setTextColor(const QColor& c) +{ + m_text = c; + m_editor.update(); +} + +QColor TextEditorLineNumbers::backgroundColor() const +{ + return m_background; +} + +void TextEditorLineNumbers::setBackgroundColor(const QColor& c) +{ + m_background = c; + m_editor.update(); +} + +void TextEditorLineNumbers::paintEvent(QPaintEvent* e) +{ + QPainter painter(this); + painter.fillRect(e->rect(), m_background); + + QFrame::paintEvent(e); + m_editor.paintLineNumbers(e, m_text); +} + void TextEditorLineNumbers::updateAreaWidth() { m_editor.setViewportMargins(areaWidth(), 0, 0, 0); @@ -331,25 +411,6 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } } -void TextEditorLineNumbers::highlightCurrentLine() -{ - QList extraSelections; - - if (!m_editor.isReadOnly()) { - QTextEdit::ExtraSelection selection; - - QColor lineColor = QColor(Qt::yellow).lighter(160); - - selection.format.setBackground(lineColor); - selection.format.setProperty(QTextFormat::FullWidthSelection, true); - selection.cursor = m_editor.textCursor(); - selection.cursor.clearSelection(); - extraSelections.append(selection); - } - - m_editor.setExtraSelections(extraSelections); -} - TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), diff --git a/src/texteditor.h b/src/texteditor.h index 0ae39c60..0f7447ff 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -5,8 +5,10 @@ class TextEditor; -class TextEditorToolbar +class TextEditorToolbar : public QObject { + Q_OBJECT; + public: TextEditorToolbar(TextEditor& editor); @@ -25,34 +27,47 @@ private: // mostly from https://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html // -class TextEditorLineNumbers : public QWidget +class TextEditorLineNumbers : public QFrame { + Q_OBJECT; + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor); + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor); + public: TextEditorLineNumbers(TextEditor& editor); + QSize sizeHint() const override; int areaWidth() const; + QColor textColor() const; + void setTextColor(const QColor& c); + + QColor backgroundColor() const; + void setBackgroundColor(const QColor& c); + protected: void paintEvent(QPaintEvent *event) override; private: TextEditor& m_editor; + QColor m_background, m_text; void updateAreaWidth(); void updateArea(const QRect &rect, int dy); - void highlightCurrentLine(); }; class TextEditorHighlighter : public QSyntaxHighlighter { + Q_OBJECT; + public: TextEditorHighlighter(QTextDocument* doc); - QColor backgroundColor(); + QColor backgroundColor() const; void setBackgroundColor(const QColor& c); - QColor textColor(); + QColor textColor() const; void setTextColor(const QColor& c); protected: @@ -67,9 +82,10 @@ private: class TextEditor : public QPlainTextEdit { - Q_OBJECT - Q_PROPERTY(QString textColor READ textColor WRITE setTextColor) - Q_PROPERTY(QString backgroundColor READ backgroundColor WRITE setBackgroundColor) + Q_OBJECT; + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor); + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor); + Q_PROPERTY(QColor highlightBackgroundColor READ highlightBackgroundColor WRITE setHighlightBackgroundColor); friend class TextEditorLineNumbers; @@ -89,11 +105,14 @@ public: bool dirty() const; - QString textColor() const; - void setTextColor(const QString& s); + QColor backgroundColor() const; + void setBackgroundColor(const QColor& c); - QString backgroundColor() const; - void setBackgroundColor(const QString& s); + QColor textColor() const; + void setTextColor(const QColor& c); + + QColor highlightBackgroundColor() const; + void setHighlightBackgroundColor(const QColor& c); signals: void modified(bool b); @@ -106,16 +125,19 @@ private: TextEditorToolbar m_toolbar; TextEditorLineNumbers* m_lineNumbers; TextEditorHighlighter* m_highlighter; + QColor m_highlightBackground; QString m_filename; QString m_encoding; bool m_dirty; + void setDefaultStyle(); void onModified(bool b); void dirty(bool b); QWidget* wrapEditWidget(); - void paintLineNumbers(QPaintEvent* e); + void highlightCurrentLine(); + void paintLineNumbers(QPaintEvent* e, const QColor& textColor); }; #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From 491b96c0528159f0830e439f5c5a55e3ff09ad1a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 03:06:31 -0400 Subject: made TextEditorToolbar a widget added save and word wrap icons --- src/resources/save.svg | 1 + src/resources/word-wrap.svg | 1 + src/texteditor.cpp | 17 ++++++----------- src/texteditor.h | 7 ++----- 4 files changed, 10 insertions(+), 16 deletions(-) create mode 100644 src/resources/save.svg create mode 100644 src/resources/word-wrap.svg (limited to 'src') diff --git a/src/resources/save.svg b/src/resources/save.svg new file mode 100644 index 00000000..3fb2a8df --- /dev/null +++ b/src/resources/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/word-wrap.svg b/src/resources/word-wrap.svg new file mode 100644 index 00000000..63b1f1f0 --- /dev/null +++ b/src/resources/word-wrap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/texteditor.cpp b/src/texteditor.cpp index ccafb37a..78ce1610 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -4,9 +4,10 @@ TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), - m_toolbar(*this), m_lineNumbers(nullptr), m_highlighter(nullptr), + m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), m_dirty(false) { + m_toolbar = new TextEditorToolbar(*this); m_lineNumbers = new TextEditorLineNumbers(*this); m_highlighter = new TextEditorHighlighter(document()); @@ -177,7 +178,7 @@ void TextEditor::setupToolbar() auto* layout = new QVBoxLayout(widget); // adding toolbar and edit - layout->addWidget(m_toolbar.widget()); + layout->addWidget(m_toolbar); layout->addWidget(this); // make the edit stretch @@ -414,16 +415,15 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), - m_widget(new QWidget), - m_save(new QAction(QObject::tr("&Save"))), - m_wordWrap(new QAction(QObject::tr("&Word wrap"))) + m_save(new QAction(QIcon(":/MO/gui/save"), QObject::tr("&Save"))), + m_wordWrap(new QAction(QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"))) { QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); m_wordWrap->setCheckable(true); QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); - auto* layout = new QHBoxLayout(m_widget); + auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setAlignment(Qt::AlignLeft); @@ -439,11 +439,6 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); } -QWidget* TextEditorToolbar::widget() -{ - return m_widget; -} - void TextEditorToolbar::onTextModified(bool b) { m_save->setEnabled(b); diff --git a/src/texteditor.h b/src/texteditor.h index 0f7447ff..eef5ca52 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -5,18 +5,15 @@ class TextEditor; -class TextEditorToolbar : public QObject +class TextEditorToolbar : public QFrame { Q_OBJECT; public: TextEditorToolbar(TextEditor& editor); - QWidget* widget(); - private: TextEditor& m_editor; - QWidget* m_widget; QAction* m_save; QAction* m_wordWrap; @@ -122,7 +119,7 @@ protected: void resizeEvent(QResizeEvent* e) override; private: - TextEditorToolbar m_toolbar; + TextEditorToolbar* m_toolbar; TextEditorLineNumbers* m_lineNumbers; TextEditorHighlighter* m_highlighter; QColor m_highlightBackground; -- cgit v1.3.1 From ab7f42dd97e5e6f6076877469a774213bfc3bf76 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 03:12:49 -0400 Subject: split ExpanderWidget into its own set of files --- src/CMakeLists.txt | 3 +++ src/expanderwidget.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/expanderwidget.h | 46 ++++++++++++++++++++++++++++++++++++++++++ src/modinfodialog.cpp | 54 -------------------------------------------------- src/modinfodialog.h | 42 +-------------------------------------- 5 files changed, 104 insertions(+), 95 deletions(-) create mode 100644 src/expanderwidget.cpp create mode 100644 src/expanderwidget.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f75ab1e0..d5aa8247 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,6 +120,7 @@ SET(organizer_SRCS apiuseraccount.cpp filerenamer.cpp texteditor.cpp + expanderwidget.cpp shared/windows_error.cpp shared/error_report.cpp @@ -221,6 +222,7 @@ SET(organizer_HDRS apiuseraccount.h filerenamer.h texteditor.h + expanderwidget.h shared/windows_error.h shared/error_report.h @@ -405,6 +407,7 @@ set(utilities set(widgets descriptionpage + expanderwidget genericicondelegate filerenamer filterwidget diff --git a/src/expanderwidget.cpp b/src/expanderwidget.cpp new file mode 100644 index 00000000..2f47da5b --- /dev/null +++ b/src/expanderwidget.cpp @@ -0,0 +1,54 @@ +#include "expanderwidget.h" + +ExpanderWidget::ExpanderWidget() + : m_button(nullptr), m_content(nullptr), opened_(false) +{ +} + +ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content) + : ExpanderWidget() +{ + set(button, content); +} + +void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) +{ + m_button = button; + m_content = content; + + m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); + + toggle(o); +} + +void ExpanderWidget::toggle() +{ + if (opened()) { + toggle(false); + } + else { + toggle(true); + } +} + +void ExpanderWidget::toggle(bool b) +{ + if (b) { + m_button->setArrowType(Qt::DownArrow); + m_content->show(); + } else { + m_button->setArrowType(Qt::RightArrow); + m_content->hide(); + } + + // the state has to be remembered instead of using m_content's visibility + // because saving the state in saveConflictExpandersState() happens after the + // dialog is closed, which marks all the widgets hidden + opened_ = b; +} + +bool ExpanderWidget::opened() const +{ + return opened_; +} diff --git a/src/expanderwidget.h b/src/expanderwidget.h new file mode 100644 index 00000000..da3eb9d6 --- /dev/null +++ b/src/expanderwidget.h @@ -0,0 +1,46 @@ +#ifndef EXPANDERWIDGET_H +#define EXPANDERWIDGET_H + +#include + +/* Takes a QToolButton and a widget and creates an expandable widget. +**/ +class ExpanderWidget +{ +public: + /** empty expander, use set() + **/ + ExpanderWidget(); + + /** see set() + **/ + ExpanderWidget(QToolButton* button, QWidget* content); + + /** @brief sets the button and content widgets to use + * the button will be given an arrow icon, clicking it will toggle the + * visibility of the given widget + * @param button the button that toggles the content + * @param content the widget that will be shown or hidden + * @param opened initial state, defaults to closed + **/ + void set(QToolButton* button, QWidget* content, bool opened=false); + + /** either opens or closes the expander depending on the current state + **/ + void toggle(); + + /** sets the current state of the expander + **/ + void toggle(bool b); + + /** returns whether the expander is currently opened + **/ + bool opened() const; + +private: + QToolButton* m_button; + QWidget* m_content; + bool opened_; +}; + +#endif // EXPANDERWIDGET_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 743f76b0..065058fc 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -79,60 +79,6 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS const int max_scan_for_context_menu = 50; -ExpanderWidget::ExpanderWidget() - : m_button(nullptr), m_content(nullptr), opened_(false) -{ -} - -ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content) - : ExpanderWidget() -{ - set(button, content); -} - -void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) -{ - m_button = button; - m_content = content; - - m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); - - toggle(o); -} - -void ExpanderWidget::toggle() -{ - if (opened()) { - toggle(false); - } - else { - toggle(true); - } -} - -void ExpanderWidget::toggle(bool b) -{ - if (b) { - m_button->setArrowType(Qt::DownArrow); - m_content->show(); - } else { - m_button->setArrowType(Qt::RightArrow); - m_content->hide(); - } - - // the state has to be remembered instead of using m_content's visibility - // because saving the state in saveConflictExpandersState() happens after the - // dialog is closed, which marks all the widgets hidden - opened_ = b; -} - -bool ExpanderWidget::opened() const -{ - return opened_; -} - - class ElideLeftDelegate : public QStyledItemDelegate { public: diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 09924671..df450ac0 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "organizercore.h" #include "filterwidget.h" #include "filerenamer.h" +#include "expanderwidget.h" #include #include @@ -53,47 +54,6 @@ class CategoryFactory; class TextEditor; -/* Takes a QToolButton and a widget and creates an expandable widget. - **/ -class ExpanderWidget -{ -public: - /** empty expander, use set() - **/ - ExpanderWidget(); - - /** see set() - **/ - ExpanderWidget(QToolButton* button, QWidget* content); - - /** @brief sets the button and content widgets to use - * the button will be given an arrow icon, clicking it will toggle the - * visibility of the given widget - * @param button the button that toggles the content - * @param content the widget that will be shown or hidden - * @param opened initial state, defaults to closed - **/ - void set(QToolButton* button, QWidget* content, bool opened=false); - - /** either opens or closes the expander depending on the current state - **/ - void toggle(); - - /** sets the current state of the expander - **/ - void toggle(bool b); - - /** returns whether the expander is currently opened - **/ - bool opened() const; - -private: - QToolButton* m_button; - QWidget* m_content; - bool opened_; -}; - - /** * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system -- cgit v1.3.1 From 6e2ae2444ca0bdea964cf0551edd338e6dafa2bc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:00:00 -0400 Subject: split text file tab into its own class --- src/CMakeLists.txt | 3 ++ src/modinfodialog.cpp | 100 +++++++++++++++++------------------------ src/modinfodialog.h | 29 +++++++++--- src/modinfodialogtextfiles.cpp | 91 +++++++++++++++++++++++++++++++++++++ src/modinfodialogtextfiles.h | 21 +++++++++ 5 files changed, 180 insertions(+), 64 deletions(-) create mode 100644 src/modinfodialogtextfiles.cpp create mode 100644 src/modinfodialogtextfiles.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5aa8247..7e62f3e9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogtextfiles.cpp modinfo.cpp modinfobackup.cpp modinfoforeign.cpp @@ -154,6 +155,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogtextfiles.h modinfo.h modinfobackup.h modinfoforeign.h @@ -352,6 +354,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogtextfiles modinfoforeign modinfooverwrite modinforegular diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 065058fc..7e72653f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -38,6 +38,8 @@ along with Mod Organizer. If not, see . #include "previewdialog.h" #include "texteditor.h" +#include "modinfodialogtextfiles.h" + #include #include #include @@ -59,6 +61,18 @@ using namespace MOBase; using namespace MOShared; +std::vector> ModInfoDialogTab::createTabs( + Ui::ModInfoDialog* ui) +{ + std::vector> v; + + v.push_back(std::make_unique(ui)); + + return v; +} + + + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); public: @@ -254,6 +268,9 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) { ui->setupUi(this); + + m_tabs = ModInfoDialogTab::createTabs(ui); + this->setWindowTitle(modInfo->name()); this->setWindowModality(Qt::WindowModal); @@ -305,12 +322,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - ui->textFileView->setupToolbar(); - - ui->tabTextSplitter->setSizes({200, 1}); - ui->tabTextSplitter->setStretchFactor(0, 0); - ui->tabTextSplitter->setStretchFactor(1, 1); - // refresh everything but the conflict lists, which are done in exec() because // they depend on restoring the state to some widgets; this refresh has to be // done here because some of the checks below depend on the ui to decide which @@ -333,15 +344,17 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } else if (unmanaged) { + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_INIFILES, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_IMAGES, false); } else { + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); + initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); @@ -838,7 +851,10 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( void ModInfoDialog::refreshFiles() { // clearing - ui->textFileList->clear(); + for (auto& tab : m_tabs) { + tab->clear(); + } + ui->iniTweaksList->clear(); ui->iniFileList->clear(); ui->inactiveESPList->clear(); @@ -857,9 +873,13 @@ void ModInfoDialog::refreshFiles() while (dirIterator.hasNext()) { QString fileName = dirIterator.next(); - if (fileName.endsWith(".txt", Qt::CaseInsensitive)) { - ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1)); - } else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && + for (auto& tab : m_tabs) { + if (tab->feedFile(m_RootPath, fileName)) { + break; + } + } + + if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && !fileName.endsWith("meta.ini")) { QString namePart = fileName.mid(m_RootPath.length() + 1); if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { @@ -903,7 +923,6 @@ void ModInfoDialog::refreshFiles() } } - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } @@ -943,9 +962,17 @@ void ModInfoDialog::saveCategories(QTreeWidgetItem *currentNode) void ModInfoDialog::on_closeButton_clicked() { - if (allowNavigateFromTXT() && allowNavigateFromINI()) { - this->close(); + for (auto& tab : m_tabs) { + if (!tab->canClose()) { + return; + } + } + + if (!allowNavigateFromINI()) { + return; } + + close(); } @@ -981,23 +1008,6 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) ui->imageLabel->setPixmap(QPixmap::fromImage(image)); } -bool ModInfoDialog::allowNavigateFromTXT() -{ - if (ui->textFileView->dirty()) { - const int res = QMessageBox::question( - this, tr("Save changes?"), - tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentTextFile(); - } - } - return true; -} - bool ModInfoDialog::allowNavigateFromINI() { if (ui->saveButton->isEnabled()) { @@ -1012,29 +1022,6 @@ bool ModInfoDialog::allowNavigateFromINI() return true; } -void ModInfoDialog::on_textFileList_currentItemChanged( - QListWidgetItem *current, QListWidgetItem *previous) -{ - const QString fullPath = m_RootPath + "/" + current->text(); - - if (fullPath == ui->textFileView->filename()) { - // the new file is the same as the currently displayed file. May be the - // result of a cancellation - return; - } - - if (allowNavigateFromTXT()) { - openTextFile(fullPath); - } else { - ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } -} - -void ModInfoDialog::openTextFile(const QString &fileName) -{ - ui->textFileView->load(fileName); -} - void ModInfoDialog::openIniFile(const QString &fileName) { QFile iniFile(fileName); @@ -1109,11 +1096,6 @@ void ModInfoDialog::on_saveButton_clicked() saveCurrentIniFile(); } -void ModInfoDialog::saveCurrentTextFile() -{ - ui->textFileView->save(); -} - void ModInfoDialog::saveCurrentIniFile() { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index df450ac0..97ab9a38 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -53,9 +53,30 @@ class QTreeView; class CategoryFactory; class TextEditor; +class TextFilesTab; + + +class ModInfoDialogTab +{ +public: + static std::vector> createTabs( + Ui::ModInfoDialog* ui); + + ModInfoDialogTab() = default; + ModInfoDialogTab(const ModInfoDialogTab&) = delete; + ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; + ModInfoDialogTab(ModInfoDialogTab&&) = default; + ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; + virtual ~ModInfoDialogTab() = default; + + virtual void clear() = 0; + virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; + virtual bool canClose() = 0; +}; + /** - * this is a larger dialog used to visualise information abount the mod. + * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system **/ class ModInfoDialog : public MOBase::TutorableDialog @@ -147,11 +168,8 @@ private: void deleteFile(const QModelIndex &index); void saveIniTweaks(); void saveCategories(QTreeWidgetItem *currentNode); - void saveCurrentTextFile(); void saveCurrentIniFile(); - void openTextFile(const QString &fileName); void openIniFile(const QString &fileName); - bool allowNavigateFromTXT(); bool allowNavigateFromINI(); FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); @@ -189,7 +207,6 @@ private slots: void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); @@ -234,6 +251,8 @@ private: ModInfo::Ptr m_ModInfo; + std::vector> m_tabs; + QSignalMapper m_ThumbnailMapper; QString m_RootPath; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp new file mode 100644 index 00000000..98da8c4a --- /dev/null +++ b/src/modinfodialogtextfiles.cpp @@ -0,0 +1,91 @@ +#include "modinfodialogtextfiles.h" +#include "ui_modinfodialog.h" +#include + +class TextFileItem : public QListWidgetItem +{ +public: + TextFileItem(const QString& rootPath, QString fullPath) + : m_fullPath(std::move(fullPath)) + { + setText(m_fullPath.mid(rootPath.length() + 1)); + } + + const QString& fullPath() const + { + return m_fullPath; + } + +private: + QString m_fullPath; +}; + + +TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) + : ui(ui) +{ + ui->textFileView->setupToolbar(); + + ui->tabTextSplitter->setSizes({200, 1}); + ui->tabTextSplitter->setStretchFactor(0, 0); + ui->tabTextSplitter->setStretchFactor(1, 1); + + QObject::connect( + ui->textFileList, &QListWidget::currentItemChanged, + [&](auto* current, auto* previous){ onSelection(current, previous); }); +} + +void TextFilesTab::clear() +{ + ui->textFileList->clear(); +} + +bool TextFilesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + if (fullPath.endsWith(".txt", Qt::CaseInsensitive)) { + ui->textFileList->addItem(new TextFileItem(rootPath, fullPath)); + return true; + } + + return false; +} + +bool TextFilesTab::canClose() +{ + if (!ui->textFileView->dirty()) { + return true; + } + + const int res = QMessageBox::question( + ui->tabText, + QObject::tr("Save changes?"), + QObject::tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if (res == QMessageBox::Cancel) { + return false; + } + + if (res == QMessageBox::Yes) { + ui->textFileView->save(); + } + + return true; +} + +void TextFilesTab::onSelection( + QListWidgetItem* current, QListWidgetItem* previous) +{ + auto* item = dynamic_cast(current); + if (!item) { + qCritical("TextFilesTab: item is not a TextFileItem"); + return; + } + + if (!canClose()) { + ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); + return; + } + + ui->textFileView->load(item->fullPath()); +} diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h new file mode 100644 index 00000000..ab9e972c --- /dev/null +++ b/src/modinfodialogtextfiles.h @@ -0,0 +1,21 @@ +#ifndef MODINFODIALOGTEXTFILES_H +#define MODINFODIALOGTEXTFILES_H + +#include "modinfodialog.h" + +class TextFilesTab : public ModInfoDialogTab +{ +public: + TextFilesTab(Ui::ModInfoDialog* ui); + + void clear() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; + bool canClose() override; + +private: + Ui::ModInfoDialog* ui; + + void onSelection(QListWidgetItem* current, QListWidgetItem* previous); +}; + +#endif // MODINFODIALOGTEXTFILES_H -- cgit v1.3.1 From 10f9fd041cefb14659f07c2163ef27796fae7e3e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:04:03 -0400 Subject: disable editor without selection --- src/modinfodialogtextfiles.cpp | 13 ++++++++++++- src/modinfodialogtextfiles.h | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 98da8c4a..50174ec5 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -38,6 +38,7 @@ TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) void TextFilesTab::clear() { ui->textFileList->clear(); + select(nullptr); } bool TextFilesTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -87,5 +88,15 @@ void TextFilesTab::onSelection( return; } - ui->textFileView->load(item->fullPath()); + select(item); +} + +void TextFilesTab::select(TextFileItem* item) +{ + if (item) { + ui->textFileView->setEnabled(true); + ui->textFileView->load(item->fullPath()); + } else { + ui->textFileView->setEnabled(false); + } } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index ab9e972c..80eb9dc0 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -3,6 +3,8 @@ #include "modinfodialog.h" +class TextFileItem; + class TextFilesTab : public ModInfoDialogTab { public: @@ -16,6 +18,7 @@ private: Ui::ModInfoDialog* ui; void onSelection(QListWidgetItem* current, QListWidgetItem* previous); + void select(TextFileItem* item); }; #endif // MODINFODIALOGTEXTFILES_H -- cgit v1.3.1 From b96f154625a0a139e722731b055f022bc90c2058 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:29:15 -0400 Subject: removed unused ini tweaks widgets splitter in ini tab --- src/modinfodialog.cpp | 90 +------------------ src/modinfodialog.h | 6 -- src/modinfodialog.ui | 233 ++++++++++++++++++++++++++------------------------ 3 files changed, 122 insertions(+), 207 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 7e72653f..23b8f4d8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -359,7 +359,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); } - initINITweaks(); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr); @@ -421,7 +420,6 @@ ModInfoDialog::~ModInfoDialog() else m_ModInfo->setNotes(ui->notesEdit->toHtml()); saveCategories(ui->categoriesTree->invisibleRootItem()); - saveIniTweaks(); // ini tweaks are written to the ini file directly. This is the only information not managed by ModInfo delete ui->descriptionView->page(); delete ui->descriptionView; delete ui; @@ -436,19 +434,6 @@ int ModInfoDialog::exec() return TutorableDialog::exec(); } -void ModInfoDialog::initINITweaks() -{ - int numTweaks = m_Settings->beginReadArray("INI Tweaks"); - for (int i = 0; i < numTweaks; ++i) { - m_Settings->setArrayIndex(i); - QList items = ui->iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString); - if (items.size() != 0) { - items.at(0)->setCheckState(Qt::Checked); - } - } - m_Settings->endArray(); -} - void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) { ui->fileTree = findChild("fileTree"); @@ -855,7 +840,6 @@ void ModInfoDialog::refreshFiles() tab->clear(); } - ui->iniTweaksList->clear(); ui->iniFileList->clear(); ui->inactiveESPList->clear(); ui->activeESPList->clear(); @@ -882,15 +866,7 @@ void ModInfoDialog::refreshFiles() if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && !fileName.endsWith("meta.ini")) { QString namePart = fileName.mid(m_RootPath.length() + 1); - if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { - QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList); - newItem->setData(Qt::UserRole, namePart); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newItem); - } else { - ui->iniFileList->addItem(namePart); - } + ui->iniFileList->addItem(namePart); } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || fileName.endsWith(".esm", Qt::CaseInsensitive) || fileName.endsWith(".esl", Qt::CaseInsensitive)) { @@ -1038,23 +1014,6 @@ void ModInfoDialog::openIniFile(const QString &fileName) ui->saveButton->setEnabled(false); } - -void ModInfoDialog::saveIniTweaks() -{ - m_Settings->remove("INI Tweaks"); - m_Settings->beginWriteArray("INI Tweaks"); - - int countEnabled = 0; - for (int i = 0; i < ui->iniTweaksList->count(); ++i) { - if (ui->iniTweaksList->item(i)->checkState() == Qt::Checked) { - m_Settings->setArrayIndex(countEnabled++); - m_Settings->setValue("name", ui->iniTweaksList->item(i)->text()); - } - } - m_Settings->endArray(); -} - - void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) { QString fullPath = m_RootPath + "/" + current->text(); @@ -1072,25 +1031,6 @@ void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, } } - -void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - QString fullPath = m_RootPath + "/" + current->data(Qt::UserRole).toString(); - - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } - - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } - -} - void ModInfoDialog::on_saveButton_clicked() { saveCurrentIniFile(); @@ -2129,31 +2069,3 @@ void ModInfoDialog::on_prevButton_clicked() emit modOpenPrev(tab); this->accept(); } - - -void ModInfoDialog::createTweak() -{ - QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name")); - if (name.isNull()) { - return; - } else if (!fixDirectoryName(name)) { - QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name")); - return; - } else if (ui->iniTweaksList->findItems(name, Qt::MatchFixedString).count() != 0) { - QMessageBox::critical(this, tr("Error"), tr("A tweak by that name exists")); - return; - } - - QListWidgetItem *newTweak = new QListWidgetItem(name + ".ini"); - newTweak->setData(Qt::UserRole, "INI Tweaks/" + name + ".ini"); - newTweak->setFlags(newTweak->flags() | Qt::ItemIsUserCheckable); - newTweak->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newTweak); -} - -void ModInfoDialog::on_iniTweaksList_customContextMenuRequested(const QPoint &pos) -{ - QMenu menu; - menu.addAction(tr("Create Tweak"), this, SLOT(createTweak())); - menu.exec(ui->iniTweaksList->mapToGlobal(pos)); -} diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 97ab9a38..0d54177a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -153,7 +153,6 @@ public slots: private: void initFiletree(ModInfo::Ptr modInfo); - void initINITweaks(); void refreshLists(); @@ -166,7 +165,6 @@ private: QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); - void saveIniTweaks(); void saveCategories(QTreeWidgetItem *currentNode); void saveCurrentIniFile(); void openIniFile(const QString &fileName); @@ -208,7 +206,6 @@ private slots: void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwriteTree_customContextMenuRequested(const QPoint &pos); @@ -225,9 +222,6 @@ private slots: void on_prevButton_clicked(); - void on_iniTweaksList_customContextMenuRequested(const QPoint &pos); - - void createTweak(); private: using FileEntry = MOShared::FileEntry; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 785fdeb4..10ebbb33 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -38,15 +38,58 @@ false - - - A list of text-files in the mod directory. - - - A list of text-files in the mod directory like readmes. - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Text Files + + + + + + + A list of text-files in the mod directory. + + + A list of text-files in the mod directory like readmes. + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + - @@ -55,113 +98,79 @@ INI-Files - + - - - 6 - - - QLayout::SetMinimumSize + + + Qt::Horizontal - - - - Ini Files - - - - - - - - 228 - 16777215 - - - - This is a list of .ini files in the mod. - - - This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. - - - - - - - false - - - Ini Tweaks *This feature is non-functional* - - - - - - - false - - - - 228 - 16777215 - - - - Qt::CustomContextMenu - - - This is a list of ini tweaks (ini modifications that can be toggled). - - - This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod whether the tweaks are really optional. - - - - - - - - - - - - - - - - - true - - - false - - - false - - - false - - - - - - - false - - - Save changes to the file. - - - Save changes to the file. This overwrites the original. There is no automatic backup! - - - Save + + + + 6 - - - + + + + Ini Files + + + + + + + This is a list of .ini files in the mod. + + + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. + + + + + + + + + + + + + + + + + true + + + false + + + false + + + false + + + + + + + false + + + Save changes to the file. + + + Save changes to the file. This overwrites the original. There is no automatic backup! + + + Save + + + + + + -- cgit v1.3.1 From 81815d202b4364847062ba248321474ef5a2d686 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:54:41 -0400 Subject: split ini tab stuff to IniFilesTab refactored TextFilesTab into GenericFiles so it can be used by IniFilesTab --- src/modinfodialog.cpp | 90 +------------------------------ src/modinfodialog.h | 8 --- src/modinfodialog.ui | 38 ++----------- src/modinfodialogtextfiles.cpp | 117 +++++++++++++++++++++++++++++------------ src/modinfodialogtextfiles.h | 41 ++++++++++++--- 5 files changed, 124 insertions(+), 170 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 23b8f4d8..8f989d02 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -67,6 +67,7 @@ std::vector> ModInfoDialogTab::createTabs( std::vector> v; v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } @@ -840,7 +841,6 @@ void ModInfoDialog::refreshFiles() tab->clear(); } - ui->iniFileList->clear(); ui->inactiveESPList->clear(); ui->activeESPList->clear(); ui->imageLabel->setPixmap({}); @@ -863,11 +863,7 @@ void ModInfoDialog::refreshFiles() } } - if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && - !fileName.endsWith("meta.ini")) { - QString namePart = fileName.mid(m_RootPath.length() + 1); - ui->iniFileList->addItem(namePart); - } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || + if (fileName.endsWith(".esp", Qt::CaseInsensitive) || fileName.endsWith(".esm", Qt::CaseInsensitive) || fileName.endsWith(".esl", Qt::CaseInsensitive)) { QString relativePath = fileName.mid(m_RootPath.length() + 1); @@ -944,10 +940,6 @@ void ModInfoDialog::on_closeButton_clicked() } } - if (!allowNavigateFromINI()) { - return; - } - close(); } @@ -984,84 +976,6 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) ui->imageLabel->setPixmap(QPixmap::fromImage(image)); } -bool ModInfoDialog::allowNavigateFromINI() -{ - if (ui->saveButton->isEnabled()) { - int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->iniFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentIniFile(); - } - } - return true; -} - -void ModInfoDialog::openIniFile(const QString &fileName) -{ - QFile iniFile(fileName); - iniFile.open(QIODevice::ReadOnly); - QByteArray buffer = iniFile.readAll(); - - QTextCodec *codec = QTextCodec::codecForUtfText(buffer, QTextCodec::codecForName("utf-8")); - QTextEdit *iniFileView = findChild("iniFileView"); - iniFileView->setText(codec->toUnicode(buffer)); - iniFileView->setProperty("currentFile", fileName); - iniFileView->setProperty("encoding", codec->name()); - iniFile.close(); - - ui->saveButton->setEnabled(false); -} - -void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - QString fullPath = m_RootPath + "/" + current->text(); - - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } - - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } -} - -void ModInfoDialog::on_saveButton_clicked() -{ - saveCurrentIniFile(); -} - - -void ModInfoDialog::saveCurrentIniFile() -{ - QVariant fileNameVar = ui->iniFileView->property("currentFile"); - QVariant encodingVar = ui->iniFileView->property("encoding"); - if (fileNameVar.isValid() && !fileNameVar.toString().isEmpty()) { - QString fileName = fileNameVar.toString(); - QDir().mkpath(QFileInfo(fileName).absolutePath()); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->iniFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveButton->setEnabled(false); -} - -void ModInfoDialog::on_iniFileView_textChanged() -{ - QPushButton* saveButton = findChild("saveButton"); - saveButton->setEnabled(true); -} - void ModInfoDialog::on_activateESP_clicked() { QListWidget *activeESPList = findChild("activeESPList"); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 0d54177a..70fd157c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -53,8 +53,6 @@ class QTreeView; class CategoryFactory; class TextEditor; -class TextFilesTab; - class ModInfoDialogTab { @@ -166,9 +164,6 @@ private: bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); void saveCategories(QTreeWidgetItem *currentNode); - void saveCurrentIniFile(); - void openIniFile(const QString &fileName); - bool allowNavigateFromINI(); FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); void addCheckedCategories(QTreeWidgetItem *tree); @@ -193,7 +188,6 @@ private slots: void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); - void on_saveButton_clicked(); void on_activateESP_clicked(); void on_deactivateESP_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); @@ -201,11 +195,9 @@ private slots: void on_sourceGameEdit_currentIndexChanged(int); void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); - void on_iniFileView_textChanged(); void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwriteTree_customContextMenuRequested(const QPoint &pos); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 10ebbb33..c6ab111f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -86,7 +86,7 @@ 0 - + @@ -96,11 +96,11 @@ - INI-Files + INI Files - + Qt::Horizontal @@ -129,43 +129,15 @@ - + - + - - true - - - false - - - false - - - false - - - - - - - false - - - Save changes to the file. - - - Save changes to the file. This overwrites the original. There is no automatic backup! - - - Save - diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 50174ec5..cf800aa6 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -2,10 +2,10 @@ #include "ui_modinfodialog.h" #include -class TextFileItem : public QListWidgetItem +class FileListItem : public QListWidgetItem { public: - TextFileItem(const QString& rootPath, QString fullPath) + FileListItem(const QString& rootPath, QString fullPath) : m_fullPath(std::move(fullPath)) { setText(m_fullPath.mid(rootPath.length() + 1)); @@ -21,46 +21,36 @@ private: }; -TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) - : ui(ui) +GenericFilesTab::GenericFilesTab(QListWidget* list, QSplitter* sp, TextEditor* e) + : m_list(list), m_editor(e) { - ui->textFileView->setupToolbar(); + m_editor->setupToolbar(); - ui->tabTextSplitter->setSizes({200, 1}); - ui->tabTextSplitter->setStretchFactor(0, 0); - ui->tabTextSplitter->setStretchFactor(1, 1); + sp->setSizes({200, 1}); + sp->setStretchFactor(0, 0); + sp->setStretchFactor(1, 1); QObject::connect( - ui->textFileList, &QListWidget::currentItemChanged, + m_list, &QListWidget::currentItemChanged, [&](auto* current, auto* previous){ onSelection(current, previous); }); } -void TextFilesTab::clear() +void GenericFilesTab::clear() { - ui->textFileList->clear(); + m_list->clear(); select(nullptr); } -bool TextFilesTab::feedFile(const QString& rootPath, const QString& fullPath) +bool GenericFilesTab::canClose() { - if (fullPath.endsWith(".txt", Qt::CaseInsensitive)) { - ui->textFileList->addItem(new TextFileItem(rootPath, fullPath)); - return true; - } - - return false; -} - -bool TextFilesTab::canClose() -{ - if (!ui->textFileView->dirty()) { + if (!m_editor->dirty()) { return true; } const int res = QMessageBox::question( - ui->tabText, + m_list, QObject::tr("Save changes?"), - QObject::tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), + QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (res == QMessageBox::Cancel) { @@ -68,35 +58,94 @@ bool TextFilesTab::canClose() } if (res == QMessageBox::Yes) { - ui->textFileView->save(); + m_editor->save(); } return true; } -void TextFilesTab::onSelection( +bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + static constexpr const char* extensions[] = { + ".txt" + }; + + for (const auto* e : extensions) { + if (wantsFile(rootPath, fullPath)) { + m_list->addItem(new FileListItem(rootPath, fullPath)); + return true; + } + } + + return false; +} + +void GenericFilesTab::onSelection( QListWidgetItem* current, QListWidgetItem* previous) { - auto* item = dynamic_cast(current); + auto* item = dynamic_cast(current); if (!item) { - qCritical("TextFilesTab: item is not a TextFileItem"); + qCritical("TextFilesTab: item is not a FileListItem"); return; } if (!canClose()) { - ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); + m_list->setCurrentItem(previous, QItemSelectionModel::Current); return; } select(item); } -void TextFilesTab::select(TextFileItem* item) +void GenericFilesTab::select(FileListItem* item) { if (item) { - ui->textFileView->setEnabled(true); - ui->textFileView->load(item->fullPath()); + m_editor->setEnabled(true); + m_editor->load(item->fullPath()); } else { - ui->textFileView->setEnabled(false); + m_editor->setEnabled(false); + } +} + + +TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) + : GenericFilesTab(ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) +{ +} + +bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const +{ + static constexpr const char* extensions[] = { + ".txt" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + return true; + } } + + return false; +} + +IniFilesTab::IniFilesTab(Ui::ModInfoDialog* ui) + : GenericFilesTab(ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) +{ +} + +bool IniFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const +{ + static constexpr const char* extensions[] = { + ".ini", ".cfg" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + if (!fullPath.endsWith("meta.ini")) { + return true; + } + } + } + + return false; } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 80eb9dc0..8725ea09 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -2,23 +2,50 @@ #define MODINFODIALOGTEXTFILES_H #include "modinfodialog.h" +#include +#include -class TextFileItem; +class FileListItem; +class TextEditor; -class TextFilesTab : public ModInfoDialogTab +class GenericFilesTab : public ModInfoDialogTab { public: - TextFilesTab(Ui::ModInfoDialog* ui); + GenericFilesTab(QListWidget* list, QSplitter* splitter, TextEditor* editor); void clear() override; - bool feedFile(const QString& rootPath, const QString& fullPath) override; bool canClose() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; -private: - Ui::ModInfoDialog* ui; +protected: + QListWidget* m_list; + TextEditor* m_editor; + + virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; +private: void onSelection(QListWidgetItem* current, QListWidgetItem* previous); - void select(TextFileItem* item); + void select(FileListItem* item); +}; + + +class TextFilesTab : public GenericFilesTab +{ +public: + TextFilesTab(Ui::ModInfoDialog* ui); + +protected: + bool wantsFile(const QString& rootPath, const QString& fullPath) const override; +}; + + +class IniFilesTab : public GenericFilesTab +{ +public: + IniFilesTab(Ui::ModInfoDialog* ui); + +protected: + bool wantsFile(const QString& rootPath, const QString& fullPath) const override; }; #endif // MODINFODIALOGTEXTFILES_H -- cgit v1.3.1 From 9f1520c69e88113d9a1a06f91bbabe9cf5ddef9f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 05:50:45 -0400 Subject: split images tab stuff in ImagesTab text editor: fixed modified flag not being set to false when loading an empty file --- src/CMakeLists.txt | 3 ++ src/modinfodialog.cpp | 51 ++++++------------------- src/modinfodialog.h | 5 +-- src/modinfodialog.ui | 8 ++-- src/modinfodialogimages.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++++ src/modinfodialogimages.h | 37 ++++++++++++++++++ src/texteditor.cpp | 12 +++++- 7 files changed, 158 insertions(+), 49 deletions(-) create mode 100644 src/modinfodialogimages.cpp create mode 100644 src/modinfodialogimages.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e62f3e9..c5018baf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogimages.cpp modinfodialogtextfiles.cpp modinfo.cpp modinfobackup.cpp @@ -155,6 +156,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogimages.h modinfodialogtextfiles.h modinfo.h modinfobackup.h @@ -354,6 +356,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogimages modinfodialogtextfiles modinfoforeign modinfooverwrite diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 8f989d02..cc3140b6 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see . #include "texteditor.h" #include "modinfodialogtextfiles.h" +#include "modinfodialogimages.h" #include #include @@ -68,10 +69,16 @@ std::vector> ModInfoDialogTab::createTabs( v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } +bool ModInfoDialogTab::canClose() +{ + return true; +} + class ModFileListWidget : public QListWidgetItem { @@ -262,7 +269,7 @@ public: ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_ThumbnailMapper(this), m_RequestStarted(false), + m_RequestStarted(false), m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_PreviewAction(nullptr), m_RenameAction(nullptr), m_DeleteAction(nullptr), m_HideAction(nullptr), m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), @@ -307,8 +314,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->descriptionView->setPage(new DescriptionPage()); - connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&))); - connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&))); connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); connect(ui->descriptionView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); //TODO: No easy way to delegate links @@ -347,14 +352,16 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_INIFILES, false); + ui->tabWidget->setTabEnabled(TAB_IMAGES, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); } else { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); + ui->tabWidget->setTabEnabled(TAB_INIFILES, true); + ui->tabWidget->setTabEnabled(TAB_IMAGES, true); initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); @@ -843,13 +850,6 @@ void ModInfoDialog::refreshFiles() ui->inactiveESPList->clear(); ui->activeESPList->clear(); - ui->imageLabel->setPixmap({}); - - while (ui->thumbnailArea->count() > 0) { - auto* item = ui->thumbnailArea->takeAt(0); - delete item->widget(); - delete item; - } if (m_RootPath.length() > 0) { @@ -875,27 +875,10 @@ void ModInfoDialog::refreshFiles() } else { ui->activeESPList->addItem(relativePath); } - } else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) || - (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { - QImage image = QImage(fileName); - if (!image.isNull()) { - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(128); - } else { - image = image.scaledToHeight(96); - } - - QPushButton *thumbnailButton = new QPushButton(QPixmap::fromImage(image), ""); - thumbnailButton->setIconSize(QSize(image.width(), image.height())); - connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map())); - m_ThumbnailMapper.setMapping(thumbnailButton, fileName); - ui->thumbnailArea->addWidget(thumbnailButton); - } } } } - ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } @@ -964,18 +947,6 @@ void ModInfoDialog::openTab(int tab) } } -void ModInfoDialog::thumbnailClicked(const QString &fileName) -{ - ui->imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - QImage image(fileName); - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(ui->imageLabel->geometry().width()); - } else { - image = image.scaledToHeight(ui->imageLabel->geometry().height()); - } - ui->imageLabel->setPixmap(QPixmap::fromImage(image)); -} - void ModInfoDialog::on_activateESP_clicked() { QListWidget *activeESPList = findChild("activeESPList"); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 70fd157c..f3ef6e76 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -69,7 +69,7 @@ public: virtual void clear() = 0; virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; - virtual bool canClose() = 0; + virtual bool canClose(); }; @@ -135,7 +135,6 @@ public: signals: - void thumbnailClickedSignal(const QString &filename); void linkActivated(const QString &link); void downloadRequest(const QString &link); void modOpen(const QString &modName, int tab); @@ -172,7 +171,6 @@ private: int tabIndex(const QString &tabId); private slots: - void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); void linkClicked(QString url); @@ -239,7 +237,6 @@ private: std::vector> m_tabs; - QSignalMapper m_ThumbnailMapper; QString m_RootPath; OrganizerCore *m_OrganizerCore; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index c6ab111f..ba670ef3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -154,7 +154,7 @@ - + 0 0 @@ -183,7 +183,7 @@ - + 16777215 @@ -193,7 +193,7 @@ true - + 0 @@ -225,7 +225,7 @@ 0 - + 0 diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp new file mode 100644 index 00000000..4f2ee78c --- /dev/null +++ b/src/modinfodialogimages.cpp @@ -0,0 +1,91 @@ +#include "modinfodialogimages.h" +#include "ui_modinfodialog.h" + +ThumbnailButton::ThumbnailButton(const QString& fullPath, QImage original) + : m_original(std::move(original)) +{ + const auto ratio = static_cast(m_original.width()) / m_original.height(); + + QImage thumbnail; + if (ratio > 1.34) { + thumbnail = m_original.scaledToWidth(128); + } else { + thumbnail = m_original.scaledToHeight(96); + } + + setIcon(QPixmap::fromImage(thumbnail)); + setIconSize(QSize(thumbnail.width(), thumbnail.height())); + + connect(this, &QPushButton::clicked, [&]{ emit open(m_original); }); +} + +const QImage& ThumbnailButton::image() const +{ + return m_original; +} + + +ImagesTab::ImagesTab(Ui::ModInfoDialog* ui) + : ui(ui) +{ +} + +void ImagesTab::clear() +{ + ui->imageLabel->setPixmap({}); + + while (ui->imageThumbnails->count() > 0) { + auto* item = ui->imageThumbnails->takeAt(0); + delete item->widget(); + delete item; + } +} + +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + static constexpr const char* extensions[] = { + ".png", ".jpg" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + add(fullPath); + return true; + } + } + + return false; +} + +void ImagesTab::add(const QString& fullPath) +{ + QImage image = QImage(fullPath); + + if (image.isNull()) { + qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; + return; + } + + auto* button = new ThumbnailButton(fullPath, std::move(image)); + + QObject::connect( + button, &ThumbnailButton::open, + [&](const QImage& image){ onOpen(image); }); + + ui->imageThumbnails->addWidget(button); +} + +void ImagesTab::onOpen(const QImage& original) +{ + QImage image; + + const auto ratio = static_cast(original.width()) / original.height(); + + if (ratio > 1.34) { + image = original.scaledToWidth(ui->imageLabel->geometry().width()); + } else { + image = original.scaledToHeight(ui->imageLabel->geometry().height()); + } + + ui->imageLabel->setPixmap(QPixmap::fromImage(image)); +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h new file mode 100644 index 00000000..3c492e77 --- /dev/null +++ b/src/modinfodialogimages.h @@ -0,0 +1,37 @@ +#ifndef MODINFODIALOGIMAGES_H +#define MODINFODIALOGIMAGES_H + +#include "modinfodialog.h" + +class ThumbnailButton : public QPushButton +{ + Q_OBJECT; + +public: + ThumbnailButton(const QString& fullPath, QImage image); + const QImage& image() const; + +signals: + void open(const QImage& image); + +private: + const QImage m_original; +}; + + +class ImagesTab : public ModInfoDialogTab +{ +public: + ImagesTab(Ui::ModInfoDialog* ui); + + void clear() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; + +private: + Ui::ModInfoDialog* ui; + + void add(const QString& fullPath); + void onOpen(const QImage& image); +}; + +#endif // MODINFODIALOGIMAGES_H diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 78ce1610..99490b22 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -60,9 +60,19 @@ void TextEditor::setDefaultStyle() bool TextEditor::load(const QString& filename) { m_filename = filename; - setPlainText(MOBase::readFileText(filename, &m_encoding)); + clear(); + + const QString s = MOBase::readFileText(filename, &m_encoding); + + setPlainText(s); document()->setModified(false); + if (s.isEmpty()) { + // the modificationChanged even is not fired by the setModified() call + // above when the text being set is empty + onModified(false); + } + return true; } -- cgit v1.3.1 From a13067602590f16484561b9ca23f15aa45089db8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 08:56:43 -0400 Subject: images list now vertical on the left reworked scaling to respect aspect ratio and not exceed original size --- src/modinfodialog.ui | 100 +++++++++++++----------------------- src/modinfodialogimages.cpp | 122 ++++++++++++++++++++++++++++++++------------ src/modinfodialogimages.h | 21 ++++++-- 3 files changed, 141 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index ba670ef3..c1dba1c7 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -150,68 +150,14 @@ Images - + - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Qt::LeftToRight - - - - - - Qt::AlignCenter - - - - - - - - 16777215 - 128 - - - - true + + + Qt::Horizontal - - - - 0 - 0 - 746 - 126 - - - - Images located in the mod. - - - This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - - - - 0 - + + 0 @@ -225,14 +171,40 @@ 0 - - - 0 + + + true - + + + + 0 + 0 + 604 + 436 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4f2ee78c..fb2ad1fe 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,44 +1,109 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" -ThumbnailButton::ThumbnailButton(const QString& fullPath, QImage original) - : m_original(std::move(original)) +ScalableImage::ScalableImage(QImage original) + : m_original(std::move(original)), m_border(1) { - const auto ratio = static_cast(m_original.width()) / m_original.height(); + auto sp = sizePolicy(); + sp.setHeightForWidth(true); + setSizePolicy(sp); +} + +void ScalableImage::setImage(QImage image) +{ + m_original = std::move(image); + m_scaled = {}; + + update(); +} + +const QImage& ScalableImage::image() const +{ + return m_original; +} + +bool ScalableImage::hasHeightForWidth() const +{ + return true; +} + +int ScalableImage::heightForWidth(int w) const +{ + return w; +} + +void ScalableImage::paintEvent(QPaintEvent* e) +{ + if (m_original.isNull()) { + return; + } + + const QRect widgetRect = rect(); + const QRect imageRect = widgetRect.adjusted( + m_border, m_border, -m_border, -m_border); + + const auto ratio = std::min({ + 1.0, + static_cast(imageRect.width()) / m_original.width(), + static_cast(imageRect.height()) / m_original.height()}); + + const QSize scaledSize( + static_cast(std::round(m_original.width() * ratio)), + static_cast(std::round(m_original.height() * ratio))); - QImage thumbnail; - if (ratio > 1.34) { - thumbnail = m_original.scaledToWidth(128); - } else { - thumbnail = m_original.scaledToHeight(96); + if (m_scaled.isNull() || m_scaled.size() != scaledSize) { + qDebug() << "scaled to " << scaledSize; + + m_scaled = m_original.scaled( + scaledSize.width(), scaledSize.height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - setIcon(QPixmap::fromImage(thumbnail)); - setIconSize(QSize(thumbnail.width(), thumbnail.height())); + const QRect drawBorderRect = widgetRect.adjusted(0, 0, -1, -1); + + const QRect drawImageRect( + (imageRect.left()+imageRect.width()/2) - m_scaled.width()/2, + (imageRect.top()+imageRect.height()/2) - m_scaled.height()/2, + m_scaled.width(), m_scaled.height()); + + + QPainter painter(this); - connect(this, &QPushButton::clicked, [&]{ emit open(m_original); }); + painter.setPen(QColor(Qt::black)); + painter.drawRect(drawBorderRect); + painter.drawImage(drawImageRect, m_scaled); } -const QImage& ThumbnailButton::image() const +void ScalableImage::mousePressEvent(QMouseEvent* e) { - return m_original; + if (e->button() == Qt::LeftButton) { + emit clicked(m_original); + } } ImagesTab::ImagesTab(Ui::ModInfoDialog* ui) - : ui(ui) + : ui(ui), m_image(new ScalableImage) { + ui->imagesImage->layout()->addWidget(m_image); + ui->imagesThumbnails->setLayout(new QVBoxLayout); + + ui->tabImagesSplitter->setSizes({128, 1}); + ui->tabImagesSplitter->setStretchFactor(0, 0); + ui->tabImagesSplitter->setStretchFactor(1, 1); } void ImagesTab::clear() { - ui->imageLabel->setPixmap({}); + m_image->setImage({}); - while (ui->imageThumbnails->count() > 0) { - auto* item = ui->imageThumbnails->takeAt(0); + while (ui->imagesThumbnails->layout()->count() > 0) { + auto* item = ui->imagesThumbnails->layout()->takeAt(0); delete item->widget(); delete item; } + + static_cast(ui->imagesThumbnails->layout())->addStretch(1); } bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -66,26 +131,17 @@ void ImagesTab::add(const QString& fullPath) return; } - auto* button = new ThumbnailButton(fullPath, std::move(image)); + auto* thumbnail = new ScalableImage(std::move(image)); QObject::connect( - button, &ThumbnailButton::open, - [&](const QImage& image){ onOpen(image); }); + thumbnail, &ScalableImage::clicked, + [&](const QImage& image){ onClicked(image); }); - ui->imageThumbnails->addWidget(button); + static_cast(ui->imagesThumbnails->layout())->insertWidget( + ui->imagesThumbnails->layout()->count() - 1, thumbnail); } -void ImagesTab::onOpen(const QImage& original) +void ImagesTab::onClicked(const QImage& original) { - QImage image; - - const auto ratio = static_cast(original.width()) / original.height(); - - if (ratio > 1.34) { - image = original.scaledToWidth(ui->imageLabel->geometry().width()); - } else { - image = original.scaledToHeight(ui->imageLabel->geometry().height()); - } - - ui->imageLabel->setPixmap(QPixmap::fromImage(image)); + m_image->setImage(original); } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 3c492e77..73fb5936 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -3,19 +3,29 @@ #include "modinfodialog.h" -class ThumbnailButton : public QPushButton +class ScalableImage : public QWidget { Q_OBJECT; public: - ThumbnailButton(const QString& fullPath, QImage image); + ScalableImage(QImage image={}); + + void setImage(QImage image); const QImage& image() const; + bool hasHeightForWidth() const; + int heightForWidth(int w) const; + signals: - void open(const QImage& image); + void clicked(const QImage& image); + +protected: + void paintEvent(QPaintEvent* e) override; + void mousePressEvent(QMouseEvent* e) override; private: - const QImage m_original; + QImage m_original, m_scaled; + int m_border; }; @@ -29,9 +39,10 @@ public: private: Ui::ModInfoDialog* ui; + ScalableImage* m_image; void add(const QString& fullPath); - void onOpen(const QImage& image); + void onClicked(const QImage& image); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From f8a037a409d1b6bbb2f34b237b8b66d454179f56 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 18 Jun 2019 09:29:06 -0400 Subject: changed layout of esps tab, moved to modinfodialogesps.cpp/h slightly offset next/previous images so they look better when shown next to each other vertically --- src/CMakeLists.txt | 3 + src/modinfodialog.cpp | 98 +------------ src/modinfodialog.h | 2 - src/modinfodialog.ui | 268 ++++++++++++++++++++++-------------- src/modinfodialogesps.cpp | 287 +++++++++++++++++++++++++++++++++++++++ src/modinfodialogesps.h | 26 ++++ src/modinfodialogimages.cpp | 2 - src/resources/go-next_16.png | Bin 676 -> 719 bytes src/resources/go-previous_16.png | Bin 655 -> 718 bytes 9 files changed, 483 insertions(+), 203 deletions(-) create mode 100644 src/modinfodialogesps.cpp create mode 100644 src/modinfodialogesps.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c5018baf..aba922f9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogesps.cpp modinfodialogimages.cpp modinfodialogtextfiles.cpp modinfo.cpp @@ -156,6 +157,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogesps.h modinfodialogimages.h modinfodialogtextfiles.h modinfo.h @@ -356,6 +358,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogesps modinfodialogimages modinfodialogtextfiles modinfoforeign diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index cc3140b6..a52f2031 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -40,6 +40,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" +#include "modinfodialogesps.h" #include #include @@ -70,6 +71,7 @@ std::vector> ModInfoDialogTab::createTabs( v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } @@ -353,15 +355,16 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_INIFILES, false); ui->tabWidget->setTabEnabled(TAB_IMAGES, false); + ui->tabWidget->setTabEnabled(TAB_ESPS, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); } else { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); ui->tabWidget->setTabEnabled(TAB_INIFILES, true); ui->tabWidget->setTabEnabled(TAB_IMAGES, true); + ui->tabWidget->setTabEnabled(TAB_ESPS, true); initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); @@ -848,10 +851,6 @@ void ModInfoDialog::refreshFiles() tab->clear(); } - ui->inactiveESPList->clear(); - ui->activeESPList->clear(); - - if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -862,24 +861,8 @@ void ModInfoDialog::refreshFiles() break; } } - - if (fileName.endsWith(".esp", Qt::CaseInsensitive) || - fileName.endsWith(".esm", Qt::CaseInsensitive) || - fileName.endsWith(".esl", Qt::CaseInsensitive)) { - QString relativePath = fileName.mid(m_RootPath.length() + 1); - if (relativePath.contains('/')) { - QFileInfo fileInfo(fileName); - QListWidgetItem *newItem = new QListWidgetItem(fileInfo.fileName()); - newItem->setData(Qt::UserRole, relativePath); - ui->inactiveESPList->addItem(newItem); - } else { - ui->activeESPList->addItem(relativePath); - } - } } } - - ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) @@ -947,79 +930,6 @@ void ModInfoDialog::openTab(int tab) } } -void ModInfoDialog::on_activateESP_clicked() -{ - QListWidget *activeESPList = findChild("activeESPList"); - QListWidget *inactiveESPList = findChild("inactiveESPList"); - - int selectedRow = inactiveESPList->currentRow(); - if (selectedRow < 0) { - return; - } - - QListWidgetItem *selectedItem = inactiveESPList->takeItem(selectedRow); - - QDir root(m_RootPath); - bool renamed = false; - - while (root.exists(selectedItem->text())) { - bool okClicked = false; - QString newName = QInputDialog::getText(this, tr("File Exists"), tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, selectedItem->text(), &okClicked); - if (!okClicked) { - inactiveESPList->insertItem(selectedRow, selectedItem); - return; - } else if (newName.size() > 0) { - selectedItem->setText(newName); - renamed = true; - } - } - - if (root.rename(selectedItem->data(Qt::UserRole).toString(), selectedItem->text())) { - activeESPList->addItem(selectedItem); - if (renamed) { - selectedItem->setData(Qt::UserRole, QVariant()); - } - } else { - inactiveESPList->insertItem(selectedRow, selectedItem); - reportError(tr("failed to move file")); - } -} - - -void ModInfoDialog::on_deactivateESP_clicked() -{ - QListWidget *activeESPList = findChild("activeESPList"); - QListWidget *inactiveESPList = findChild("inactiveESPList"); - - int selectedRow = activeESPList->currentRow(); - if (selectedRow < 0) { - return; - } - - QDir root(m_RootPath); - - QListWidgetItem *selectedItem = activeESPList->takeItem(selectedRow); - - // if we moved the file from optional to active in this session, we move the file back to - // where it came from. Otherwise, it is moved to the new folder "optional" - if (selectedItem->data(Qt::UserRole).isNull()) { - selectedItem->setData(Qt::UserRole, QString("optional/") + selectedItem->text()); - if (!root.exists("optional")) { - if (!root.mkdir("optional")) { - reportError(tr("failed to create directory \"optional\"")); - activeESPList->insertItem(selectedRow, selectedItem); - return; - } - } - } - - if (root.rename(selectedItem->text(), selectedItem->data(Qt::UserRole).toString())) { - inactiveESPList->addItem(selectedItem); - } else { - activeESPList->insertItem(selectedRow, selectedItem); - } -} - void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString &link) { emit linkActivated(link); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index f3ef6e76..6ecf16bf 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -186,8 +186,6 @@ private slots: void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); - void on_activateESP_clicked(); - void on_deactivateESP_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); void on_modIDEdit_editingFinished(); void on_sourceGameEdit_currentIndexChanged(int); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index c1dba1c7..f1212634 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -104,7 +104,7 @@ Qt::Horizontal - + 6 @@ -128,7 +128,7 @@ - + @@ -180,7 +180,7 @@ 0 0 - 604 + 741 436 @@ -213,117 +213,175 @@ Optional ESPs - - - - - List of esps, esms, and esls that can not be loaded by the game. - - - List of esps, esms, and esls contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. -They usually contain optional functionality, see the readme. - -Most mods do not have optional esps, so chances are good you are looking at an empty list. - - - - - - - Optional ESPs + + + + + Qt::Horizontal - - - - - - - - - 96 - 0 - - - - Make the selected mod in the lower list unavailable. - - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - - - - - - - :/MO/gui/resources/go-up.png:/MO/gui/resources/go-up.png + + + + 0 - - - 22 - 22 - + + 0 - - - - - - - 96 - 0 - + + 0 - - Move a file to the data directory. + + 0 - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + + + + Optional ESPs + + + + + + + List of esps, esms, and esls that can not be loaded by the game. + + + List of esps, esms, and esls contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. +They usually contain optional functionality, see the readme. + +Most mods do not have optional esps, so chances are good you are looking at an empty list. + + + + + + + + + 0 - - + + 0 - - - :/MO/gui/resources/go-down.png:/MO/gui/resources/go-down.png + + 0 - - - 22 - 22 - + + 0 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - ESPs in the data directory and thus visible to the game. - - - These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. - - - - - - - Available ESPs - + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Move a file to the data directory. + + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + + + + + + + :/MO/gui/next:/MO/gui/next + + + + 22 + 22 + + + + + + + + Make the selected mod in the lower list unavailable. + + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + + + + + + + :/MO/gui/previous:/MO/gui/previous + + + + 22 + 22 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Available ESPs + + + + + + + ESPs in the data directory and thus visible to the game. + + + These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + + + + + + + + diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp new file mode 100644 index 00000000..32dcdb35 --- /dev/null +++ b/src/modinfodialogesps.cpp @@ -0,0 +1,287 @@ +#include "modinfodialogesps.h" +#include "ui_modinfodialog.h" +#include + +using MOBase::reportError; + + +class ESP +{ +public: + ESP(QString rootPath, QString relativePath) + : m_rootPath(std::move(rootPath)), m_active(false) + { + if (relativePath.contains('/')) { + m_inactivePath = relativePath; + } else { + m_activePath = relativePath; + m_active = true; + } + } + + const QString& rootPath() const + { + return m_rootPath; + } + + const QString& relativePath() const + { + if (m_active) { + return m_activePath; + } else { + return m_inactivePath; + } + } + + const QString& activePath() const + { + return m_activePath; + } + + const QString& inactivePath() const + { + return m_inactivePath; + } + + QFileInfo fileInfo() const + { + return m_rootPath + QDir::separator() + relativePath(); + } + + bool isActive() const + { + return m_active; + } + + bool activate(const QString& newName) + { + QDir root(m_rootPath); + + if (root.rename(m_inactivePath, newName)) { + m_active = true; + m_activePath = newName; + + if (QFileInfo(m_inactivePath).fileName() != newName) { + // file was renamed + m_inactivePath = QFileInfo(m_inactivePath).path() + QDir::separator() + newName; + } + + return true; + } + + return false; + } + + bool deactivate(const QString& newName) + { + QDir root(m_rootPath); + + if (root.rename(m_activePath, newName)) { + m_active = false; + m_inactivePath = newName; + return true; + } + + return false; + } + +private: + QString m_rootPath; + QString m_activePath; + QString m_inactivePath; + bool m_active; +}; + + +class ESPItem : public QListWidgetItem +{ +public: + ESPItem(ESP esp) + : m_esp(std::move(esp)) + { + updateText(); + } + + const ESP& esp() const + { + return m_esp; + } + + void setESP(ESP esp) + { + m_esp = esp; + updateText(); + } + +private: + ESP m_esp; + + void updateText() + { + setText(m_esp.fileInfo().fileName()); + } +}; + + +ESPsTab::ESPsTab(Ui::ModInfoDialog* ui) + : ui(ui) +{ + QObject::connect( + ui->activateESP1, &QToolButton::clicked, [&]{ onActivate(); }); + + QObject::connect( + ui->deactivateESP1, &QToolButton::clicked, [&]{ onDeactivate(); }); +} + +void ESPsTab::clear() +{ + ui->inactiveESPList1->clear(); + ui->activeESPList1->clear(); +} + +bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + static constexpr const char* extensions[] = { + ".esp", ".esm", ".esl" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + QString relativePath = fullPath.mid(rootPath.length() + 1); + + auto* item = new ESPItem({rootPath, relativePath}); + + if (item->esp().isActive()) { + ui->activeESPList1->addItem(item); + } else { + ui->inactiveESPList1->addItem(item); + } + + return true; + } + } + + return false; +} + +void ESPsTab::onActivate() +{ + auto* item = selectedInactive(); + if (!item) { + qWarning("ESPsTab::onActivate(): no selection"); + return; + } + + ESP esp = item->esp(); + + if (esp.isActive()) { + qWarning("ESPsTab::onActive(): item is already active"); + return; + } + + QDir root(esp.rootPath()); + const QFileInfo file(esp.fileInfo()); + + QString newName = file.fileName(); + + while (root.exists(newName)) { + bool okClicked = false; + + newName = QInputDialog::getText( + ui->tabESPs, + QObject::tr("File Exists"), + QObject::tr("A file with that name exists, please enter a new one"), + QLineEdit::Normal, file.fileName(), &okClicked); + + if (!okClicked) { + return; + } + + if (newName.isEmpty()) { + newName = file.fileName(); + } + } + + if (esp.activate(newName)) { + ui->inactiveESPList1->takeItem(ui->inactiveESPList1->row(item)); + ui->activeESPList1->addItem(item); + item->setESP(esp); + } else { + reportError(QObject::tr("Failed to move file")); + } +} + +void ESPsTab::onDeactivate() +{ + auto* item = selectedActive(); + if (!item) { + qWarning("ESPsTab::onDeactivate(): no selection"); + return; + } + + ESP esp = item->esp(); + + if (!esp.isActive()) { + qWarning("ESPsTab::onDeactivate(): item is already inactive"); + return; + } + + QDir root(esp.rootPath()); + + // if we moved the file from optional to active in this session, we move the + // file back to where it came from. Otherwise, it is moved to the new folder + // "optional" + + QString newName = esp.inactivePath(); + + if (newName.isEmpty()) { + if (!root.exists("optional")) { + if (!root.mkdir("optional")) { + reportError(QObject::tr("Failed to create directory \"optional\"")); + return; + } + } + + newName = QString("optional") + QDir::separator() + esp.fileInfo().fileName(); + } + + if (esp.deactivate(newName)) { + ui->activeESPList1->takeItem(ui->activeESPList1->row(item)); + ui->inactiveESPList1->addItem(item); + item->setESP(esp); + } else { + reportError(QObject::tr("Failed to move file")); + } +} + +ESPItem* ESPsTab::selectedInactive() +{ + auto* item = ui->inactiveESPList1->currentItem(); + if (!item) { + return nullptr; + } + + auto* esp = dynamic_cast(item); + if (!esp) { + qCritical("ESPsTab: inactive item is not an ESPItem"); + return nullptr; + } + + return esp; +} + +ESPItem* ESPsTab::selectedActive() +{ + auto* item = ui->activeESPList1->currentItem(); + if (!item) { + return nullptr; + } + + auto* esp = dynamic_cast(item); + if (!esp) { + qCritical("ESPsTab: active item is not an ESPItem"); + return nullptr; + } + + return esp; +} diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h new file mode 100644 index 00000000..a46677cb --- /dev/null +++ b/src/modinfodialogesps.h @@ -0,0 +1,26 @@ +#ifndef MODINFODIALOGESPS_H +#define MODINFODIALOGESPS_H + +#include "modinfodialog.h" + +class ESPItem; + +class ESPsTab : public ModInfoDialogTab +{ +public: + ESPsTab(Ui::ModInfoDialog* ui); + + void clear() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; + +private: + Ui::ModInfoDialog* ui; + + void onActivate(); + void onDeactivate(); + + ESPItem* selectedInactive(); + ESPItem* selectedActive(); +}; + +#endif // MODINFODIALOGESPS_H diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index fb2ad1fe..1659bed3 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -52,8 +52,6 @@ void ScalableImage::paintEvent(QPaintEvent* e) static_cast(std::round(m_original.height() * ratio))); if (m_scaled.isNull() || m_scaled.size() != scaledSize) { - qDebug() << "scaled to " << scaledSize; - m_scaled = m_original.scaled( scaledSize.width(), scaledSize.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); diff --git a/src/resources/go-next_16.png b/src/resources/go-next_16.png index 6ef8de76..58742d39 100644 Binary files a/src/resources/go-next_16.png and b/src/resources/go-next_16.png differ diff --git a/src/resources/go-previous_16.png b/src/resources/go-previous_16.png index 659cd90d..b4b22d04 100644 Binary files a/src/resources/go-previous_16.png and b/src/resources/go-previous_16.png differ -- cgit v1.3.1 From c98115e2f6f59b807382c41256d7f1a86009aa40 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 19 Jun 2019 17:24:23 -0400 Subject: in the process of moving stuff to modinfodialogconflicts.h/cpp --- src/CMakeLists.txt | 3 + src/modinfodialog.cpp | 480 ++----------------------------------- src/modinfodialog.h | 45 ++-- src/modinfodialog.ui | 58 ++--- src/modinfodialogconflicts.cpp | 530 +++++++++++++++++++++++++++++++++++++++++ src/modinfodialogconflicts.h | 86 +++++++ 6 files changed, 689 insertions(+), 513 deletions(-) create mode 100644 src/modinfodialogconflicts.cpp create mode 100644 src/modinfodialogconflicts.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aba922f9..eead6541 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp modinfodialogtextfiles.cpp @@ -157,6 +158,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h modinfodialogtextfiles.h @@ -358,6 +360,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogconflicts modinfodialogesps modinfodialogimages modinfodialogtextfiles diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a52f2031..c24176b3 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -41,6 +41,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" +#include "modinfodialogconflicts.h" #include #include @@ -72,6 +73,7 @@ std::vector> ModInfoDialogTab::createTabs( v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } @@ -81,6 +83,15 @@ bool ModInfoDialogTab::canClose() return true; } +void ModInfoDialogTab::saveState(Settings&) +{ + // no-op +} + +void ModInfoDialogTab::restoreState(const Settings& s) +{ + // no-op +} class ModFileListWidget : public QListWidgetItem { @@ -103,20 +114,6 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS const int max_scan_for_context_menu = 50; -class ElideLeftDelegate : public QStyledItemDelegate -{ -public: - using QStyledItemDelegate::QStyledItemDelegate; - -protected: - void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const - { - QStyledItemDelegate::initStyleOption(option, index); - option->textElideMode = Qt::ElideLeft; - } -}; - - bool canPreviewFile( PluginContainer* pluginContainer, bool isArchive, const QString& filename) { @@ -165,110 +162,6 @@ bool canUnhideFile(bool isArchive, const QString& filename) } -int naturalCompare(const QString& a, const QString& b) -{ - static QCollator c = []{ - QCollator c; - c.setNumericMode(true); - c.setCaseSensitivity(Qt::CaseInsensitive); - return c; - }(); - - return c.compare(a, b); -} - - -class ConflictItem : public QTreeWidgetItem -{ -public: - static const int FILENAME_USERROLE = Qt::UserRole + 1; - static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2; - static const int ARCHIVE_USERROLE = Qt::UserRole + 3; - static const int INDEX_USERROLE = Qt::UserRole + 4; - static const int HAS_ALTS_USERROLE = Qt::UserRole + 5; - - ConflictItem( - QStringList columns, FileEntry::Index index, const QString& fileName, - bool hasAltOrigins, const QString& altOrigin, bool archive) - : QTreeWidgetItem(columns) - { - setData(0, FILENAME_USERROLE, fileName); - setData(0, ALT_ORIGIN_USERROLE, altOrigin); - setData(0, ARCHIVE_USERROLE, archive); - setData(0, INDEX_USERROLE, index); - setData(0, HAS_ALTS_USERROLE, hasAltOrigins); - - if (archive) { - QFont f = font(0); - f.setItalic(true); - - for (int i=0; i); - return data(0, INDEX_USERROLE).toUInt(); - } - - bool canHide() const - { - return canHideFile(isArchive(), fileName()); - } - - bool canUnhide() const - { - return canUnhideFile(isArchive(), fileName()); - } - - bool canOpen() const - { - return canOpenFile(isArchive(), fileName()); - } - - bool canPreview(PluginContainer* pluginContainer) const - { - return canPreviewFile(pluginContainer, isArchive(), fileName()); - } - - bool operator<(const QTreeWidgetItem& other) const - { - const int column = treeWidget()->sortColumn(); - - if (column >= columnCount() || column >= other.columnCount()) { - // shouldn't happen - qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count"; - return false; - } - - return (naturalCompare(text(column), other.text(column)) < 0); - } -}; - - ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_RequestStarted(false), @@ -356,6 +249,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_INIFILES, false); ui->tabWidget->setTabEnabled(TAB_IMAGES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); + ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); @@ -365,13 +259,13 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_INIFILES, true); ui->tabWidget->setTabEnabled(TAB_IMAGES, true); ui->tabWidget->setTabEnabled(TAB_ESPS, true); + ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); } - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr); ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration()); @@ -389,36 +283,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo if (ui->tabWidget->currentIndex() == TAB_NEXUS) { activateNexusTab(); } - - m_overwriteExpander.set(ui->overwriteExpander, ui->overwriteTree, true); - m_overwrittenExpander.set(ui->overwrittenExpander, ui->overwrittenTree, true); - m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); - - - m_advancedConflictFilter.set(ui->conflictsAdvancedFilter); - m_advancedConflictFilter.changed = [&]{ refreshConflictLists(false, true); }; - - // left-elide the overwrites column so that the nearest are visible - ui->conflictsAdvancedList->setItemDelegateForColumn( - 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); - - // left-elide the file column to see filenames - ui->conflictsAdvancedList->setItemDelegateForColumn( - 1, new ElideLeftDelegate(ui->conflictsAdvancedList)); - - // don't elide the overwritten by column so that the nearest are visible - - connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { - refreshConflictLists(false, true); - }); - - connect(ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&] { - refreshConflictLists(false, true); - }); - - connect(ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&] { - refreshConflictLists(false, true); - }); } @@ -488,41 +352,19 @@ int ModInfoDialog::tabIndex(const QString &tabId) void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); - s.directInterface().setValue("mod_info_conflicts", saveConflictsState()); - s.directInterface().setValue( - "mod_info_conflicts_overwrite", - ui->overwriteTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_conflicts_noconflict", - ui->noConflictTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_conflicts_overwritten", - ui->overwrittenTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_advanced_conflicts", - ui->conflictsAdvancedList->header()->saveState()); + for (const auto& tab : m_tabs) { + tab->saveState(s); + } } void ModInfoDialog::restoreState(const Settings& s) { restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - restoreConflictsState(s.directInterface().value("mod_info_conflicts").toByteArray()); - - ui->overwriteTree->header()->restoreState( - s.directInterface().value("mod_info_conflicts_overwrite").toByteArray()); - - ui->noConflictTree->header()->restoreState( - s.directInterface().value("mod_info_conflicts_noconflict").toByteArray()); - ui->overwrittenTree->header()->restoreState( - s.directInterface().value("mod_info_conflicts_overwritten").toByteArray()); - - ui->conflictsAdvancedList->header()->restoreState( - s.directInterface().value("mod_info_advanced_conflicts").toByteArray()); + for (const auto& tab : m_tabs) { + tab->restoreState(s); + } } void ModInfoDialog::restoreTabState(const QByteArray &state) @@ -556,37 +398,6 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) ui->tabWidget->blockSignals(false); } -void ModInfoDialog::restoreConflictsState(const QByteArray &state) -{ - QDataStream stream(state); - - bool overwriteExpanded = false; - bool overwrittenExpanded = false; - bool noConflictExpanded = false; - - stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded; - - if (stream.status() == QDataStream::Ok) { - m_overwriteExpander.toggle(overwriteExpanded); - m_overwrittenExpander.toggle(overwrittenExpanded); - m_nonconflictExpander.toggle(noConflictExpanded); - } - - int index = 0; - bool noConflictChecked = false; - bool showAllChecked = false; - bool showNearestChecked = false; - - stream >> index >> noConflictChecked >> showAllChecked >> showNearestChecked; - - if (stream.status() == QDataStream::Ok) { - ui->tabConflictsTabs->setCurrentIndex(index); - ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked); - ui->conflictsAdvancedShowAll->setChecked(showAllChecked); - ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked); - } -} - QByteArray ModInfoDialog::saveTabState() const { QByteArray result; @@ -599,251 +410,12 @@ QByteArray ModInfoDialog::saveTabState() const return result; } -QByteArray ModInfoDialog::saveConflictsState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - - stream - << m_overwriteExpander.opened() - << m_overwrittenExpander.opened() - << m_nonconflictExpander.opened() - << ui->tabConflictsTabs->currentIndex() - << ui->conflictsAdvancedShowNoConflict->isChecked() - << ui->conflictsAdvancedShowAll->isChecked() - << ui->conflictsAdvancedShowNearest->isChecked(); - - return result; -} - void ModInfoDialog::refreshLists() { refreshConflictLists(true, true); refreshFiles(); } -void ModInfoDialog::refreshConflictLists( - bool refreshGeneral, bool refreshAdvanced) -{ - int numNonConflicting = 0; - int numOverwrite = 0; - int numOverwritten = 0; - - if (refreshGeneral) { - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - ui->noConflictTree->clear(); - } - - if (refreshAdvanced) { - ui->conflictsAdvancedList->clear(); - } - - if (m_Origin != nullptr) { - std::vector files = m_Origin->getFiles(); - - for (const auto& file : m_Origin->getFiles()) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(m_RootPath); - - bool archive = false; - const int fileOrigin = file->getOrigin(archive); - const auto& alternatives = file->getAlternatives(); - - if (refreshGeneral) { - if (fileOrigin == m_Origin->getID()) { - if (!alternatives.empty()) { - ui->overwriteTree->addTopLevelItem(createOverwriteItem( - file->getIndex(), archive, fileName, relativeName, alternatives)); - - ++numOverwrite; - } else { - // otherwise, put the file in the noconflict tree - ui->noConflictTree->addTopLevelItem(createNoConflictItem( - file->getIndex(), archive, fileName, relativeName)); - - ++numNonConflicting; - } - } else { - ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( - file->getIndex(), fileOrigin, archive, fileName, relativeName)); - - ++numOverwritten; - } - } - - if (refreshAdvanced) { - auto* advancedItem = createAdvancedConflictItem( - file->getIndex(), fileOrigin, archive, - fileName, relativeName, alternatives); - - if (advancedItem) { - ui->conflictsAdvancedList->addTopLevelItem(advancedItem); - } - } - } - } - - if (refreshGeneral) { - ui->overwriteCount->display(numOverwrite); - ui->overwrittenCount->display(numOverwritten); - ui->noConflictCount->display(numNonConflicting); - } -} - -QTreeWidgetItem* ModInfoDialog::createOverwriteItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, - const FileEntry::AlternativesVector& alternatives) -{ - QString altString; - - for (const auto& alt : alternatives) { - if (!altString.isEmpty()) { - altString += ", "; - } - - altString += ToQString(m_Directory->getOriginByID(alt.first).getName()); - } - - QStringList fields(relativeName); - fields.append(altString); - - const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()); - - return new ConflictItem(fields, index, fileName, true, origin, archive); -} - -QTreeWidgetItem* ModInfoDialog::createNoConflictItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName) -{ - return new ConflictItem({relativeName}, index, fileName, false, "", archive); -} - -QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( - FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName) -{ - const FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); - - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); - - return new ConflictItem( - fields, index, fileName, true, ToQString(realOrigin.getName()), archive); -} - -QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( - FileEntry::Index index,int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, - const MOShared::FileEntry::AlternativesVector& alternatives) -{ - QString before, after; - - if (!alternatives.empty()) { - int beforePrio = 0; - int afterPrio = std::numeric_limits::max(); - - for (const auto& alt : alternatives) - { - const auto altOrigin = m_Directory->getOriginByID(alt.first); - - if (ui->conflictsAdvancedShowAll->isChecked()) { - // fills 'before' and 'after' with all the alternatives that come - // before and after this mod in terms of priority - - if (altOrigin.getPriority() < m_Origin->getPriority()) { - // add all the mods having a lower priority than this one - if (!before.isEmpty()) { - before += ", "; - } - - before += ToQString(altOrigin.getName()); - } else if (altOrigin.getPriority() > m_Origin->getPriority()) { - // add all the mods having a higher priority than this one - if (!after.isEmpty()) { - after += ", "; - } - - after += ToQString(altOrigin.getName()); - } - } else { - // keep track of the nearest mods that come before and after this one - // in terms of priority - - if (altOrigin.getPriority() < m_Origin->getPriority()) { - // the alternative has a lower priority than this mod - - if (altOrigin.getPriority() > beforePrio) { - // the alternative has a higher priority and therefore is closer - // to this mod, use it - before = ToQString(altOrigin.getName()); - beforePrio = altOrigin.getPriority(); - } - } - - if (altOrigin.getPriority() > m_Origin->getPriority()) { - // the alternative has a higher priority than this mod - - if (altOrigin.getPriority() < afterPrio) { - // the alternative has a lower priority and there is closer - // to this mod, use it - after = ToQString(altOrigin.getName()); - afterPrio = altOrigin.getPriority(); - } - } - } - } - - // the primary origin is never in the list of alternatives, so it has to - // be handled separately - // - // if 'after' is not empty, it means at least one alternative with a higher - // priority than this mod was found; if the user only wants to see the - // nearest mods, it's not worth checking for the primary origin because it - // will always have a higher priority than the alternatives (or it wouldn't - // be the primary) - if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { - FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); - - // if no mods overwrite this file, the primary origin is the same as this - // mod, so ignore that - if (realOrigin.getID() != m_Origin->getID()) { - if (!after.isEmpty()) { - after += ", "; - } - - after += ToQString(realOrigin.getName()); - } - } - } - - bool hasAlts = !before.isEmpty() || !after.isEmpty(); - - if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { - // if both before and after are empty, it means this file has no conflicts - // at all, only display it if the user wants it - if (!hasAlts) { - return nullptr; - } - } - - bool matched = m_advancedConflictFilter.matches([&](auto&& what) { - return - before.contains(what, Qt::CaseInsensitive) || - relativeName.contains(what, Qt::CaseInsensitive) || - after.contains(what, Qt::CaseInsensitive); - }); - - if (!matched) { - return nullptr; - } - - return new ConflictItem( - {before, relativeName, after}, index, fileName, hasAlts, "", archive); -} - void ModInfoDialog::refreshFiles() { // clearing @@ -1475,7 +1047,6 @@ void ModInfoDialog::refreshPrimaryCategoriesBox() } } - void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) { if (index != -1) { @@ -1483,19 +1054,6 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) } } - -void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) -{ - if (auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); - - if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); - } - } -} - FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) { const QString newName = oldName + ModInfo::s_HiddenExt; diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6ecf16bf..53b5d94a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -70,9 +70,31 @@ public: virtual void clear() = 0; virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; virtual bool canClose(); + virtual void saveState(Settings& s); + virtual void restoreState(const Settings& s); }; +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; + } +}; + + +bool canPreviewFile(PluginContainer* pluginContainer, bool isArchive, const QString& filename); +bool canOpenFile(bool isArchive, const QString& filename); +bool canHideFile(bool isArchive, const QString& filename); +bool canUnhideFile(bool isArchive, const QString& filename); + + /** * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system @@ -262,36 +284,13 @@ private: std::map m_RealTabPos; - ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; - FilterWidget m_advancedConflictFilter; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); void refreshFiles(); - QTreeWidgetItem* createOverwriteItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, - const MOShared::FileEntry::AlternativesVector& alternatives); - - QTreeWidgetItem* createNoConflictItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName); - - QTreeWidgetItem* createOverwrittenItem( - FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName); - - QTreeWidgetItem* createAdvancedConflictItem( - FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, - const MOShared::FileEntry::AlternativesVector& alternatives); - void restoreTabState(const QByteArray &state); - void restoreConflictsState(const QByteArray &state); - QByteArray saveTabState() const; - QByteArray saveConflictsState() const; void changeFiletreeVisibility(bool visible); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index f1212634..a26d15ab 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -241,7 +241,7 @@ - + List of esps, esms, and esls that can not be loaded by the game. @@ -285,7 +285,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Move a file to the data directory. @@ -308,7 +308,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Make the selected mod in the lower list unavailable. @@ -368,7 +368,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + ESPs in the data directory and thus visible to the game. @@ -386,23 +386,23 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Conflicts - + - 0 + 1 - + General - + 0 @@ -423,11 +423,11 @@ Most mods do not have optional esps, so chances are good you are looking at an e 0 - + - + - + 0 @@ -444,7 +444,7 @@ text-align: left; - + QFrame::Sunken @@ -461,7 +461,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -502,11 +502,11 @@ text-align: left; - + - + - + 0 @@ -523,7 +523,7 @@ text-align: left; - + QFrame::Sunken @@ -537,7 +537,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -578,11 +578,11 @@ text-align: left; - + - + - + 0 @@ -599,7 +599,7 @@ text-align: left; - + QFrame::Sunken @@ -613,7 +613,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -696,7 +696,7 @@ text-align: left; 0 - + Qt::CustomContextMenu @@ -751,7 +751,7 @@ text-align: left; 0 - + Whether files that have no conflicts should be visible in the list @@ -764,7 +764,7 @@ text-align: left; - + Shows all mods overwriting or being overwritten by this mod @@ -777,7 +777,7 @@ text-align: left; - + Shows only the nearest conflicting mods, in order of priority @@ -802,7 +802,7 @@ text-align: left; 0 - + Filter diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp new file mode 100644 index 00000000..17313436 --- /dev/null +++ b/src/modinfodialogconflicts.cpp @@ -0,0 +1,530 @@ +#include "modinfodialogconflicts.h" +#include "ui_modinfodialog.h" +#include "utility.h" + +using namespace MOShared; +using namespace MOBase; + +int naturalCompare(const QString& a, const QString& b) +{ + static QCollator c = []{ + QCollator c; + c.setNumericMode(true); + c.setCaseSensitivity(Qt::CaseInsensitive); + return c; + }(); + + return c.compare(a, b); +} + + +class ConflictItem : public QTreeWidgetItem +{ +public: + static const int FILENAME_USERROLE = Qt::UserRole + 1; + static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2; + static const int ARCHIVE_USERROLE = Qt::UserRole + 3; + static const int INDEX_USERROLE = Qt::UserRole + 4; + static const int HAS_ALTS_USERROLE = Qt::UserRole + 5; + + ConflictItem( + QStringList columns, FileEntry::Index index, const QString& fileName, + bool hasAltOrigins, const QString& altOrigin, bool archive) + : QTreeWidgetItem(columns) + { + setData(0, FILENAME_USERROLE, fileName); + setData(0, ALT_ORIGIN_USERROLE, altOrigin); + setData(0, ARCHIVE_USERROLE, archive); + setData(0, INDEX_USERROLE, index); + setData(0, HAS_ALTS_USERROLE, hasAltOrigins); + + if (archive) { + QFont f = font(0); + f.setItalic(true); + + for (int i=0; i); + return data(0, INDEX_USERROLE).toUInt(); + } + + bool canHide() const + { + return canHideFile(isArchive(), fileName()); + } + + bool canUnhide() const + { + return canUnhideFile(isArchive(), fileName()); + } + + bool canOpen() const + { + return canOpenFile(isArchive(), fileName()); + } + + bool canPreview(PluginContainer* pluginContainer) const + { + return canPreviewFile(pluginContainer, isArchive(), fileName()); + } + + bool operator<(const QTreeWidgetItem& other) const + { + const int column = treeWidget()->sortColumn(); + + if (column >= columnCount() || column >= other.columnCount()) { + // shouldn't happen + qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count"; + return false; + } + + return (naturalCompare(text(column), other.text(column)) < 0); + } +}; + + +ConflictsTab::ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) + : ui(ui), m_general(ui, oc), m_advanced(ui, oc) +{ +} + +void ConflictsTab::saveState(Settings& s) +{ + s.directInterface().setValue( + "mod_info_conflicts_tab", ui->tabConflictsTabs1->currentIndex()); + + m_general.saveState(s); + m_advanced.saveState(s); +} + +void ConflictsTab::restoreState(const Settings& s) +{ + ui->tabConflictsTabs1->setCurrentIndex( + s.directInterface().value("mod_info_conflicts_tab", 0).toInt()); + + m_general.restoreState(s); + m_advanced.restoreState(s); +} + + +GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) + : ui(ui), m_core(oc), m_origin(nullptr) +{ + m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true); + m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true); + m_expanders.nonconflict.set(ui->noConflictExpander1, ui->noConflictTree1); + + QObject::connect( + ui->overwriteTree1, &QTreeWidget::itemDoubleClicked, + [&](auto* item, int col){ onOverwriteActivated(item, col); }); +} + +void GeneralConflictsTab::saveState(Settings& s) +{ + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + + stream + << m_expanders.overwrite.opened() + << m_expanders.overwritten.opened() + << m_expanders.nonconflict.opened(); + + s.directInterface().setValue( + "mod_info_conflicts_general_expanders", result); + + s.directInterface().setValue( + "mod_info_conflicts_general_overwrite", + ui->overwriteTree1->header()->saveState()); + + s.directInterface().setValue( + "mod_info_conflicts_general_noconflict", + ui->noConflictTree1->header()->saveState()); + + s.directInterface().setValue( + "mod_info_conflicts_general_overwritten", + ui->overwrittenTree1->header()->saveState()); +} + +void GeneralConflictsTab::restoreState(const Settings& s) +{ + QDataStream stream(s.directInterface() + .value("mod_info_conflicts_general_expanders").toByteArray()); + + bool overwriteExpanded = false; + bool overwrittenExpanded = false; + bool noConflictExpanded = false; + + stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded; + + if (stream.status() == QDataStream::Ok) { + m_expanders.overwrite.toggle(overwriteExpanded); + m_expanders.overwritten.toggle(overwrittenExpanded); + m_expanders.nonconflict.toggle(noConflictExpanded); + } + + ui->overwriteTree1->header()->restoreState(s.directInterface() + .value("mod_info_conflicts_general_overwrite").toByteArray()); + + ui->noConflictTree1->header()->restoreState(s.directInterface() + .value("mod_info_conflicts_general_noconflict").toByteArray()); + + ui->overwrittenTree1->header()->restoreState(s.directInterface() + .value("mod_info_conflicts_general_overwritten").toByteArray()); +} + +void GeneralConflictsTab::rebuild(ModInfo::Ptr mod, FilesOrigin* origin) +{ + m_mod = mod; + m_origin = origin; + + update(); +} + +void GeneralConflictsTab::update() +{ + int numNonConflicting = 0; + int numOverwrite = 0; + int numOverwritten = 0; + + ui->overwriteTree1->clear(); + ui->overwrittenTree1->clear(); + ui->noConflictTree1->clear(); + + if (m_origin != nullptr) { + const auto rootPath = m_mod->absolutePath(); + + for (const auto& file : m_origin->getFiles()) { + const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + const QString fileName = relativeName.mid(0).prepend(rootPath); + + bool archive = false; + const int fileOrigin = file->getOrigin(archive); + const auto& alternatives = file->getAlternatives(); + + if (fileOrigin == m_origin->getID()) { + if (!alternatives.empty()) { + ui->overwriteTree1->addTopLevelItem(createOverwriteItem( + file->getIndex(), archive, fileName, relativeName, alternatives)); + + ++numOverwrite; + } else { + // otherwise, put the file in the noconflict tree + ui->noConflictTree1->addTopLevelItem(createNoConflictItem( + file->getIndex(), archive, fileName, relativeName)); + + ++numNonConflicting; + } + } else { + ui->overwrittenTree1->addTopLevelItem(createOverwrittenItem( + file->getIndex(), fileOrigin, archive, fileName, relativeName)); + + ++numOverwritten; + } + } + } + + ui->overwriteCount1->display(numOverwrite); + ui->overwrittenCount1->display(numOverwritten); + ui->noConflictCount1->display(numNonConflicting); +} + +QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, + const FileEntry::AlternativesVector& alternatives) +{ + const auto& ds = *m_core.directoryStructure(); + + QString altString; + + for (const auto& alt : alternatives) { + if (!altString.isEmpty()) { + altString += ", "; + } + + altString += ToQString(ds.getOriginByID(alt.first).getName()); + } + + QStringList fields(relativeName); + fields.append(altString); + + const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); + + return new ConflictItem(fields, index, fileName, true, origin, archive); +} + +QTreeWidgetItem* GeneralConflictsTab::createNoConflictItem( + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName) +{ + return new ConflictItem({relativeName}, index, fileName, false, "", archive); +} + +QTreeWidgetItem* GeneralConflictsTab::createOverwrittenItem( + FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName) +{ + const auto& ds = *m_core.directoryStructure(); + + const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + + QStringList fields(relativeName); + fields.append(ToQString(realOrigin.getName())); + + return new ConflictItem( + fields, index, fileName, true, ToQString(realOrigin.getName()), archive); +} + +void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int) +{ + if (auto* ci=dynamic_cast(item)) { + const auto origin = ci->altOrigin(); + + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } + } +} + + +AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) + : ui(ui), m_core(oc), m_origin(nullptr) +{ + // left-elide the overwrites column so that the nearest are visible + ui->conflictsAdvancedList1->setItemDelegateForColumn( + 0, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + + // left-elide the file column to see filenames + ui->conflictsAdvancedList1->setItemDelegateForColumn( + 1, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + + // don't elide the overwritten by column so that the nearest are visible + + QObject::connect(ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, [&] { + update(); + }); + + QObject::connect(ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, [&] { + update(); + }); + + QObject::connect(ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, [&] { + update(); + }); + + m_filter.set(ui->conflictsAdvancedFilter1); + m_filter.changed = [&]{ update(); }; +} + +void AdvancedConflictsTab::saveState(Settings& s) +{ + s.directInterface().setValue( + "mod_info_conflicts_advanced_list", + ui->conflictsAdvancedList1->header()->saveState()); + + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + + stream + << ui->conflictsAdvancedShowNoConflict1->isChecked() + << ui->conflictsAdvancedShowAll1->isChecked() + << ui->conflictsAdvancedShowNearest1->isChecked(); + + s.directInterface().setValue( + "mod_info_conflicts_advanced_options", + ui->conflictsAdvancedList1->header()->saveState()); +} + +void AdvancedConflictsTab::restoreState(const Settings& s) +{ + ui->conflictsAdvancedList1->header()->restoreState( + s.directInterface().value("mod_info_conflicts_advanced_list").toByteArray()); + + QDataStream stream(s.directInterface() + .value("mod_info_conflicts_advanced_options").toByteArray()); + + bool noConflictChecked = false; + bool showAllChecked = false; + bool showNearestChecked = false; + + stream >> noConflictChecked >> showAllChecked >> showNearestChecked; + + if (stream.status() == QDataStream::Ok) { + ui->conflictsAdvancedShowNoConflict1->setChecked(noConflictChecked); + ui->conflictsAdvancedShowAll1->setChecked(showAllChecked); + ui->conflictsAdvancedShowNearest1->setChecked(showNearestChecked); + } +} + +void AdvancedConflictsTab::rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + m_mod = mod; + m_origin = origin; + + update(); +} + +void AdvancedConflictsTab::update() +{ + ui->conflictsAdvancedList1->clear(); + + if (m_origin != nullptr) { + const auto rootPath = m_mod->absolutePath(); + + for (const auto& file : m_origin->getFiles()) { + const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + const QString fileName = relativeName.mid(0).prepend(rootPath); + + bool archive = false; + const int fileOrigin = file->getOrigin(archive); + const auto& alternatives = file->getAlternatives(); + + auto* advancedItem = createItem( + file->getIndex(), fileOrigin, archive, + fileName, relativeName, alternatives); + + if (advancedItem) { + ui->conflictsAdvancedList1->addTopLevelItem(advancedItem); + } + } + } +} + +QTreeWidgetItem* AdvancedConflictsTab::createItem( + FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives) +{ + const auto& ds = *m_core.directoryStructure(); + + QString before, after; + + if (!alternatives.empty()) { + int beforePrio = 0; + int afterPrio = std::numeric_limits::max(); + + for (const auto& alt : alternatives) + { + const auto altOrigin = ds.getOriginByID(alt.first); + + if (ui->conflictsAdvancedShowAll1->isChecked()) { + // fills 'before' and 'after' with all the alternatives that come + // before and after this mod in terms of priority + + if (altOrigin.getPriority() < m_origin->getPriority()) { + // add all the mods having a lower priority than this one + if (!before.isEmpty()) { + before += ", "; + } + + before += ToQString(altOrigin.getName()); + } else if (altOrigin.getPriority() > m_origin->getPriority()) { + // add all the mods having a higher priority than this one + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(altOrigin.getName()); + } + } else { + // keep track of the nearest mods that come before and after this one + // in terms of priority + + if (altOrigin.getPriority() < m_origin->getPriority()) { + // the alternative has a lower priority than this mod + + if (altOrigin.getPriority() > beforePrio) { + // the alternative has a higher priority and therefore is closer + // to this mod, use it + before = ToQString(altOrigin.getName()); + beforePrio = altOrigin.getPriority(); + } + } + + if (altOrigin.getPriority() > m_origin->getPriority()) { + // the alternative has a higher priority than this mod + + if (altOrigin.getPriority() < afterPrio) { + // the alternative has a lower priority and there is closer + // to this mod, use it + after = ToQString(altOrigin.getName()); + afterPrio = altOrigin.getPriority(); + } + } + } + } + + // the primary origin is never in the list of alternatives, so it has to + // be handled separately + // + // if 'after' is not empty, it means at least one alternative with a higher + // priority than this mod was found; if the user only wants to see the + // nearest mods, it's not worth checking for the primary origin because it + // will always have a higher priority than the alternatives (or it wouldn't + // be the primary) + if (after.isEmpty() || ui->conflictsAdvancedShowAll1->isChecked()) { + FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + + // if no mods overwrite this file, the primary origin is the same as this + // mod, so ignore that + if (realOrigin.getID() != m_origin->getID()) { + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(realOrigin.getName()); + } + } + } + + bool hasAlts = !before.isEmpty() || !after.isEmpty(); + + if (!ui->conflictsAdvancedShowNoConflict1->isChecked()) { + // if both before and after are empty, it means this file has no conflicts + // at all, only display it if the user wants it + if (!hasAlts) { + return nullptr; + } + } + + bool matched = m_filter.matches([&](auto&& what) { + return + before.contains(what, Qt::CaseInsensitive) || + relativeName.contains(what, Qt::CaseInsensitive) || + after.contains(what, Qt::CaseInsensitive); + }); + + if (!matched) { + return nullptr; + } + + return new ConflictItem( + {before, relativeName, after}, index, fileName, hasAlts, "", archive); +} diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h new file mode 100644 index 00000000..894f9dd4 --- /dev/null +++ b/src/modinfodialogconflicts.h @@ -0,0 +1,86 @@ +#ifndef MODINFODIALOGCONFLICTS_H +#define MODINFODIALOGCONFLICTS_H + +#include "modinfodialog.h" +#include "expanderwidget.h" + +class GeneralConflictsTab +{ +public: + GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + + void saveState(Settings& s); + void restoreState(const Settings& s); + + void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void update(); + +private: + struct Expanders + { + ExpanderWidget overwrite, overwritten, nonconflict; + }; + + Ui::ModInfoDialog* ui; + OrganizerCore& m_core; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; + Expanders m_expanders; + + QTreeWidgetItem* createOverwriteItem( + MOShared::FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives); + + QTreeWidgetItem* createNoConflictItem( + MOShared::FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName); + + QTreeWidgetItem* createOverwrittenItem( + MOShared::FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName); + + void onOverwriteActivated(QTreeWidgetItem* item, int); +}; + + +class AdvancedConflictsTab +{ +public: + AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + + void saveState(Settings& s); + void restoreState(const Settings& s); + + void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void update(); + +private: + Ui::ModInfoDialog* ui; + OrganizerCore& m_core; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; + FilterWidget m_filter; + + QTreeWidgetItem* createItem( + MOShared::FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives); +}; + + +class ConflictsTab : public ModInfoDialogTab +{ +public: + ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; + +private: + Ui::ModInfoDialog* ui; + GeneralConflictsTab m_general; + AdvancedConflictsTab m_advanced; +}; + +#endif // MODINFODIALOGCONFLICTS_H -- cgit v1.3.1 From b5c47eb161f6e20c9275909d32202e52bf3ba4a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:24:44 -0400 Subject: pass parent widget to tabs for dialogs finished moving over conflict tab to its own set of files --- src/modinfodialog.cpp | 444 +++++++---------------------------------- src/modinfodialog.h | 62 ++---- src/modinfodialogconflicts.cpp | 441 +++++++++++++++++++++++++++++++++++++--- src/modinfodialogconflicts.h | 80 +++++++- src/modinfodialogesps.cpp | 30 +-- src/modinfodialogesps.h | 5 +- src/modinfodialogimages.cpp | 2 +- src/modinfodialogimages.h | 4 +- src/modinfodialogtextfiles.cpp | 17 +- src/modinfodialogtextfiles.h | 13 +- 10 files changed, 616 insertions(+), 482 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c24176b3..d52103b9 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -63,20 +63,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +const int max_scan_for_context_menu = 50; -std::vector> ModInfoDialogTab::createTabs( - Ui::ModInfoDialog* ui) -{ - std::vector> v; - - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - - return v; -} bool ModInfoDialogTab::canClose() { @@ -93,6 +81,26 @@ void ModInfoDialogTab::restoreState(const Settings& s) // no-op } +void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) +{ + // no-op +} + +void ModInfoDialogTab::update() +{ + // no-op +} + +void ModInfoDialogTab::emitOriginModified(int originID) +{ + emit originModified(originID); +} + +void ModInfoDialogTab::emitModOpen(QString name) +{ + emit modOpen(name); +} + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); @@ -109,20 +117,16 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS return LHS.m_SortValue < RHS.m_SortValue; } -// if there are more than 50 selected items in the conflict tree or filetree, -// don't bother checking whether menu items apply to them, just show all of them -const int max_scan_for_context_menu = 50; - bool canPreviewFile( - PluginContainer* pluginContainer, bool isArchive, const QString& filename) + PluginContainer& pluginContainer, bool isArchive, const QString& filename) { if (isArchive) { return false; } const auto ext = QFileInfo(filename).suffix(); - return pluginContainer->previewGenerator().previewSupported(ext); + return pluginContainer.previewGenerator().previewSupported(ext); } bool canOpenFile(bool isArchive, const QString&) @@ -161,6 +165,18 @@ bool canUnhideFile(bool isArchive, const QString& filename) return true; } +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName) +{ + const QString newName = oldName + ModInfo::s_HiddenExt; + return renamer.rename(oldName, newName); +} + +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName) +{ + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + return renamer.rename(oldName, newName); +} + ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), @@ -172,7 +188,20 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo { ui->setupUi(this); - m_tabs = ModInfoDialogTab::createTabs(ui); + m_tabs = createTabs(); + + for (std::size_t i=0; i(i)); + }); + } this->setWindowTitle(modInfo->name()); this->setWindowModality(Qt::WindowModal); @@ -223,12 +252,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - // refresh everything but the conflict lists, which are done in exec() because - // they depend on restoring the state to some widgets; this refresh has to be - // done here because some of the checks below depend on the ui to decide which - // tabs to enable - refreshFiles(); - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); @@ -283,8 +306,11 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo if (ui->tabWidget->currentIndex() == TAB_NEXUS) { activateNexusTab(); } -} + for (auto& tab : m_tabs) { + tab->setMod(m_ModInfo, m_Origin); + } +} ModInfoDialog::~ModInfoDialog() { @@ -301,11 +327,23 @@ ModInfoDialog::~ModInfoDialog() delete m_Settings; } +std::vector> ModInfoDialog::createTabs() +{ + std::vector> v; + + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique( + this, ui, *m_OrganizerCore, *m_PluginContainer)); + + return v; +} int ModInfoDialog::exec() { - // no need to refresh the other stuff, that was done in the constructor - refreshConflictLists(true, true); + refreshLists(); return TutorableDialog::exec(); } @@ -412,17 +450,15 @@ QByteArray ModInfoDialog::saveTabState() const void ModInfoDialog::refreshLists() { - refreshConflictLists(true, true); + for (auto& tab : m_tabs) { + tab->update(); + } + refreshFiles(); } void ModInfoDialog::refreshFiles() { - // clearing - for (auto& tab : m_tabs) { - tab->clear(); - } - if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -936,7 +972,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (!canPreviewFile(m_PluginContainer, false, fileName)) { + if (!canPreviewFile(*m_PluginContainer, false, fileName)) { enablePreview = false; } @@ -1054,342 +1090,6 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) } } -FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) -{ - const QString newName = oldName + ModInfo::s_HiddenExt; - return renamer.rename(oldName, newName); -} - -FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const QString &oldName) -{ - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - return renamer.rename(oldName, newName); -} - -void ModInfoDialog::changeConflictItemsVisibility( - const QList& items, bool visible) -{ - bool changed = false; - bool stop = false; - - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << items.size() << " conflict files"; - - QFlags flags = - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - - if (items.size() > 1) { - flags |= FileRenamer::MULTIPLE; - } - - FileRenamer renamer(this, flags); - - for (const auto* item : items) { - if (stop) { - break; - } - - const auto* ci = dynamic_cast(item); - if (!ci) { - continue; - } - - auto result = FileRenamer::RESULT_CANCEL; - - if (visible) { - if (!ci->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; - continue; - } - result = unhideFile(renamer, ci->fileName()); - - } else { - if (!ci->canHide()) { - qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; - continue; - } - result = hideFile(renamer, ci->fileName()); - } - - switch (result) { - case FileRenamer::RESULT_OK: { - // will trigger a refresh at the end - changed = true; - break; - } - - case FileRenamer::RESULT_SKIP: { - // nop - break; - } - - case FileRenamer::RESULT_CANCEL: { - // stop right now, but make sure to refresh if needed - stop = true; - break; - } - } - } - - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; - - if (changed) { - qDebug().nospace() << "triggering refresh"; - if (m_Origin) { - emit originModified(m_Origin->getID()); - } - refreshLists(); - } -} - -void ModInfoDialog::openConflictItems(const QList& items) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - m_OrganizerCore->executeFileVirtualized(this, ci->fileName()); - } - } -} - -void ModInfoDialog::previewConflictItems(const QList& items) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - m_OrganizerCore->previewFileWithAlternatives(this, ci->fileName()); - } - } -} - -void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->overwriteTree); -} - -void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->overwrittenTree); -} - -void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->noConflictTree); -} - -void ModInfoDialog::on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->conflictsAdvancedList); -} - -void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) -{ - auto actions = createConflictMenuActions(tree->selectedItems()); - - QMenu menu; - - // open - if (actions.open) { - connect(actions.open, &QAction::triggered, [&]{ - openConflictItems(tree->selectedItems()); - }); - - menu.addAction(actions.open); - } - - // preview - if (actions.preview) { - connect(actions.preview, &QAction::triggered, [&]{ - previewConflictItems(tree->selectedItems()); - }); - - menu.addAction(actions.preview); - } - - // hide - if (actions.hide) { - connect(actions.hide, &QAction::triggered, [&]{ - changeConflictItemsVisibility(tree->selectedItems(), false); - }); - - menu.addAction(actions.hide); - } - - // unhide - if (actions.unhide) { - connect(actions.unhide, &QAction::triggered, [&]{ - changeConflictItemsVisibility(tree->selectedItems(), true); - }); - - menu.addAction(actions.unhide); - } - - // goto - if (actions.gotoMenu) { - menu.addMenu(actions.gotoMenu); - - for (auto* a : actions.gotoActions) { - connect(a, &QAction::triggered, [&, name=a->text()]{ - close(); - emit modOpen(name, TAB_CONFLICTS); - }); - - actions.gotoMenu->addAction(a); - } - } - - if (!menu.isEmpty()) { - menu.exec(tree->viewport()->mapToGlobal(pos)); - } -} - -ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( - const QList& selection) -{ - if (selection.empty()) { - return {}; - } - - bool enableHide = true; - bool enableUnhide = true; - bool enableOpen = true; - bool enablePreview = true; - bool enableGoto = true; - - if (selection.size() == 1) { - // this is a single selection - const auto* ci = dynamic_cast(selection[0]); - if (!ci) { - return {}; - } - - enableHide = ci->canHide(); - enableUnhide = ci->canUnhide(); - enableOpen = ci->canOpen(); - enablePreview = ci->canPreview(m_PluginContainer); - enableGoto = ci->hasAlts(); - } - else { - // this is a multiple selection, don't show open/preview so users don't open - // a thousand files - enableOpen = false; - enablePreview = false; - - // don't bother with this on multiple selection, at least for now - enableGoto = false; - - if (selection.size() < max_scan_for_context_menu) { - // if the number of selected items is low, checking them to accurately - // show the menu items is worth it - enableHide = false; - enableUnhide = false; - - for (const auto* item : selection) { - if (const auto* ci=dynamic_cast(item)) { - if (ci->canHide()) { - enableHide = true; - } - - if (ci->canUnhide()) { - enableUnhide = true; - } - - if (enableHide && enableUnhide && enableGoto) { - // found all, no need to check more - break; - } - } - } - } - } - - ConflictActions actions; - - actions.hide = new QAction(tr("Hide"), this); - actions.hide->setEnabled(enableHide); - - // note that it is possible for hidden files to appear if they override other - // hidden files from another mod - actions.unhide = new QAction(tr("Unhide"), this); - actions.unhide->setEnabled(enableUnhide); - - actions.open = new QAction(tr("Open/Execute"), this); - actions.open->setEnabled(enableOpen); - - actions.preview = new QAction(tr("Preview"), this); - actions.preview->setEnabled(enablePreview); - - actions.gotoMenu = new QMenu(tr("Go to..."), this); - actions.gotoMenu->setEnabled(enableGoto); - - if (enableGoto) { - actions.gotoActions = createGotoActions(selection); - } - - return actions; -} - -std::vector ModInfoDialog::createGotoActions(const QList& selection) -{ - if (!m_Origin || selection.size() != 1) { - return {}; - } - - const auto* item = dynamic_cast(selection[0]); - if (!item) { - return {}; - } - - auto file = m_Origin->findFile(item->fileIndex()); - if (!file) { - return {}; - } - - - std::vector mods; - - // add all alternatives - for (const auto& alt : file->getAlternatives()) { - const auto& o = m_Directory->getOriginByID(alt.first); - if (o.getID() != m_Origin->getID()) { - mods.push_back(ToQString(o.getName())); - } - } - - // add the real origin if different from this mod - const FilesOrigin& realOrigin = m_Directory->getOriginByID(file->getOrigin()); - if (realOrigin.getID() != m_Origin->getID()) { - mods.push_back(ToQString(realOrigin.getName())); - } - - std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { - return (QString::localeAwareCompare(a, b) < 0); - }); - - std::vector actions; - - for (const auto& name : mods) { - actions.push_back(new QAction(name, this)); - } - - return actions; -} - -void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) -{ - if (const auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); - - if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); - } - } -} - void ModInfoDialog::on_refreshButton_clicked() { if (m_ModInfo->getNexusID() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 53b5d94a..2a3ee2d8 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -54,12 +54,11 @@ class CategoryFactory; class TextEditor; -class ModInfoDialogTab +class ModInfoDialogTab : public QObject { -public: - static std::vector> createTabs( - Ui::ModInfoDialog* ui); + Q_OBJECT; +public: ModInfoDialogTab() = default; ModInfoDialogTab(const ModInfoDialogTab&) = delete; ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; @@ -72,6 +71,17 @@ public: virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); + + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + virtual void update(); + +signals: + void originModified(int originID); + void modOpen(QString name); + +protected: + void emitOriginModified(int originID); + void emitModOpen(QString name); }; @@ -89,11 +99,14 @@ protected: }; -bool canPreviewFile(PluginContainer* pluginContainer, bool isArchive, const QString& filename); +bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(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); + /** * this is a larger dialog used to visualise information about the mod. @@ -185,8 +198,6 @@ private: bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); void saveCategories(QTreeWidgetItem *currentNode); - FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); - FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); void addCheckedCategories(QTreeWidgetItem *tree); void refreshPrimaryCategoriesBox(); @@ -216,12 +227,6 @@ private slots: void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_overwriteTree_customContextMenuRequested(const QPoint &pos); - void on_overwrittenTree_customContextMenuRequested(const QPoint &pos); - void on_noConflictTree_customContextMenuRequested(const QPoint &pos); - void on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); @@ -235,22 +240,6 @@ private slots: private: using FileEntry = MOShared::FileEntry; - struct ConflictActions - { - QAction* hide; - QAction* unhide; - QAction* open; - QAction* preview; - QMenu* gotoMenu; - std::vector gotoActions; - - ConflictActions() : - hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), - gotoMenu(nullptr) - { - } - }; - Ui::ModInfoDialog *ui; ModInfo::Ptr m_ModInfo; @@ -285,27 +274,14 @@ private: std::map m_RealTabPos; + std::vector> createTabs(); - void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); void refreshFiles(); void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; void changeFiletreeVisibility(bool visible); - - void openConflictItems(const QList& items); - void previewConflictItems(const QList& items); - void changeConflictItemsVisibility( - const QList& items, bool visible); - - void showConflictMenu(const QPoint &pos, QTreeWidget* tree); - - ConflictActions createConflictMenuActions( - const QList& selection); - - std::vector createGotoActions( - const QList& selection); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 17313436..22306d0c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -5,6 +5,11 @@ using namespace MOShared; using namespace MOBase; +// if there are more than 50 selected items in the conflict tree or filetree, +// don't bother checking whether menu items apply to them, just show all of them +const int max_scan_for_context_menu = 50; + + int naturalCompare(const QString& a, const QString& b) { static QCollator c = []{ @@ -89,7 +94,7 @@ public: return canOpenFile(isArchive(), fileName()); } - bool canPreview(PluginContainer* pluginContainer) const + bool canPreview(PluginContainer& pluginContainer) const { return canPreviewFile(pluginContainer, isArchive(), fileName()); } @@ -109,9 +114,45 @@ public: }; -ConflictsTab::ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_general(ui, oc), m_advanced(ui, oc) +ConflictsTab::ConflictsTab( + QWidget* parent, Ui::ModInfoDialog* ui, + OrganizerCore& oc, PluginContainer& plugin) : + m_parent(parent), ui(ui), m_core(oc), m_plugin(plugin), + m_origin(nullptr), m_general(this, ui, oc), m_advanced(this, ui, oc) +{ + connect( + &m_general, &GeneralConflictsTab::modOpen, + [&](const QString& name){ emitModOpen(name); }); + + connect( + &m_advanced, &AdvancedConflictsTab::modOpen, + [&](const QString& name){ emitModOpen(name); }); +} + +void ConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + m_mod = mod; + m_origin = origin; + + m_general.setMod(mod, origin); + m_advanced.setMod(mod, origin); +} + +void ConflictsTab::update() { + m_general.update(); + m_advanced.update(); +} + +void ConflictsTab::clear() +{ + m_general.clear(); + m_advanced.clear(); +} + +bool ConflictsTab::feedFile(const QString&, const QString&) +{ + return false; } void ConflictsTab::saveState(Settings& s) @@ -132,9 +173,307 @@ void ConflictsTab::restoreState(const Settings& s) m_advanced.restoreState(s); } +void ConflictsTab::changeItemsVisibility( + const QList& items, bool visible) +{ + bool changed = false; + bool stop = false; + + qDebug().nospace() + << (visible ? "unhiding" : "hiding") << " " + << items.size() << " conflict files"; + + QFlags flags = + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + + if (items.size() > 1) { + flags |= FileRenamer::MULTIPLE; + } + + FileRenamer renamer(m_parent, flags); + + for (const auto* item : items) { + if (stop) { + break; + } + + const auto* ci = dynamic_cast(item); + if (!ci) { + continue; + } + + auto result = FileRenamer::RESULT_CANCEL; + + if (visible) { + if (!ci->canUnhide()) { + qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; + continue; + } + + result = unhideFile(renamer, ci->fileName()); + + } else { + if (!ci->canHide()) { + qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; + continue; + } + + result = hideFile(renamer, ci->fileName()); + } + + switch (result) { + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } + + case FileRenamer::RESULT_SKIP: { + // nop + break; + } + + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; + } + } + } + + qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; + + if (changed) { + qDebug().nospace() << "triggering refresh"; + + if (m_origin) { + emit originModified(m_origin->getID()); + } + + update(); + } +} + +void ConflictsTab::openItems(const QList& items) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + if (auto* ci=dynamic_cast(item)) { + m_core.executeFileVirtualized(m_parent, ci->fileName()); + } + } +} + +void ConflictsTab::previewItems(const QList& items) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + if (auto* ci=dynamic_cast(item)) { + m_core.previewFileWithAlternatives(m_parent, ci->fileName()); + } + } +} + +void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) +{ + auto actions = createMenuActions(tree->selectedItems()); + + QMenu menu; + + // open + if (actions.open) { + connect(actions.open, &QAction::triggered, [&]{ + openItems(tree->selectedItems()); + }); + + menu.addAction(actions.open); + } + + // preview + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewItems(tree->selectedItems()); + }); + + menu.addAction(actions.preview); + } + + // hide + if (actions.hide) { + connect(actions.hide, &QAction::triggered, [&]{ + changeItemsVisibility(tree->selectedItems(), false); + }); + + menu.addAction(actions.hide); + } + + // unhide + if (actions.unhide) { + connect(actions.unhide, &QAction::triggered, [&]{ + changeItemsVisibility(tree->selectedItems(), true); + }); + + menu.addAction(actions.unhide); + } + + // goto + if (actions.gotoMenu) { + menu.addMenu(actions.gotoMenu); + + for (auto* a : actions.gotoActions) { + connect(a, &QAction::triggered, [&, name=a->text()]{ + emitModOpen(name); + }); + + actions.gotoMenu->addAction(a); + } + } + + if (!menu.isEmpty()) { + menu.exec(tree->viewport()->mapToGlobal(pos)); + } +} + +ConflictsTab::Actions ConflictsTab::createMenuActions( + const QList& selection) +{ + if (selection.empty()) { + return {}; + } + + bool enableHide = true; + bool enableUnhide = true; + bool enableOpen = true; + bool enablePreview = true; + bool enableGoto = true; + + if (selection.size() == 1) { + // this is a single selection + const auto* ci = dynamic_cast(selection[0]); + if (!ci) { + return {}; + } + + enableHide = ci->canHide(); + enableUnhide = ci->canUnhide(); + enableOpen = ci->canOpen(); + enablePreview = ci->canPreview(m_plugin); + enableGoto = ci->hasAlts(); + } + else { + // this is a multiple selection, don't show open/preview so users don't open + // a thousand files + enableOpen = false; + enablePreview = false; + + // don't bother with this on multiple selection, at least for now + enableGoto = false; + + if (selection.size() < max_scan_for_context_menu) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; + + for (const auto* item : selection) { + if (const auto* ci=dynamic_cast(item)) { + if (ci->canHide()) { + enableHide = true; + } + + if (ci->canUnhide()) { + enableUnhide = true; + } + + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more + break; + } + } + } + } + } + + Actions actions; + + actions.hide = new QAction(tr("Hide"), m_parent); + actions.hide->setEnabled(enableHide); + + // note that it is possible for hidden files to appear if they override other + // hidden files from another mod + actions.unhide = new QAction(tr("Unhide"), m_parent); + actions.unhide->setEnabled(enableUnhide); + + actions.open = new QAction(tr("Open/Execute"), m_parent); + actions.open->setEnabled(enableOpen); + + actions.preview = new QAction(tr("Preview"), m_parent); + actions.preview->setEnabled(enablePreview); + + actions.gotoMenu = new QMenu(tr("Go to..."), m_parent); + actions.gotoMenu->setEnabled(enableGoto); + + if (enableGoto) { + actions.gotoActions = createGotoActions(selection); + } + + return actions; +} + +std::vector ConflictsTab::createGotoActions( + const QList& selection) +{ + if (!m_origin || selection.size() != 1) { + return {}; + } + + const auto* item = dynamic_cast(selection[0]); + if (!item) { + return {}; + } + + auto file = m_origin->findFile(item->fileIndex()); + if (!file) { + return {}; + } + + + std::vector mods; + const auto& ds = *m_core.directoryStructure(); + + // add all alternatives + for (const auto& alt : file->getAlternatives()) { + const auto& o = ds.getOriginByID(alt.first); + if (o.getID() != m_origin->getID()) { + mods.push_back(ToQString(o.getName())); + } + } + + // add the real origin if different from this mod + const FilesOrigin& realOrigin = ds.getOriginByID(file->getOrigin()); + if (realOrigin.getID() != m_origin->getID()) { + mods.push_back(ToQString(realOrigin.getName())); + } + + std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { + return (QString::localeAwareCompare(a, b) < 0); + }); + + std::vector actions; + + for (const auto& name : mods) { + actions.push_back(new QAction(name, m_parent)); + } + + return actions; +} -GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_core(oc), m_origin(nullptr) + +GeneralConflictsTab::GeneralConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) + : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true); m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true); @@ -143,6 +482,33 @@ GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& o QObject::connect( ui->overwriteTree1, &QTreeWidget::itemDoubleClicked, [&](auto* item, int col){ onOverwriteActivated(item, col); }); + + QObject::connect( + ui->overwrittenTree1, &QTreeWidget::itemDoubleClicked, + [&](auto* item, int col){ onOverwrittenActivated(item, col); }); + + QObject::connect( + ui->overwriteTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree1); }); + + QObject::connect( + ui->overwrittenTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree1); }); + + QObject::connect( + ui->noConflictTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree1); }); +} + +void GeneralConflictsTab::clear() +{ + ui->overwriteTree1->clear(); + ui->overwrittenTree1->clear(); + ui->noConflictTree1->clear(); + + ui->overwriteCount1->display(0); + ui->overwrittenCount1->display(0); + ui->noConflictCount1->display(0); } void GeneralConflictsTab::saveState(Settings& s) @@ -198,24 +564,20 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::rebuild(ModInfo::Ptr mod, FilesOrigin* origin) +void GeneralConflictsTab::setMod(ModInfo::Ptr mod, FilesOrigin* origin) { m_mod = mod; m_origin = origin; - - update(); } void GeneralConflictsTab::update() { + clear(); + int numNonConflicting = 0; int numOverwrite = 0; int numOverwritten = 0; - ui->overwriteTree1->clear(); - ui->overwrittenTree1->clear(); - ui->noConflictTree1->clear(); - if (m_origin != nullptr) { const auto rootPath = m_mod->absolutePath(); @@ -307,15 +669,26 @@ void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int) const auto origin = ci->altOrigin(); if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); + emit modOpen(origin); } } } +void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) +{ + if (const auto* ci=dynamic_cast(item)) { + const auto origin = ci->altOrigin(); + + if (!origin.isEmpty()) { + emit modOpen(origin); + } + } +} -AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_core(oc), m_origin(nullptr) + +AdvancedConflictsTab::AdvancedConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) + : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList1->setItemDelegateForColumn( @@ -327,22 +700,31 @@ AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& // don't elide the overwritten by column so that the nearest are visible - QObject::connect(ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, + [&]{ update(); }); - QObject::connect(ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, + [&]{ update(); }); - QObject::connect(ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, + [&]{ update(); }); + + QObject::connect( + ui->conflictsAdvancedList1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList1); }); m_filter.set(ui->conflictsAdvancedFilter1); m_filter.changed = [&]{ update(); }; } +void AdvancedConflictsTab::clear() +{ + ui->conflictsAdvancedList1->clear(); +} + void AdvancedConflictsTab::saveState(Settings& s) { s.directInterface().setValue( @@ -358,8 +740,7 @@ void AdvancedConflictsTab::saveState(Settings& s) << ui->conflictsAdvancedShowNearest1->isChecked(); s.directInterface().setValue( - "mod_info_conflicts_advanced_options", - ui->conflictsAdvancedList1->header()->saveState()); + "mod_info_conflicts_advanced_options", result); } void AdvancedConflictsTab::restoreState(const Settings& s) @@ -383,17 +764,15 @@ void AdvancedConflictsTab::restoreState(const Settings& s) } } -void AdvancedConflictsTab::rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +void AdvancedConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; m_origin = origin; - - update(); } void AdvancedConflictsTab::update() { - ui->conflictsAdvancedList1->clear(); + clear(); if (m_origin != nullptr) { const auto rootPath = m_mod->absolutePath(); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 894f9dd4..5843884a 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -4,23 +4,33 @@ #include "modinfodialog.h" #include "expanderwidget.h" -class GeneralConflictsTab +class ConflictsTab; + +class GeneralConflictsTab : public QObject { + Q_OBJECT; + public: - GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + GeneralConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + void clear(); void saveState(Settings& s); void restoreState(const Settings& s); - void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); +signals: + void modOpen(QString name); + private: struct Expanders { ExpanderWidget overwrite, overwritten, nonconflict; }; + ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; ModInfo::Ptr m_mod; @@ -41,21 +51,34 @@ private: const QString& fileName, const QString& relativeName); void onOverwriteActivated(QTreeWidgetItem* item, int); + void onOverwrittenActivated(QTreeWidgetItem *item, int); + + void onOverwriteTreeContext(const QPoint &pos); + void onOverwrittenTreeContext(const QPoint &pos); + void onNoConflictTreeContext(const QPoint &pos); }; -class AdvancedConflictsTab +class AdvancedConflictsTab : public QObject { + Q_OBJECT; + public: - AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + AdvancedConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + void clear(); void saveState(Settings& s); void restoreState(const Settings& s); - void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); +signals: + void modOpen(QString name); + private: + ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; ModInfo::Ptr m_mod; @@ -71,16 +94,59 @@ private: class ConflictsTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + ConflictsTab( + QWidget* parent, Ui::ModInfoDialog* ui, + OrganizerCore& oc, PluginContainer& plugin); + + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + void update() override; + void clear() override; + bool feedFile(const QString& rootPath, const QString& filename) override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; + void openItems(const QList& items); + void previewItems(const QList& items); + + void changeItemsVisibility( + const QList& items, bool visible); + + void showContextMenu(const QPoint &pos, QTreeWidget* tree); + private: + struct Actions + { + QAction* hide; + QAction* unhide; + QAction* open; + QAction* preview; + QMenu* gotoMenu; + std::vector gotoActions; + + Actions() : + hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), + gotoMenu(nullptr) + { + } + }; + + QWidget* m_parent; Ui::ModInfoDialog* ui; + OrganizerCore& m_core; + PluginContainer& m_plugin; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; GeneralConflictsTab m_general; AdvancedConflictsTab m_advanced; + + Actions createMenuActions(const QList& selection); + + std::vector createGotoActions( + const QList& selection); }; #endif // MODINFODIALOGCONFLICTS_H diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 32dcdb35..6c87b10d 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -123,20 +123,20 @@ private: }; -ESPsTab::ESPsTab(Ui::ModInfoDialog* ui) - : ui(ui) +ESPsTab::ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui) + : m_parent(parent), ui(ui) { QObject::connect( - ui->activateESP1, &QToolButton::clicked, [&]{ onActivate(); }); + ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); QObject::connect( - ui->deactivateESP1, &QToolButton::clicked, [&]{ onDeactivate(); }); + ui->deactivateESP, &QToolButton::clicked, [&]{ onDeactivate(); }); } void ESPsTab::clear() { - ui->inactiveESPList1->clear(); - ui->activeESPList1->clear(); + ui->inactiveESPList->clear(); + ui->activeESPList->clear(); } bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -152,9 +152,9 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) auto* item = new ESPItem({rootPath, relativePath}); if (item->esp().isActive()) { - ui->activeESPList1->addItem(item); + ui->activeESPList->addItem(item); } else { - ui->inactiveESPList1->addItem(item); + ui->inactiveESPList->addItem(item); } return true; @@ -188,7 +188,7 @@ void ESPsTab::onActivate() bool okClicked = false; newName = QInputDialog::getText( - ui->tabESPs, + m_parent, QObject::tr("File Exists"), QObject::tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, file.fileName(), &okClicked); @@ -203,8 +203,8 @@ void ESPsTab::onActivate() } if (esp.activate(newName)) { - ui->inactiveESPList1->takeItem(ui->inactiveESPList1->row(item)); - ui->activeESPList1->addItem(item); + ui->inactiveESPList->takeItem(ui->inactiveESPList->row(item)); + ui->activeESPList->addItem(item); item->setESP(esp); } else { reportError(QObject::tr("Failed to move file")); @@ -246,8 +246,8 @@ void ESPsTab::onDeactivate() } if (esp.deactivate(newName)) { - ui->activeESPList1->takeItem(ui->activeESPList1->row(item)); - ui->inactiveESPList1->addItem(item); + ui->activeESPList->takeItem(ui->activeESPList->row(item)); + ui->inactiveESPList->addItem(item); item->setESP(esp); } else { reportError(QObject::tr("Failed to move file")); @@ -256,7 +256,7 @@ void ESPsTab::onDeactivate() ESPItem* ESPsTab::selectedInactive() { - auto* item = ui->inactiveESPList1->currentItem(); + auto* item = ui->inactiveESPList->currentItem(); if (!item) { return nullptr; } @@ -272,7 +272,7 @@ ESPItem* ESPsTab::selectedInactive() ESPItem* ESPsTab::selectedActive() { - auto* item = ui->activeESPList1->currentItem(); + auto* item = ui->activeESPList->currentItem(); if (!item) { return nullptr; } diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index a46677cb..8da70377 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -7,13 +7,16 @@ class ESPItem; class ESPsTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ESPsTab(Ui::ModInfoDialog* ui); + ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; private: + QWidget* m_parent; Ui::ModInfoDialog* ui; void onActivate(); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1659bed3..752c8d0c 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -80,7 +80,7 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) } -ImagesTab::ImagesTab(Ui::ModInfoDialog* ui) +ImagesTab::ImagesTab(QWidget*, Ui::ModInfoDialog* ui) : ui(ui), m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 73fb5936..a40904f7 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -31,8 +31,10 @@ private: class ImagesTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ImagesTab(Ui::ModInfoDialog* ui); + ImagesTab(QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index cf800aa6..0dd6f06e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -21,8 +21,9 @@ private: }; -GenericFilesTab::GenericFilesTab(QListWidget* list, QSplitter* sp, TextEditor* e) - : m_list(list), m_editor(e) +GenericFilesTab::GenericFilesTab( + QWidget* parent, QListWidget* list, QSplitter* sp, TextEditor* e) + : m_parent(parent), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -48,7 +49,7 @@ bool GenericFilesTab::canClose() } const int res = QMessageBox::question( - m_list, + m_parent, QObject::tr("Save changes?"), QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); @@ -108,8 +109,9 @@ void GenericFilesTab::select(FileListItem* item) } -TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) - : GenericFilesTab(ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) +TextFilesTab::TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + parent, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -128,8 +130,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab(Ui::ModInfoDialog* ui) - : GenericFilesTab(ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) +IniFilesTab::IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + parent, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 8725ea09..d30e20d7 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -10,17 +10,22 @@ class TextEditor; class GenericFilesTab : public ModInfoDialogTab { -public: - GenericFilesTab(QListWidget* list, QSplitter* splitter, TextEditor* editor); + Q_OBJECT; +public: void clear() override; bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; protected: + QWidget* m_parent; QListWidget* m_list; TextEditor* m_editor; + GenericFilesTab( + QWidget* parent, + QListWidget* list, QSplitter* splitter, TextEditor* editor); + virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; private: @@ -32,7 +37,7 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab(Ui::ModInfoDialog* ui); + TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -42,7 +47,7 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab(Ui::ModInfoDialog* ui); + IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- cgit v1.3.1 From 90280399aaa1abb571c32d4ff063b4f8d5efc39b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:32:37 -0400 Subject: renamed widgets to their proper name --- src/modinfodialog.ui | 50 +++++++++---------- src/modinfodialogconflicts.cpp | 108 ++++++++++++++++++++--------------------- 2 files changed, 79 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a26d15ab..c8ffd470 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -128,7 +128,7 @@ - + @@ -386,23 +386,23 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Conflicts - + 1 - + General - + 0 @@ -423,11 +423,11 @@ Most mods do not have optional esps, so chances are good you are looking at an e 0 - + - + - + 0 @@ -444,7 +444,7 @@ text-align: left; - + QFrame::Sunken @@ -461,7 +461,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -502,11 +502,11 @@ text-align: left; - + - + - + 0 @@ -523,7 +523,7 @@ text-align: left; - + QFrame::Sunken @@ -537,7 +537,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -578,11 +578,11 @@ text-align: left; - + - + - + 0 @@ -599,7 +599,7 @@ text-align: left; - + QFrame::Sunken @@ -613,7 +613,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -696,7 +696,7 @@ text-align: left; 0 - + Qt::CustomContextMenu @@ -751,7 +751,7 @@ text-align: left; 0 - + Whether files that have no conflicts should be visible in the list @@ -764,7 +764,7 @@ text-align: left; - + Shows all mods overwriting or being overwritten by this mod @@ -777,7 +777,7 @@ text-align: left; - + Shows only the nearest conflicting mods, in order of priority @@ -802,7 +802,7 @@ text-align: left; 0 - + Filter diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 22306d0c..0107b8a1 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -158,7 +158,7 @@ bool ConflictsTab::feedFile(const QString&, const QString&) void ConflictsTab::saveState(Settings& s) { s.directInterface().setValue( - "mod_info_conflicts_tab", ui->tabConflictsTabs1->currentIndex()); + "mod_info_conflicts_tab", ui->tabConflictsTabs->currentIndex()); m_general.saveState(s); m_advanced.saveState(s); @@ -166,7 +166,7 @@ void ConflictsTab::saveState(Settings& s) void ConflictsTab::restoreState(const Settings& s) { - ui->tabConflictsTabs1->setCurrentIndex( + ui->tabConflictsTabs->setCurrentIndex( s.directInterface().value("mod_info_conflicts_tab", 0).toInt()); m_general.restoreState(s); @@ -475,40 +475,40 @@ GeneralConflictsTab::GeneralConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { - m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true); - m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true); - m_expanders.nonconflict.set(ui->noConflictExpander1, ui->noConflictTree1); + m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); + m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); + m_expanders.nonconflict.set(ui->noConflictExpander, ui->noConflictTree); QObject::connect( - ui->overwriteTree1, &QTreeWidget::itemDoubleClicked, + ui->overwriteTree, &QTreeWidget::itemDoubleClicked, [&](auto* item, int col){ onOverwriteActivated(item, col); }); QObject::connect( - ui->overwrittenTree1, &QTreeWidget::itemDoubleClicked, + ui->overwrittenTree, &QTreeWidget::itemDoubleClicked, [&](auto* item, int col){ onOverwrittenActivated(item, col); }); QObject::connect( - ui->overwriteTree1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree1); }); + ui->overwriteTree, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree); }); QObject::connect( - ui->overwrittenTree1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree1); }); + ui->overwrittenTree, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree); }); QObject::connect( - ui->noConflictTree1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree1); }); + ui->noConflictTree, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree); }); } void GeneralConflictsTab::clear() { - ui->overwriteTree1->clear(); - ui->overwrittenTree1->clear(); - ui->noConflictTree1->clear(); + ui->overwriteTree->clear(); + ui->overwrittenTree->clear(); + ui->noConflictTree->clear(); - ui->overwriteCount1->display(0); - ui->overwrittenCount1->display(0); - ui->noConflictCount1->display(0); + ui->overwriteCount->display(0); + ui->overwrittenCount->display(0); + ui->noConflictCount->display(0); } void GeneralConflictsTab::saveState(Settings& s) @@ -526,15 +526,15 @@ void GeneralConflictsTab::saveState(Settings& s) s.directInterface().setValue( "mod_info_conflicts_general_overwrite", - ui->overwriteTree1->header()->saveState()); + ui->overwriteTree->header()->saveState()); s.directInterface().setValue( "mod_info_conflicts_general_noconflict", - ui->noConflictTree1->header()->saveState()); + ui->noConflictTree->header()->saveState()); s.directInterface().setValue( "mod_info_conflicts_general_overwritten", - ui->overwrittenTree1->header()->saveState()); + ui->overwrittenTree->header()->saveState()); } void GeneralConflictsTab::restoreState(const Settings& s) @@ -554,13 +554,13 @@ void GeneralConflictsTab::restoreState(const Settings& s) m_expanders.nonconflict.toggle(noConflictExpanded); } - ui->overwriteTree1->header()->restoreState(s.directInterface() + ui->overwriteTree->header()->restoreState(s.directInterface() .value("mod_info_conflicts_general_overwrite").toByteArray()); - ui->noConflictTree1->header()->restoreState(s.directInterface() + ui->noConflictTree->header()->restoreState(s.directInterface() .value("mod_info_conflicts_general_noconflict").toByteArray()); - ui->overwrittenTree1->header()->restoreState(s.directInterface() + ui->overwrittenTree->header()->restoreState(s.directInterface() .value("mod_info_conflicts_general_overwritten").toByteArray()); } @@ -591,19 +591,19 @@ void GeneralConflictsTab::update() if (fileOrigin == m_origin->getID()) { if (!alternatives.empty()) { - ui->overwriteTree1->addTopLevelItem(createOverwriteItem( + ui->overwriteTree->addTopLevelItem(createOverwriteItem( file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree - ui->noConflictTree1->addTopLevelItem(createNoConflictItem( + ui->noConflictTree->addTopLevelItem(createNoConflictItem( file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { - ui->overwrittenTree1->addTopLevelItem(createOverwrittenItem( + ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; @@ -611,9 +611,9 @@ void GeneralConflictsTab::update() } } - ui->overwriteCount1->display(numOverwrite); - ui->overwrittenCount1->display(numOverwritten); - ui->noConflictCount1->display(numNonConflicting); + ui->overwriteCount->display(numOverwrite); + ui->overwrittenCount->display(numOverwritten); + ui->noConflictCount->display(numNonConflicting); } QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( @@ -691,53 +691,53 @@ AdvancedConflictsTab::AdvancedConflictsTab( : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { // left-elide the overwrites column so that the nearest are visible - ui->conflictsAdvancedList1->setItemDelegateForColumn( - 0, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + ui->conflictsAdvancedList->setItemDelegateForColumn( + 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); // left-elide the file column to see filenames - ui->conflictsAdvancedList1->setItemDelegateForColumn( - 1, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + ui->conflictsAdvancedList->setItemDelegateForColumn( + 1, new ElideLeftDelegate(ui->conflictsAdvancedList)); // don't elide the overwritten by column so that the nearest are visible QObject::connect( - ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, + ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&]{ update(); }); QObject::connect( - ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, + ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&]{ update(); }); QObject::connect( - ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, + ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); QObject::connect( - ui->conflictsAdvancedList1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList1); }); + ui->conflictsAdvancedList, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); - m_filter.set(ui->conflictsAdvancedFilter1); + m_filter.set(ui->conflictsAdvancedFilter); m_filter.changed = [&]{ update(); }; } void AdvancedConflictsTab::clear() { - ui->conflictsAdvancedList1->clear(); + ui->conflictsAdvancedList->clear(); } void AdvancedConflictsTab::saveState(Settings& s) { s.directInterface().setValue( "mod_info_conflicts_advanced_list", - ui->conflictsAdvancedList1->header()->saveState()); + ui->conflictsAdvancedList->header()->saveState()); QByteArray result; QDataStream stream(&result, QIODevice::WriteOnly); stream - << ui->conflictsAdvancedShowNoConflict1->isChecked() - << ui->conflictsAdvancedShowAll1->isChecked() - << ui->conflictsAdvancedShowNearest1->isChecked(); + << ui->conflictsAdvancedShowNoConflict->isChecked() + << ui->conflictsAdvancedShowAll->isChecked() + << ui->conflictsAdvancedShowNearest->isChecked(); s.directInterface().setValue( "mod_info_conflicts_advanced_options", result); @@ -745,7 +745,7 @@ void AdvancedConflictsTab::saveState(Settings& s) void AdvancedConflictsTab::restoreState(const Settings& s) { - ui->conflictsAdvancedList1->header()->restoreState( + ui->conflictsAdvancedList->header()->restoreState( s.directInterface().value("mod_info_conflicts_advanced_list").toByteArray()); QDataStream stream(s.directInterface() @@ -758,9 +758,9 @@ void AdvancedConflictsTab::restoreState(const Settings& s) stream >> noConflictChecked >> showAllChecked >> showNearestChecked; if (stream.status() == QDataStream::Ok) { - ui->conflictsAdvancedShowNoConflict1->setChecked(noConflictChecked); - ui->conflictsAdvancedShowAll1->setChecked(showAllChecked); - ui->conflictsAdvancedShowNearest1->setChecked(showNearestChecked); + ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked); + ui->conflictsAdvancedShowAll->setChecked(showAllChecked); + ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked); } } @@ -790,7 +790,7 @@ void AdvancedConflictsTab::update() fileName, relativeName, alternatives); if (advancedItem) { - ui->conflictsAdvancedList1->addTopLevelItem(advancedItem); + ui->conflictsAdvancedList->addTopLevelItem(advancedItem); } } } @@ -813,7 +813,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( { const auto altOrigin = ds.getOriginByID(alt.first); - if (ui->conflictsAdvancedShowAll1->isChecked()) { + if (ui->conflictsAdvancedShowAll->isChecked()) { // fills 'before' and 'after' with all the alternatives that come // before and after this mod in terms of priority @@ -868,7 +868,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // nearest mods, it's not worth checking for the primary origin because it // will always have a higher priority than the alternatives (or it wouldn't // be the primary) - if (after.isEmpty() || ui->conflictsAdvancedShowAll1->isChecked()) { + if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); // if no mods overwrite this file, the primary origin is the same as this @@ -885,7 +885,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( bool hasAlts = !before.isEmpty() || !after.isEmpty(); - if (!ui->conflictsAdvancedShowNoConflict1->isChecked()) { + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it if (!hasAlts) { -- cgit v1.3.1 From a2e5f7d38214c9872a5c3a360065b5790bb29e2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:43:16 -0400 Subject: split ModInfoDialogTab --- src/CMakeLists.txt | 3 +++ src/modinfodialog.cpp | 36 ------------------------------------ src/modinfodialog.h | 33 +-------------------------------- src/modinfodialogconflicts.cpp | 1 + src/modinfodialogconflicts.h | 5 ++++- src/modinfodialogesps.h | 2 +- src/modinfodialogimages.h | 2 +- src/modinfodialogtab.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/modinfodialogtab.h | 42 ++++++++++++++++++++++++++++++++++++++++++ src/modinfodialogtextfiles.h | 2 +- 10 files changed, 90 insertions(+), 72 deletions(-) create mode 100644 src/modinfodialogtab.cpp create mode 100644 src/modinfodialogtab.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eead6541..adaddb77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ SET(organizer_SRCS modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp + modinfodialogtab.cpp modinfodialogtextfiles.cpp modinfo.cpp modinfobackup.cpp @@ -161,6 +162,7 @@ SET(organizer_HDRS modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h + modinfodialogtab.h modinfodialogtextfiles.h modinfo.h modinfobackup.h @@ -363,6 +365,7 @@ set(modinfo modinfodialogconflicts modinfodialogesps modinfodialogimages + modinfodialogtab modinfodialogtextfiles modinfoforeign modinfooverwrite diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d52103b9..8231d366 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -66,42 +66,6 @@ using namespace MOShared; const int max_scan_for_context_menu = 50; -bool ModInfoDialogTab::canClose() -{ - return true; -} - -void ModInfoDialogTab::saveState(Settings&) -{ - // no-op -} - -void ModInfoDialogTab::restoreState(const Settings& s) -{ - // no-op -} - -void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) -{ - // no-op -} - -void ModInfoDialogTab::update() -{ - // no-op -} - -void ModInfoDialogTab::emitOriginModified(int originID) -{ - emit originModified(originID); -} - -void ModInfoDialogTab::emitModOpen(QString name) -{ - emit modOpen(name); -} - - class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); public: diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 2a3ee2d8..8e7fed17 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -43,7 +43,6 @@ along with Mod Organizer. If not, see . #include #include - namespace Ui { class ModInfoDialog; } @@ -52,37 +51,7 @@ class QFileSystemModel; class QTreeView; class CategoryFactory; class TextEditor; - - -class ModInfoDialogTab : public QObject -{ - Q_OBJECT; - -public: - ModInfoDialogTab() = default; - ModInfoDialogTab(const ModInfoDialogTab&) = delete; - ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; - ModInfoDialogTab(ModInfoDialogTab&&) = default; - ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; - virtual ~ModInfoDialogTab() = default; - - virtual void clear() = 0; - virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; - virtual bool canClose(); - virtual void saveState(Settings& s); - virtual void restoreState(const Settings& s); - - virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); - virtual void update(); - -signals: - void originModified(int originID); - void modOpen(QString name); - -protected: - void emitOriginModified(int originID); - void emitModOpen(QString name); -}; +class ModInfoDialogTab; class ElideLeftDelegate : public QStyledItemDelegate diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 0107b8a1..1734f4f9 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -1,5 +1,6 @@ #include "modinfodialogconflicts.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include "utility.h" using namespace MOShared; diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 5843884a..4d4f279c 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -1,10 +1,13 @@ #ifndef MODINFODIALOGCONFLICTS_H #define MODINFODIALOGCONFLICTS_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" #include "expanderwidget.h" +#include "filterwidget.h" +#include "directoryentry.h" class ConflictsTab; +class OrganizerCore; class GeneralConflictsTab : public QObject { diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index 8da70377..d71ad3ff 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGESPS_H #define MODINFODIALOGESPS_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" class ESPItem; diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index a40904f7..708045c8 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGIMAGES_H #define MODINFODIALOGIMAGES_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" class ScalableImage : public QWidget { diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp new file mode 100644 index 00000000..b4734526 --- /dev/null +++ b/src/modinfodialogtab.cpp @@ -0,0 +1,36 @@ +#include "modinfodialogtab.h" + +bool ModInfoDialogTab::canClose() +{ + return true; +} + +void ModInfoDialogTab::saveState(Settings&) +{ + // no-op +} + +void ModInfoDialogTab::restoreState(const Settings& s) +{ + // no-op +} + +void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) +{ + // no-op +} + +void ModInfoDialogTab::update() +{ + // no-op +} + +void ModInfoDialogTab::emitOriginModified(int originID) +{ + emit originModified(originID); +} + +void ModInfoDialogTab::emitModOpen(QString name) +{ + emit modOpen(name); +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h new file mode 100644 index 00000000..93a495f4 --- /dev/null +++ b/src/modinfodialogtab.h @@ -0,0 +1,42 @@ +#ifndef MODINFODIALOGTAB_H +#define MODINFODIALOGTAB_H + +#include "modinfo.h" +#include + +namespace MOShared { class FilesOrigin; } +namespace Ui { class ModInfoDialog; } + +class Settings; + +class ModInfoDialogTab : public QObject +{ + Q_OBJECT; + +public: + ModInfoDialogTab() = default; + ModInfoDialogTab(const ModInfoDialogTab&) = delete; + ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; + ModInfoDialogTab(ModInfoDialogTab&&) = default; + ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; + virtual ~ModInfoDialogTab() = default; + + virtual void clear() = 0; + virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; + virtual bool canClose(); + virtual void saveState(Settings& s); + virtual void restoreState(const Settings& s); + + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + virtual void update(); + +signals: + void originModified(int originID); + void modOpen(QString name); + +protected: + void emitOriginModified(int originID); + void emitModOpen(QString name); +}; + +#endif // MODINFODIALOGTAB_H diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index d30e20d7..11691d64 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGTEXTFILES_H #define MODINFODIALOGTEXTFILES_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" #include #include -- cgit v1.3.1 From a72573b385a941adf7d662b8df5c8e35309fdd45 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 22:42:14 -0400 Subject: split categories tab --- src/CMakeLists.txt | 3 + src/modinfodialog.cpp | 88 ++------------------------- src/modinfodialog.h | 7 --- src/modinfodialogcategories.cpp | 129 ++++++++++++++++++++++++++++++++++++++++ src/modinfodialogcategories.h | 29 +++++++++ src/modinfodialogconflicts.cpp | 5 -- src/modinfodialogconflicts.h | 1 - src/modinfodialogesps.cpp | 1 - src/modinfodialogtab.cpp | 6 ++ src/modinfodialogtab.h | 2 +- 10 files changed, 173 insertions(+), 98 deletions(-) create mode 100644 src/modinfodialogcategories.cpp create mode 100644 src/modinfodialogcategories.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index adaddb77..6ebfaddd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogcategories.cpp modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp @@ -159,6 +160,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogcategories.h modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h @@ -362,6 +364,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogcategories modinfodialogconflicts modinfodialogesps modinfodialogimages diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 8231d366..3263a511 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -42,6 +42,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogimages.h" #include "modinfodialogesps.h" #include "modinfodialogconflicts.h" +#include "modinfodialogcategories.h" #include #include @@ -223,9 +224,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_IMAGES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, false); - //ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); - addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); - refreshPrimaryCategoriesBox(); + ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); //ui->tabWidget->setTabEnabled(TAB_NOTES, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); @@ -249,12 +248,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); initFiletree(modInfo); - addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); - refreshPrimaryCategoriesBox(); } - - ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration()); ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); @@ -279,12 +274,13 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ModInfoDialog::~ModInfoDialog() { m_ModInfo->setComments(ui->commentsEdit->text()); + //Avoid saving html stump if notes field is empty. if (ui->notesEdit->toPlainText().isEmpty()) m_ModInfo->setNotes(ui->notesEdit->toPlainText()); else m_ModInfo->setNotes(ui->notesEdit->toHtml()); - saveCategories(ui->categoriesTree->invisibleRootItem()); + delete ui->descriptionView->page(); delete ui->descriptionView; delete ui; @@ -301,6 +297,7 @@ std::vector> ModInfoDialog::createTabs() v.push_back(std::make_unique(this, ui)); v.push_back(std::make_unique( this, ui, *m_OrganizerCore, *m_PluginContainer)); + v.push_back(std::make_unique(this, ui)); return v; } @@ -437,38 +434,6 @@ void ModInfoDialog::refreshFiles() } } -void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) -{ - for (int i = 0; i < static_cast(factory.numCategories()); ++i) { - if (factory.getParentID(i) != rootLevel) { - continue; - } - int categoryID = factory.getCategoryID(i); - QTreeWidgetItem *newItem - = new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, enabledCategories.find(categoryID) - != enabledCategories.end() - ? Qt::Checked - : Qt::Unchecked); - newItem->setData(0, Qt::UserRole, categoryID); - if (factory.hasChildren(i)) { - addCategories(factory, enabledCategories, newItem, categoryID); - } - root->addChild(newItem); - } -} - - -void ModInfoDialog::saveCategories(QTreeWidgetItem *currentNode) -{ - for (int i = 0; i < currentNode->childCount(); ++i) { - QTreeWidgetItem *childNode = currentNode->child(i); - m_ModInfo->setCategory(childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); - saveCategories(childNode); - } -} - void ModInfoDialog::on_closeButton_clicked() { @@ -1011,49 +976,6 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) } -void ModInfoDialog::on_categoriesTree_itemChanged(QTreeWidgetItem *item, int) -{ - QTreeWidgetItem *parent = item->parent(); - while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { - parent->setCheckState(0, Qt::Checked); - parent = parent->parent(); - } - refreshPrimaryCategoriesBox(); -} - - -void ModInfoDialog::addCheckedCategories(QTreeWidgetItem *tree) -{ - for (int i = 0; i < tree->childCount(); ++i) { - QTreeWidgetItem *child = tree->child(i); - if (child->checkState(0) == Qt::Checked) { - ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); - addCheckedCategories(child); - } - } -} - - -void ModInfoDialog::refreshPrimaryCategoriesBox() -{ - ui->primaryCategoryBox->clear(); - int primaryCategory = m_ModInfo->getPrimaryCategory(); - addCheckedCategories(ui->categoriesTree->invisibleRootItem()); - for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { - if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { - ui->primaryCategoryBox->setCurrentIndex(i); - break; - } - } -} - -void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) -{ - if (index != -1) { - m_ModInfo->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); - } -} - void ModInfoDialog::on_refreshButton_clicked() { if (m_ModInfo->getNexusID() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 8e7fed17..384aafce 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -157,8 +157,6 @@ private: void refreshLists(); - void addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel); - void updateVersionColor(); void refreshNexusData(int modID); @@ -166,9 +164,6 @@ private: QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); - void saveCategories(QTreeWidgetItem *currentNode); - void addCheckedCategories(QTreeWidgetItem *tree); - void refreshPrimaryCategoriesBox(); int tabIndex(const QString &tabId); @@ -194,8 +189,6 @@ private slots: void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); void on_tabWidget_currentChanged(int index); - void on_primaryCategoryBox_currentIndexChanged(int index); - void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp new file mode 100644 index 00000000..7df13aea --- /dev/null +++ b/src/modinfodialogcategories.cpp @@ -0,0 +1,129 @@ +#include "modinfodialogcategories.h" +#include "ui_modinfodialog.h" +#include "categories.h" +#include "modinfo.h" + +CategoriesTab::CategoriesTab(QWidget*, Ui::ModInfoDialog* ui) + : ui(ui) +{ + connect( + ui->categoriesTree, &QTreeWidget::itemChanged, + [&](auto* item, int col){ onCategoryChanged(item, col); }); + + connect( + ui->primaryCategoryBox, + static_cast(&QComboBox::currentIndexChanged), + [&](int index){ onPrimaryChanged(index); }); +} + +void CategoriesTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + m_mod = mod; +} + +void CategoriesTab::clear() +{ + ui->categoriesTree->clear(); + ui->primaryCategoryBox->clear(); +} + +void CategoriesTab::update() +{ + clear(); + + add( + CategoryFactory::instance(), m_mod->getCategories(), + ui->categoriesTree->invisibleRootItem(), 0); + + updatePrimary(); +} + +void CategoriesTab::add( + const CategoryFactory &factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel) +{ + for (int i=0; i(factory.numCategories()); ++i) { + if (factory.getParentID(i) != rootLevel) { + continue; + } + + int categoryID = factory.getCategoryID(i); + + QTreeWidgetItem *newItem + = new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); + + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); + + newItem->setCheckState(0, enabledCategories.find(categoryID) + != enabledCategories.end() + ? Qt::Checked + : Qt::Unchecked); + + newItem->setData(0, Qt::UserRole, categoryID); + + if (factory.hasChildren(i)) { + add(factory, enabledCategories, newItem, categoryID); + } + + root->addChild(newItem); + } +} + +void CategoriesTab::updatePrimary() +{ + ui->primaryCategoryBox->clear(); + + int primaryCategory = m_mod->getPrimaryCategory(); + + addChecked(ui->categoriesTree->invisibleRootItem()); + + for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { + if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { + ui->primaryCategoryBox->setCurrentIndex(i); + break; + } + } +} + +void CategoriesTab::addChecked(QTreeWidgetItem* tree) +{ + for (int i = 0; i < tree->childCount(); ++i) { + QTreeWidgetItem *child = tree->child(i); + if (child->checkState(0) == Qt::Checked) { + ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); + addChecked(child); + } + } +} + +void CategoriesTab::save(QTreeWidgetItem* currentNode) +{ + for (int i = 0; i < currentNode->childCount(); ++i) { + QTreeWidgetItem *childNode = currentNode->child(i); + + m_mod->setCategory( + childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); + + save(childNode); + } +} + +void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) +{ + QTreeWidgetItem *parent = item->parent(); + + while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { + parent->setCheckState(0, Qt::Checked); + parent = parent->parent(); + } + + updatePrimary(); + save(ui->categoriesTree->invisibleRootItem()); +} + +void CategoriesTab::onPrimaryChanged(int index) +{ + if (index != -1) { + m_mod->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); + } +} diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h new file mode 100644 index 00000000..c0644c7f --- /dev/null +++ b/src/modinfodialogcategories.h @@ -0,0 +1,29 @@ +#include "modinfodialogtab.h" + +class CategoryFactory; + +class CategoriesTab : public ModInfoDialogTab +{ +public: + CategoriesTab(QWidget* parent, Ui::ModInfoDialog* ui); + + void clear() override; + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + void update() override; + +private: + Ui::ModInfoDialog* ui; + ModInfo::Ptr m_mod; + + void add( + const CategoryFactory& factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel); + + void updatePrimary(); + void addChecked(QTreeWidgetItem* tree); + + void save(QTreeWidgetItem* currentNode); + + void onCategoryChanged(QTreeWidgetItem* item, int col); + void onPrimaryChanged(int index); +}; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 1734f4f9..d2dca492 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -151,11 +151,6 @@ void ConflictsTab::clear() m_advanced.clear(); } -bool ConflictsTab::feedFile(const QString&, const QString&) -{ - return false; -} - void ConflictsTab::saveState(Settings& s) { s.directInterface().setValue( diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 4d4f279c..212a196d 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -108,7 +108,6 @@ public: void update() override; void clear() override; - bool feedFile(const QString& rootPath, const QString& filename) override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 6c87b10d..cb3dcc84 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -4,7 +4,6 @@ using MOBase::reportError; - class ESP { public: diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index b4734526..6dcff0ac 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,5 +1,11 @@ #include "modinfodialogtab.h" +bool ModInfoDialogTab::feedFile(const QString&, const QString&) +{ + // no-op + return false; +} + bool ModInfoDialogTab::canClose() { return true; diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 93a495f4..ead29d10 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -22,7 +22,7 @@ public: virtual ~ModInfoDialogTab() = default; virtual void clear() = 0; - virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; + virtual bool feedFile(const QString& rootPath, const QString& filename); virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); -- cgit v1.3.1 From 8d1c121f648f2f6a8e0a5e2ad76cd245e318290d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 02:24:34 -0400 Subject: split nexus tab added OrganizerCore::loggedInAction() to execute a function only when logged in, replaces a bunch of copy/pasted stuff in mainwindow moved common variables in ModInfoDialogTab moved DescriptionPage to modinfodialognexus.h, renamed to NexusTabWebpage, deleted now empty descriptionpage.h --- src/CMakeLists.txt | 5 +- src/descriptionpage.h | 28 ----- src/mainwindow.cpp | 167 ++++++------------------ src/modinfodialog.cpp | 233 +++------------------------------- src/modinfodialog.h | 29 ----- src/modinfodialog.ui | 222 +++++++++++++++----------------- src/modinfodialogcategories.cpp | 43 +++---- src/modinfodialogcategories.h | 8 +- src/modinfodialogconflicts.cpp | 91 ++++++-------- src/modinfodialogconflicts.h | 17 +-- src/modinfodialogesps.cpp | 8 +- src/modinfodialogesps.h | 7 +- src/modinfodialogimages.cpp | 6 +- src/modinfodialogimages.h | 5 +- src/modinfodialognexus.cpp | 273 ++++++++++++++++++++++++++++++++++++++++ src/modinfodialognexus.h | 67 ++++++++++ src/modinfodialogtab.cpp | 39 +++++- src/modinfodialogtab.h | 25 +++- src/modinfodialogtextfiles.cpp | 26 ++-- src/modinfodialogtextfiles.h | 12 +- src/modinforegular.cpp | 20 ++- src/modinforegular.h | 1 + src/organizercore.cpp | 15 +++ src/organizercore.h | 1 + 24 files changed, 690 insertions(+), 658 deletions(-) delete mode 100644 src/descriptionpage.h create mode 100644 src/modinfodialognexus.cpp create mode 100644 src/modinfodialognexus.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ebfaddd..7dc39fd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ SET(organizer_SRCS modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp + modinfodialognexus.cpp modinfodialogtab.cpp modinfodialogtextfiles.cpp modinfo.cpp @@ -164,6 +165,7 @@ SET(organizer_HDRS modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h + modinfodialognexus.h modinfodialogtab.h modinfodialogtextfiles.h modinfo.h @@ -223,7 +225,6 @@ SET(organizer_HDRS instancemanager.h usvfsconnector.h eventfilter.h - descriptionpage.h moshortcut.h listdialog.h lcdnumber.h @@ -368,6 +369,7 @@ set(modinfo modinfodialogconflicts modinfodialogesps modinfodialogimages + modinfodialognexus modinfodialogtab modinfodialogtextfiles modinfoforeign @@ -424,7 +426,6 @@ set(utilities ) set(widgets - descriptionpage expanderwidget genericicondelegate filerenamer diff --git a/src/descriptionpage.h b/src/descriptionpage.h deleted file mode 100644 index f6158ee0..00000000 --- a/src/descriptionpage.h +++ /dev/null @@ -1,28 +0,0 @@ -#include - -#ifndef DESCRIPTIONPAGE_H -#define DESCRIPTIONPAGE_H - -class DescriptionPage : public QWebEnginePage -{ - Q_OBJECT - -public: - DescriptionPage(QObject* parent = 0) : QWebEnginePage(parent){} - - bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame) - { - if (type == QWebEnginePage::NavigationTypeLinkClicked) - { - emit linkClicked(url); - return false; - } - return true; - } - -signals: - void linkClicked(const QUrl&); - -}; - -#endif //DESCRIPTIONPAGE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index da7c721d..8eb0838e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3073,66 +3073,34 @@ void MainWindow::backupMod_clicked() void MainWindow::resumeDownload(int downloadIndex) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + m_OrganizerCore.loggedInAction(this, [this, downloadIndex] { m_OrganizerCore.downloadManager()->resumeDownload(downloadIndex); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([this, downloadIndex] () { - this->resumeDownload(downloadIndex); - }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); - } - } + }); } void MainWindow::endorseMod(ModInfo::Ptr mod) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + m_OrganizerCore.loggedInAction(this, [this, mod] { mod->endorse(true); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([&]() { this->endorseMod(mod); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } - } + }); } void MainWindow::endorse_clicked() { QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true); - } } - else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->endorseMod(modInfo); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - return; - } + + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true); } - } - else { - endorseMod(ModInfo::getByIndex(m_ContextRow)); - } + }); } void MainWindow::dontendorse_clicked() @@ -3151,114 +3119,53 @@ void MainWindow::dontendorse_clicked() void MainWindow::unendorseMod(ModInfo::Ptr mod) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - ModInfo::getByIndex(m_ContextRow)->endorse(false); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([&]() { this->unendorseMod(mod); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } - } + m_OrganizerCore.loggedInAction(this, [mod] { + mod->endorse(false); + }); } void MainWindow::unendorse_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false); - } - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->unendorseMod(modInfo); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - return; - } } - } - else { - unendorseMod(ModInfo::getByIndex(m_ContextRow)); - } + + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false); + } + }); } void MainWindow::trackMod(ModInfo::Ptr mod, bool doTrack) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - ModInfo::getByIndex(m_ContextRow)->track(doTrack); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([&]() { this->trackMod(mod, doTrack); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); - } - } + m_OrganizerCore.loggedInAction(this, [mod, doTrack] { + mod->track(doTrack); + }); } void MainWindow::track_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true); - } - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - for (auto idx : selection->selectedRows()) { - auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, true); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); - } + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true); } - } else { - trackMod(ModInfo::getByIndex(m_ContextRow), true); - } + }); } void MainWindow::untrack_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false); - } - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - for (auto idx : selection->selectedRows()) { - auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, false); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); - } + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false); } - } else { - trackMod(ModInfo::getByIndex(m_ContextRow), false); - } + }); } void MainWindow::validationFailed(const QString &error) @@ -3316,13 +3223,11 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), &m_OrganizerCore, &m_PluginContainer, this); - connect(&dialog, SIGNAL(linkActivated(QString)), this, SLOT(linkClicked(QString))); connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); 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); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 3263a511..30110d14 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -43,6 +43,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogesps.h" #include "modinfodialogconflicts.h" #include "modinfodialogcategories.h" +#include "modinfodialognexus.h" #include #include @@ -145,7 +146,6 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_RequestStarted(false), m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_PreviewAction(nullptr), m_RenameAction(nullptr), m_DeleteAction(nullptr), m_HideAction(nullptr), m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), @@ -173,38 +173,9 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_RootPath = modInfo->absolutePath(); - QString metaFileName = m_RootPath.mid(0).append("/meta.ini"); - m_Settings = new QSettings(metaFileName, QSettings::IniFormat); - - QLineEdit *modIDEdit = findChild("modIDEdit"); - ui->modIDEdit->setValidator(new QIntValidator(modIDEdit)); - ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID())); - - connect(ui->modIDEdit, SIGNAL(linkClicked(QString)), this, SLOT(linkClicked(QString))); - - QString gameName = modInfo->getGameName(); - ui->sourceGameEdit->addItem(organizerCore->managedGame()->gameName(), organizerCore->managedGame()->gameShortName()); - if (organizerCore->managedGame()->validShortNames().size() == 0) { - ui->sourceGameEdit->setDisabled(true); - } else { - for (auto game : pluginContainer->plugins()) { - for (QString gameName : organizerCore->managedGame()->validShortNames()) { - if (game->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { - ui->sourceGameEdit->addItem(game->gameName(), game->gameShortName()); - break; - } - } - } - } - ui->sourceGameEdit->setCurrentIndex(ui->sourceGameEdit->findData(gameName)); - ui->commentsEdit->setText(modInfo->comments()); ui->notesEdit->setText(modInfo->notes()); - ui->descriptionView->setPage(new DescriptionPage()); - - connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); - connect(ui->descriptionView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); //TODO: No easy way to delegate links //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); @@ -246,14 +217,12 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_IMAGES, true); ui->tabWidget->setTabEnabled(TAB_ESPS, true); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); + ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); + ui->tabWidget->setTabEnabled(TAB_NEXUS, true); initFiletree(modInfo); } - ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration()); - ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || - (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); - // activate first enabled tab for (int i = 0; i < ui->tabWidget->count(); ++i) { if (ui->tabWidget->isTabEnabled(i)) { @@ -262,10 +231,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - if (ui->tabWidget->currentIndex() == TAB_NEXUS) { - activateNexusTab(); - } - for (auto& tab : m_tabs) { tab->setMod(m_ModInfo, m_Origin); } @@ -281,27 +246,28 @@ ModInfoDialog::~ModInfoDialog() else m_ModInfo->setNotes(ui->notesEdit->toHtml()); - delete ui->descriptionView->page(); - delete ui->descriptionView; delete ui; - delete m_Settings; } -std::vector> ModInfoDialog::createTabs() +template +std::vector> createTabsImpl( + OrganizerCore& oc, PluginContainer& plugin, + ModInfoDialog* self, Ui::ModInfoDialog* ui) { std::vector> v; - - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique( - this, ui, *m_OrganizerCore, *m_PluginContainer)); - v.push_back(std::make_unique(this, ui)); + (v.push_back(std::make_unique(oc, plugin, self, ui)), ...); return v; } +std::vector> ModInfoDialog::createTabs() +{ + return createTabsImpl< + TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, + ConflictsTab, CategoriesTab, NexusTab>( + *m_OrganizerCore, *m_PluginContainer, this, ui); +} + int ModInfoDialog::exec() { refreshLists(); @@ -434,7 +400,6 @@ void ModInfoDialog::refreshFiles() } } - void ModInfoDialog::on_closeButton_clicked() { for (auto& tab : m_tabs) { @@ -446,19 +411,6 @@ void ModInfoDialog::on_closeButton_clicked() close(); } - - -QString ModInfoDialog::getModVersion() const -{ - return m_Settings->value("version", "").toString(); -} - - -const int ModInfoDialog::getModID() const -{ - return m_Settings->value("modid", 0).toInt(); -} - void ModInfoDialog::openTab(int tab) { QTabWidget *tabWidget = findChild("tabWidget"); @@ -467,40 +419,6 @@ void ModInfoDialog::openTab(int tab) } } -void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString &link) -{ - emit linkActivated(link); -} - -void ModInfoDialog::linkClicked(const QUrl &url) -{ - //Ideally we'd ask the mod for the game and the web service then pass the game - //and URL to the web service - if (NexusInterface::instance(m_PluginContainer)->isURLGameRelated(url)) { - emit linkActivated(url.toString()); - } else { - shell::OpenLink(url); - } -} - -void ModInfoDialog::linkClicked(QString url) -{ - emit linkActivated(url); -} - - -void ModInfoDialog::refreshNexusData(int modID) -{ - if ((!m_RequestStarted) && (modID > 0)) { - m_RequestStarted = true; - - m_ModInfo->updateNXMInfo(); - - MessageDialog::showMessage(tr("Info requested, please wait"), this); - } -} - - QString ModInfoDialog::getFileCategory(int categoryID) { switch (categoryID) { @@ -514,111 +432,8 @@ QString ModInfoDialog::getFileCategory(int categoryID) } } - -void ModInfoDialog::updateVersionColor() -{ -// QPalette versionColor; - if (m_ModInfo->getVersion() != m_ModInfo->getNewestVersion()) { - ui->versionEdit->setStyleSheet("color: red"); -// versionColor.setColor(QPalette::Text, Qt::red); - ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString())); - } else { - ui->versionEdit->setStyleSheet("color: green"); -// versionColor.setColor(QPalette::Text, Qt::green); - ui->versionEdit->setToolTip(tr("No update available")); - } -// ui->versionEdit->setPalette(versionColor); -} - - -void ModInfoDialog::modDetailsUpdated(bool success) -{ - QString nexusDescription = m_ModInfo->getNexusDescription(); - QString descriptionAsHTML = "" - "" - "%1" - ""; - - if (!nexusDescription.isEmpty()) { - descriptionAsHTML = descriptionAsHTML.arg(BBCode::convertToHTML(nexusDescription)); - } else { - descriptionAsHTML = descriptionAsHTML.arg(tr("

    Uh oh!

    Sorry, there is no description available for this mod. :(

    ")); - } - - ui->descriptionView->page()->setHtml(descriptionAsHTML); - - updateVersionColor(); -} - - -void ModInfoDialog::activateNexusTab() -{ - QLineEdit *modIDEdit = findChild("modIDEdit"); - int modID = modIDEdit->text().toInt(); - if (modID > 0) { - QString nexusLink = NexusInterface::instance(m_PluginContainer)->getModURL(modID, m_ModInfo->getGameName()); - QLabel *visitNexusLabel = findChild("visitNexusLabel"); - visitNexusLabel->setText(tr("Visit on Nexus").arg(nexusLink)); - visitNexusLabel->setToolTip(nexusLink); - m_ModInfo->setURL(nexusLink); - - if (m_ModInfo->getNexusDescription().isEmpty() || QDateTime::currentDateTimeUtc() >= m_ModInfo->getLastNexusQuery().addDays(1)) { - refreshNexusData(modID); - } else { - modDetailsUpdated(true); - } - } else - modDetailsUpdated(true); - QLineEdit *versionEdit = findChild("versionEdit"); - QString currentVersion = m_Settings->value("version", "???").toString(); - versionEdit->setText(currentVersion); - ui->customUrlLineEdit->setText(m_ModInfo->getURL()); -} - - void ModInfoDialog::on_tabWidget_currentChanged(int index) { - if (index == TAB_NEXUS || m_RealTabPos[index] == TAB_NEXUS) { - activateNexusTab(); - } -} - - -void ModInfoDialog::on_modIDEdit_editingFinished() -{ - int oldID = m_Settings->value("modid", 0).toInt(); - int modID = ui->modIDEdit->text().toInt(); - if (oldID != modID){ - m_ModInfo->setNexusID(modID); - - ui->descriptionView->page()->setHtml(""); - if (modID != 0) { - m_RequestStarted = false; - refreshNexusData(modID); - } - } -} - -void ModInfoDialog::on_sourceGameEdit_currentIndexChanged(int) -{ - for (auto game : m_PluginContainer->plugins()) { - if (game->gameName() == ui->sourceGameEdit->currentText()) { - m_ModInfo->setGameName(game->gameShortName()); - return; - } - } -} - -void ModInfoDialog::on_versionEdit_editingFinished() -{ - VersionInfo version(ui->versionEdit->text()); - m_ModInfo->setVersion(version); - updateVersionColor(); -} - -void ModInfoDialog::on_customUrlLineEdit_editingFinished() -{ - m_ModInfo->setURL(ui->customUrlLineEdit->text()); } bool ModInfoDialog::recursiveDelete(const QModelIndex &index) @@ -975,22 +790,6 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); } - -void ModInfoDialog::on_refreshButton_clicked() -{ - if (m_ModInfo->getNexusID() > 0) { - QLineEdit *modIDEdit = findChild("modIDEdit"); - int modID = modIDEdit->text().toInt(); - refreshNexusData(modID); - } else - qInfo("Mod has no valid Nexus ID, info can't be updated."); -} - -void ModInfoDialog::on_endorseBtn_clicked() -{ - emit endorseMod(m_ModInfo); -} - void ModInfoDialog::on_nextButton_clicked() { int currentTab = ui->tabWidget->currentIndex(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 384aafce..7b96d21a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -138,18 +138,11 @@ public: void restoreState(const Settings& s); signals: - - void linkActivated(const QString &link); void downloadRequest(const QString &link); void modOpen(const QString &modName, int tab); void modOpenNext(int tab=-1); void modOpenPrev(int tab=-1); void originModified(int originID); - void endorseMod(ModInfo::Ptr nexusID); - -public slots: - - void modDetailsUpdated(bool success); private: @@ -157,10 +150,6 @@ private: void refreshLists(); - void updateVersionColor(); - - void refreshNexusData(int modID); - void activateNexusTab(); QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); @@ -168,9 +157,6 @@ private: int tabIndex(const QString &tabId); private slots: - void linkClicked(const QUrl &url); - void linkClicked(QString url); - void delete_activated(); void createDirectoryTriggered(); @@ -183,20 +169,10 @@ private slots: void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); - void on_visitNexusLabel_linkActivated(const QString &link); - void on_modIDEdit_editingFinished(); - void on_sourceGameEdit_currentIndexChanged(int); - void on_versionEdit_editingFinished(); - void on_customUrlLineEdit_editingFinished(); void on_tabWidget_currentChanged(int index); void on_fileTree_customContextMenuRequested(const QPoint &pos); - void on_refreshButton_clicked(); - - void on_endorseBtn_clicked(); - void on_nextButton_clicked(); - void on_prevButton_clicked(); private: @@ -217,11 +193,6 @@ private: QTreeView *m_FileTree; QModelIndexList m_FileSelection; - QSettings *m_Settings; - - std::set m_RequestIDs; - bool m_RequestStarted; - QAction *m_NewFolderAction; QAction *m_OpenAction; QAction *m_PreviewAction; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index c8ffd470..29a7400f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -829,7 +829,7 @@ text-align: left; - + true @@ -853,13 +853,13 @@ text-align: left; - +
    - + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png @@ -870,6 +870,60 @@ text-align: left; + + + + + 0 + 0 + + + + Refresh + + + Refresh all information from Nexus. + + + Refresh + + + + :/MO/gui/refresh:/MO/gui/refresh + + + Qt::ToolButtonTextBesideIcon + + + + + + + Open in Browser + + + + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png + + + Qt::ToolButtonTextBesideIcon + + + + + + + Endorse + + + + :/MO/gui/icon_favorite:/MO/gui/icon_favorite + + + Qt::ToolButtonTextBesideIcon + + + @@ -878,7 +932,7 @@ text-align: left; - + Mod ID for this mod on Nexus. @@ -891,22 +945,6 @@ p, li { white-space: pre-wrap; } - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - @@ -915,7 +953,7 @@ p, li { white-space: pre-wrap; } - + Source game for this mod. @@ -925,20 +963,7 @@ p, li { white-space: pre-wrap; } - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -952,7 +977,7 @@ p, li { white-space: pre-wrap; } - + 150 @@ -967,102 +992,61 @@ p, li { white-space: pre-wrap; } - - - - - - 0 - 0 - - - - Refresh - - - Refresh all information from Nexus. - - - - - - - :/MO/gui/refresh:/MO/gui/refresh - - - - - - - Description - - - - - - - - - - 0 - 0 - - - - - 0 - 200 - + + + QFrame::StyledPanel - - - about:blank - + + QFrame::Sunken + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 200 + + + + + about:blank + + + + + - - - - - - - 1 - 0 - - - - - - - false - - - - - - - Endorse - - - - :/MO/gui/icon_favorite:/MO/gui/icon_favorite - - - - - - Web page URL (only used if invalid Nexus ID) : + URL - + diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 7df13aea..69c7c6b5 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -3,28 +3,25 @@ #include "categories.h" #include "modinfo.h" -CategoriesTab::CategoriesTab(QWidget*, Ui::ModInfoDialog* ui) - : ui(ui) +CategoriesTab::CategoriesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui) { connect( - ui->categoriesTree, &QTreeWidget::itemChanged, + ui->categories, &QTreeWidget::itemChanged, [&](auto* item, int col){ onCategoryChanged(item, col); }); connect( - ui->primaryCategoryBox, + ui->primaryCategories, static_cast(&QComboBox::currentIndexChanged), [&](int index){ onPrimaryChanged(index); }); } -void CategoriesTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) -{ - m_mod = mod; -} - void CategoriesTab::clear() { - ui->categoriesTree->clear(); - ui->primaryCategoryBox->clear(); + ui->categories->clear(); + ui->primaryCategories->clear(); } void CategoriesTab::update() @@ -32,8 +29,8 @@ void CategoriesTab::update() clear(); add( - CategoryFactory::instance(), m_mod->getCategories(), - ui->categoriesTree->invisibleRootItem(), 0); + CategoryFactory::instance(), mod()->getCategories(), + ui->categories->invisibleRootItem(), 0); updatePrimary(); } @@ -71,15 +68,15 @@ void CategoriesTab::add( void CategoriesTab::updatePrimary() { - ui->primaryCategoryBox->clear(); + ui->primaryCategories->clear(); - int primaryCategory = m_mod->getPrimaryCategory(); + int primaryCategory = mod()->getPrimaryCategory(); - addChecked(ui->categoriesTree->invisibleRootItem()); + addChecked(ui->categories->invisibleRootItem()); - for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { - if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { - ui->primaryCategoryBox->setCurrentIndex(i); + for (int i = 0; i < ui->primaryCategories->count(); ++i) { + if (ui->primaryCategories->itemData(i).toInt() == primaryCategory) { + ui->primaryCategories->setCurrentIndex(i); break; } } @@ -90,7 +87,7 @@ void CategoriesTab::addChecked(QTreeWidgetItem* tree) for (int i = 0; i < tree->childCount(); ++i) { QTreeWidgetItem *child = tree->child(i); if (child->checkState(0) == Qt::Checked) { - ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); + ui->primaryCategories->addItem(child->text(0), child->data(0, Qt::UserRole)); addChecked(child); } } @@ -101,7 +98,7 @@ void CategoriesTab::save(QTreeWidgetItem* currentNode) for (int i = 0; i < currentNode->childCount(); ++i) { QTreeWidgetItem *childNode = currentNode->child(i); - m_mod->setCategory( + mod()->setCategory( childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); save(childNode); @@ -118,12 +115,12 @@ void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) } updatePrimary(); - save(ui->categoriesTree->invisibleRootItem()); + save(ui->categories->invisibleRootItem()); } void CategoriesTab::onPrimaryChanged(int index) { if (index != -1) { - m_mod->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); + mod()->setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); } } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index c0644c7f..76426a5d 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -5,16 +5,14 @@ class CategoryFactory; class CategoriesTab : public ModInfoDialogTab { public: - CategoriesTab(QWidget* parent, Ui::ModInfoDialog* ui); + CategoriesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; void update() override; private: - Ui::ModInfoDialog* ui; - ModInfo::Ptr m_mod; - void add( const CategoryFactory& factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d2dca492..4176ac3e 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -116,10 +116,10 @@ public: ConflictsTab::ConflictsTab( - QWidget* parent, Ui::ModInfoDialog* ui, - OrganizerCore& oc, PluginContainer& plugin) : - m_parent(parent), ui(ui), m_core(oc), m_plugin(plugin), - m_origin(nullptr), m_general(this, ui, oc), m_advanced(this, ui, oc) + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) : + ModInfoDialogTab(oc, plugin, parent, ui), + m_general(this, ui, oc), m_advanced(this, ui, oc) { connect( &m_general, &GeneralConflictsTab::modOpen, @@ -130,15 +130,6 @@ ConflictsTab::ConflictsTab( [&](const QString& name){ emitModOpen(name); }); } -void ConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) -{ - m_mod = mod; - m_origin = origin; - - m_general.setMod(mod, origin); - m_advanced.setMod(mod, origin); -} - void ConflictsTab::update() { m_general.update(); @@ -186,7 +177,7 @@ void ConflictsTab::changeItemsVisibility( flags |= FileRenamer::MULTIPLE; } - FileRenamer renamer(m_parent, flags); + FileRenamer renamer(parentWidget(), flags); for (const auto* item : items) { if (stop) { @@ -242,8 +233,8 @@ void ConflictsTab::changeItemsVisibility( if (changed) { qDebug().nospace() << "triggering refresh"; - if (m_origin) { - emit originModified(m_origin->getID()); + if (origin()) { + emit originModified(origin()->getID()); } update(); @@ -256,7 +247,7 @@ void ConflictsTab::openItems(const QList& items) // in case this changes for (auto* item : items) { if (auto* ci=dynamic_cast(item)) { - m_core.executeFileVirtualized(m_parent, ci->fileName()); + core().executeFileVirtualized(parentWidget(), ci->fileName()); } } } @@ -267,7 +258,7 @@ void ConflictsTab::previewItems(const QList& items) // in case this changes for (auto* item : items) { if (auto* ci=dynamic_cast(item)) { - m_core.previewFileWithAlternatives(m_parent, ci->fileName()); + core().previewFileWithAlternatives(parentWidget(), ci->fileName()); } } } @@ -355,7 +346,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( enableHide = ci->canHide(); enableUnhide = ci->canUnhide(); enableOpen = ci->canOpen(); - enablePreview = ci->canPreview(m_plugin); + enablePreview = ci->canPreview(plugin()); enableGoto = ci->hasAlts(); } else { @@ -394,21 +385,21 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( Actions actions; - actions.hide = new QAction(tr("Hide"), m_parent); + actions.hide = new QAction(tr("Hide"), parentWidget()); actions.hide->setEnabled(enableHide); // note that it is possible for hidden files to appear if they override other // hidden files from another mod - actions.unhide = new QAction(tr("Unhide"), m_parent); + actions.unhide = new QAction(tr("Unhide"), parentWidget()); actions.unhide->setEnabled(enableUnhide); - actions.open = new QAction(tr("Open/Execute"), m_parent); + actions.open = new QAction(tr("Open/Execute"), parentWidget()); actions.open->setEnabled(enableOpen); - actions.preview = new QAction(tr("Preview"), m_parent); + actions.preview = new QAction(tr("Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); - actions.gotoMenu = new QMenu(tr("Go to..."), m_parent); + actions.gotoMenu = new QMenu(tr("Go to..."), parentWidget()); actions.gotoMenu->setEnabled(enableGoto); if (enableGoto) { @@ -421,7 +412,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( std::vector ConflictsTab::createGotoActions( const QList& selection) { - if (!m_origin || selection.size() != 1) { + if (!origin() || selection.size() != 1) { return {}; } @@ -430,26 +421,26 @@ std::vector ConflictsTab::createGotoActions( return {}; } - auto file = m_origin->findFile(item->fileIndex()); + auto file = origin()->findFile(item->fileIndex()); if (!file) { return {}; } std::vector mods; - const auto& ds = *m_core.directoryStructure(); + const auto& ds = *core().directoryStructure(); // add all alternatives for (const auto& alt : file->getAlternatives()) { const auto& o = ds.getOriginByID(alt.first); - if (o.getID() != m_origin->getID()) { + if (o.getID() != origin()->getID()) { mods.push_back(ToQString(o.getName())); } } // add the real origin if different from this mod const FilesOrigin& realOrigin = ds.getOriginByID(file->getOrigin()); - if (realOrigin.getID() != m_origin->getID()) { + if (realOrigin.getID() != origin()->getID()) { mods.push_back(ToQString(realOrigin.getName())); } @@ -460,7 +451,7 @@ std::vector ConflictsTab::createGotoActions( std::vector actions; for (const auto& name : mods) { - actions.push_back(new QAction(name, m_parent)); + actions.push_back(new QAction(name, parentWidget())); } return actions; @@ -469,7 +460,7 @@ std::vector ConflictsTab::createGotoActions( GeneralConflictsTab::GeneralConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) + : m_tab(tab), ui(ui), m_core(oc) { m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); @@ -560,12 +551,6 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::setMod(ModInfo::Ptr mod, FilesOrigin* origin) -{ - m_mod = mod; - m_origin = origin; -} - void GeneralConflictsTab::update() { clear(); @@ -574,10 +559,10 @@ void GeneralConflictsTab::update() int numOverwrite = 0; int numOverwritten = 0; - if (m_origin != nullptr) { - const auto rootPath = m_mod->absolutePath(); + if (m_tab->origin() != nullptr) { + const auto rootPath = m_tab->mod()->absolutePath(); - for (const auto& file : m_origin->getFiles()) { + for (const auto& file : m_tab->origin()->getFiles()) { const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); const QString fileName = relativeName.mid(0).prepend(rootPath); @@ -585,7 +570,7 @@ void GeneralConflictsTab::update() const int fileOrigin = file->getOrigin(archive); const auto& alternatives = file->getAlternatives(); - if (fileOrigin == m_origin->getID()) { + if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { ui->overwriteTree->addTopLevelItem(createOverwriteItem( file->getIndex(), archive, fileName, relativeName, alternatives)); @@ -684,7 +669,7 @@ void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) AdvancedConflictsTab::AdvancedConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) + : m_tab(tab), ui(ui), m_core(oc) { // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( @@ -760,20 +745,14 @@ void AdvancedConflictsTab::restoreState(const Settings& s) } } -void AdvancedConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) -{ - m_mod = mod; - m_origin = origin; -} - void AdvancedConflictsTab::update() { clear(); - if (m_origin != nullptr) { - const auto rootPath = m_mod->absolutePath(); + if (m_tab->origin() != nullptr) { + const auto rootPath = m_tab->mod()->absolutePath(); - for (const auto& file : m_origin->getFiles()) { + for (const auto& file : m_tab->origin()->getFiles()) { const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); const QString fileName = relativeName.mid(0).prepend(rootPath); @@ -813,14 +792,14 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // fills 'before' and 'after' with all the alternatives that come // before and after this mod in terms of priority - if (altOrigin.getPriority() < m_origin->getPriority()) { + if (altOrigin.getPriority() < m_tab->origin()->getPriority()) { // add all the mods having a lower priority than this one if (!before.isEmpty()) { before += ", "; } before += ToQString(altOrigin.getName()); - } else if (altOrigin.getPriority() > m_origin->getPriority()) { + } else if (altOrigin.getPriority() > m_tab->origin()->getPriority()) { // add all the mods having a higher priority than this one if (!after.isEmpty()) { after += ", "; @@ -832,7 +811,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // keep track of the nearest mods that come before and after this one // in terms of priority - if (altOrigin.getPriority() < m_origin->getPriority()) { + if (altOrigin.getPriority() < m_tab->origin()->getPriority()) { // the alternative has a lower priority than this mod if (altOrigin.getPriority() > beforePrio) { @@ -843,7 +822,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( } } - if (altOrigin.getPriority() > m_origin->getPriority()) { + if (altOrigin.getPriority() > m_tab->origin()->getPriority()) { // the alternative has a higher priority than this mod if (altOrigin.getPriority() < afterPrio) { @@ -869,7 +848,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // if no mods overwrite this file, the primary origin is the same as this // mod, so ignore that - if (realOrigin.getID() != m_origin->getID()) { + if (realOrigin.getID() != m_tab->origin()->getID()) { if (!after.isEmpty()) { after += ", "; } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 212a196d..20362575 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -21,7 +21,6 @@ public: void saveState(Settings& s); void restoreState(const Settings& s); - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); signals: @@ -36,8 +35,6 @@ private: ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; - ModInfo::Ptr m_mod; - MOShared::FilesOrigin* m_origin; Expanders m_expanders; QTreeWidgetItem* createOverwriteItem( @@ -74,7 +71,6 @@ public: void saveState(Settings& s); void restoreState(const Settings& s); - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); signals: @@ -84,8 +80,6 @@ private: ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; - ModInfo::Ptr m_mod; - MOShared::FilesOrigin* m_origin; FilterWidget m_filter; QTreeWidgetItem* createItem( @@ -101,10 +95,9 @@ class ConflictsTab : public ModInfoDialogTab public: ConflictsTab( - QWidget* parent, Ui::ModInfoDialog* ui, - OrganizerCore& oc, PluginContainer& plugin); + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; void update() override; void clear() override; @@ -136,12 +129,6 @@ private: } }; - QWidget* m_parent; - Ui::ModInfoDialog* ui; - OrganizerCore& m_core; - PluginContainer& m_plugin; - ModInfo::Ptr m_mod; - MOShared::FilesOrigin* m_origin; GeneralConflictsTab m_general; AdvancedConflictsTab m_advanced; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index cb3dcc84..ea7eb3b0 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -122,8 +122,10 @@ private: }; -ESPsTab::ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui) - : m_parent(parent), ui(ui) +ESPsTab::ESPsTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui) { QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); @@ -187,7 +189,7 @@ void ESPsTab::onActivate() bool okClicked = false; newName = QInputDialog::getText( - m_parent, + parentWidget(), QObject::tr("File Exists"), QObject::tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, file.fileName(), &okClicked); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index d71ad3ff..e1a7a4f7 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -10,15 +10,14 @@ class ESPsTab : public ModInfoDialogTab Q_OBJECT; public: - ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui); + ESPsTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; private: - QWidget* m_parent; - Ui::ModInfoDialog* ui; - void onActivate(); void onDeactivate(); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 752c8d0c..1c7dcc1f 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -80,8 +80,10 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) } -ImagesTab::ImagesTab(QWidget*, Ui::ModInfoDialog* ui) - : ui(ui), m_image(new ScalableImage) +ImagesTab::ImagesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui), m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); ui->imagesThumbnails->setLayout(new QVBoxLayout); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 708045c8..7853935a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -34,13 +34,14 @@ class ImagesTab : public ModInfoDialogTab Q_OBJECT; public: - ImagesTab(QWidget* parent, Ui::ModInfoDialog* ui); + ImagesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; private: - Ui::ModInfoDialog* ui; ScalableImage* m_image; void add(const QString& fullPath); diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp new file mode 100644 index 00000000..753d43de --- /dev/null +++ b/src/modinfodialognexus.cpp @@ -0,0 +1,273 @@ +#include "modinfodialognexus.h" +#include "ui_modinfodialog.h" +#include "settings.h" +#include "organizercore.h" +#include "iplugingame.h" +#include "bbcode.h" +#include +#include + +NexusTab::NexusTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui), m_requestStarted(false) +{ + ui->modID->setValidator(new QIntValidator(ui->modID)); + ui->endorse->setVisible(core().settings().endorsementIntegration()); + + connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); + connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); + connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); + connect(ui->url, &QLineEdit::editingFinished, [&]{ onUrlChanged(); }); + connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); + connect(ui->refresh, &QToolButton::clicked, [&]{ updateWebpage(); }); + + connect( + ui->sourceGame, + static_cast(&QComboBox::currentIndexChanged), + [&]{ onSourceGameChanged(); }); +} + +NexusTab::~NexusTab() +{ + cleanup(); +} + +void NexusTab::cleanup() +{ + if (m_modConnection) { + disconnect(m_modConnection); + m_modConnection = {}; + } +} + +void NexusTab::clear() +{ + ui->modID->clear(); + ui->sourceGame->clear(); + ui->version->clear(); + ui->browser->setPage(new NexusTabWebpage(ui->browser)); + ui->url->clear(); +} + +void NexusTab::update() +{ + clear(); + + ui->modID->setText(QString("%1").arg(mod()->getNexusID())); + + QString gameName = mod()->getGameName(); + ui->sourceGame->addItem( + core().managedGame()->gameName(), + core().managedGame()->gameShortName()); + + if (core().managedGame()->validShortNames().size() == 0) { + ui->sourceGame->setDisabled(true); + } else { + for (auto game : plugin().plugins()) { + for (QString gameName : core().managedGame()->validShortNames()) { + if (game->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { + ui->sourceGame->addItem(game->gameName(), game->gameShortName()); + break; + } + } + } + } + + ui->sourceGame->setCurrentIndex(ui->sourceGame->findData(gameName)); + + auto* page = new NexusTabWebpage(ui->browser); + ui->browser->setPage(page); + + connect( + page, &NexusTabWebpage::linkClicked, + [&](const QUrl& url){ MOBase::shell::OpenLink(url); }); + + ui->endorse->setEnabled( + (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || + (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); + + updateWebpage(); +} + +void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + cleanup(); + + ModInfoDialogTab::setMod(mod, origin); + + m_modConnection = connect( + mod.data(), &ModInfo::modDetailsUpdated, [&]{ onModChanged(); }); +} + +void NexusTab::updateVersionColor() +{ + if (mod()->getVersion() != mod()->getNewestVersion()) { + ui->version->setStyleSheet("color: red"); + ui->version->setToolTip(tr("Current Version: %1").arg( + mod()->getNewestVersion().canonicalString())); + } else { + ui->version->setStyleSheet("color: green"); + ui->version->setToolTip(tr("No update available")); + } +} + +void NexusTab::updateWebpage() +{ + const int modID = mod()->getNexusID(); + + if (modID > 0) { + const QString nexusLink = NexusInterface::instance(&plugin()) + ->getModURL(modID, mod()->getGameName()); + + ui->openInBrowser->setToolTip(nexusLink); + mod()->setURL(nexusLink); + refreshData(modID); + } else { + onModChanged(); + } + + ui->version->setText(mod()->getVersion().displayString()); + ui->url->setText(mod()->getURL()); +} + +void NexusTab::onModChanged() +{ + m_requestStarted = false; + + const QString nexusDescription = mod()->getNexusDescription(); + + QString descriptionAsHTML = R"( + + + + + %1 +)"; + + if (nexusDescription.isEmpty()) { + descriptionAsHTML = descriptionAsHTML.arg(tr( + "
    " + "

    Uh oh!

    " + "

    Sorry, there is no description available for this mod. :(

    " + "
    ")); + + } else { + descriptionAsHTML = descriptionAsHTML.arg( + BBCode::convertToHTML(nexusDescription)); + } + + ui->browser->page()->setHtml(descriptionAsHTML); + updateVersionColor(); +} + +void NexusTab::onModIDChanged() +{ + const int oldID = mod()->getNexusID(); + const int newID = ui->modID->text().toInt(); + + if (oldID != newID){ + mod()->setNexusID(newID); + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + + ui->browser->page()->setHtml(""); + + if (newID != 0) { + refreshData(newID); + } + } +} + +void NexusTab::onSourceGameChanged() +{ + for (auto game : plugin().plugins()) { + if (game->gameName() == ui->sourceGame->currentText()) { + mod()->setGameName(game->gameShortName()); + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + refreshData(mod()->getNexusID()); + return; + } + } +} + +void NexusTab::onVersionChanged() +{ + MOBase::VersionInfo version(ui->version->text()); + mod()->setVersion(version); + updateVersionColor(); +} + +void NexusTab::onUrlChanged() +{ + mod()->setURL(ui->url->text()); + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); +} + +void NexusTab::onOpenLink() +{ + const int modID = mod()->getNexusID(); + + if (modID > 0) { + const QString nexusLink = NexusInterface::instance(&plugin()) + ->getModURL(modID, mod()->getGameName()); + + MOBase::shell::OpenLink(QUrl(nexusLink)); + } +} + +void NexusTab::onRefreshBrowser() +{ + const auto modID = mod()->getNexusID(); + + if (modID > 0) { + refreshData(modID); + } else + qInfo("Mod has no valid Nexus ID, info can't be updated."); +} + +void NexusTab::onEndorse() +{ + core().loggedInAction(parentWidget(), [m=mod()]{ m->endorse(true); }); +} + +void NexusTab::refreshData(int modID) +{ + if (tryRefreshData(modID)) { + m_requestStarted = true; + } else { + onModChanged(); + } + + //MessageDialog::showMessage(tr("Info requested, please wait"), this); +} + +bool NexusTab::tryRefreshData(int modID) +{ + if (modID <= 0) { + qDebug() << "NexusTab: can't refresh, no mod id"; + return false; + } + + if (m_requestStarted) { + qDebug() << "NexusTab: a refresh request is already running"; + return false; + } + + if (!mod()->updateNXMInfo()) { + qDebug() << "NexusTab: nexus description does not need an update"; + return false; + } + + return true; +} diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h new file mode 100644 index 00000000..2e328c6d --- /dev/null +++ b/src/modinfodialognexus.h @@ -0,0 +1,67 @@ +#ifndef MODINFODIALOGNEXUS_H +#define MODINFODIALOGNEXUS_H + +#include "modinfodialogtab.h" + +class NexusTabWebpage : public QWebEnginePage +{ + Q_OBJECT + +public: + NexusTabWebpage(QObject* parent = 0) + : QWebEnginePage(parent) + { + } + + bool acceptNavigationRequest( + const QUrl & url, QWebEnginePage::NavigationType type, bool) override + { + if (type == QWebEnginePage::NavigationTypeLinkClicked) + { + emit linkClicked(url); + return false; + } + + return true; + } + +signals: + void linkClicked(const QUrl&); +}; + + +class NexusTab : public ModInfoDialogTab +{ +public: + NexusTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + ~NexusTab(); + + void clear() override; + void update() override; + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + +private: + QMetaObject::Connection m_modConnection; + bool m_requestStarted; + + void cleanup(); + void updateVersionColor(); + void updateWebpage(); + + void refreshData(int modID); + bool tryRefreshData(int modID); + + void onModChanged(); + void onOpenLink(); + void onModIDChanged(); + void onSourceGameChanged(); + void onVersionChanged(); + void onRefreshBrowser(); + void onEndorse(); + void onUrlChanged(); +}; + +#endif // MODINFODIALOGNEXUS_H diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 6dcff0ac..ae0de5e8 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,5 +1,12 @@ #include "modinfodialogtab.h" +ModInfoDialogTab::ModInfoDialogTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), m_origin(nullptr) +{ +} + bool ModInfoDialogTab::feedFile(const QString&, const QString&) { // no-op @@ -21,14 +28,40 @@ void ModInfoDialogTab::restoreState(const Settings& s) // no-op } -void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) +void ModInfoDialogTab::update() { // no-op } -void ModInfoDialogTab::update() +void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { - // no-op + m_mod = mod; + m_origin = origin; +} + +ModInfo::Ptr ModInfoDialogTab::mod() const +{ + return m_mod; +} + +MOShared::FilesOrigin* ModInfoDialogTab::origin() const +{ + return m_origin; +} + +OrganizerCore& ModInfoDialogTab::core() +{ + return m_core; +} + +PluginContainer& ModInfoDialogTab::plugin() +{ + return m_plugin; +} + +QWidget* ModInfoDialogTab::parentWidget() +{ + return m_parent; } void ModInfoDialogTab::emitOriginModified(int originID) diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index ead29d10..dd851b31 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -8,13 +8,13 @@ namespace MOShared { class FilesOrigin; } namespace Ui { class ModInfoDialog; } class Settings; +class OrganizerCore; class ModInfoDialogTab : public QObject { Q_OBJECT; public: - ModInfoDialogTab() = default; ModInfoDialogTab(const ModInfoDialogTab&) = delete; ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; ModInfoDialogTab(ModInfoDialogTab&&) = default; @@ -22,21 +22,42 @@ public: virtual ~ModInfoDialogTab() = default; virtual void clear() = 0; + virtual void update(); virtual bool feedFile(const QString& rootPath, const QString& filename); virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); - virtual void update(); + + ModInfo::Ptr mod() const; + MOShared::FilesOrigin* origin() const; signals: void originModified(int originID); void modOpen(QString name); protected: + Ui::ModInfoDialog* ui; + + ModInfoDialogTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + OrganizerCore& core(); + PluginContainer& plugin(); + + QWidget* parentWidget(); + void emitOriginModified(int originID); void emitModOpen(QString name); + +private: + OrganizerCore& m_core; + PluginContainer& m_plugin; + QWidget* m_parent; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; }; #endif // MODINFODIALOGTAB_H diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 0dd6f06e..f48557b0 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -22,8 +22,10 @@ private: GenericFilesTab::GenericFilesTab( - QWidget* parent, QListWidget* list, QSplitter* sp, TextEditor* e) - : m_parent(parent), m_list(list), m_editor(e) + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui, + QListWidget* list, QSplitter* sp, TextEditor* e) + : ModInfoDialogTab(oc, plugin, parent, ui), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -49,7 +51,7 @@ bool GenericFilesTab::canClose() } const int res = QMessageBox::question( - m_parent, + parentWidget(), QObject::tr("Save changes?"), QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); @@ -109,9 +111,12 @@ void GenericFilesTab::select(FileListItem* item) } -TextFilesTab::TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) - : GenericFilesTab( - parent, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) +TextFilesTab::TextFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + oc, plugin, parent, ui, + ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -130,9 +135,12 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) - : GenericFilesTab( - parent, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) +IniFilesTab::IniFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + oc, plugin, parent, ui, + ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 11691d64..0dc5ec89 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -18,12 +18,12 @@ public: bool feedFile(const QString& rootPath, const QString& fullPath) override; protected: - QWidget* m_parent; QListWidget* m_list; TextEditor* m_editor; GenericFilesTab( - QWidget* parent, + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui, QListWidget* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -37,7 +37,9 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); + TextFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -47,7 +49,9 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); + IniFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index a271c4e8..babbd665 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -118,7 +118,7 @@ void ModInfoRegular::readMeta() m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; } } - + QString categoriesString = metaFile.value("category", "").toString(); QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); @@ -291,15 +291,27 @@ void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, QNet bool ModInfoRegular::updateNXMInfo() { - QDateTime time = QDateTime::currentDateTimeUtc(); - QDateTime target = m_LastNexusQuery.addDays(1); - if (m_NexusID > 0 && time >= target) { + if (needsDescriptionUpdate()) { m_NexusBridge.requestDescription(m_GameName, m_NexusID, QVariant()); return true; } + return false; } +bool ModInfoRegular::needsDescriptionUpdate() const +{ + if (m_NexusID > 0) { + QDateTime time = QDateTime::currentDateTimeUtc(); + QDateTime target = m_LastNexusQuery.addDays(1); + + if (time >= target) { + return true; + } + } + + return false; +} void ModInfoRegular::setCategory(int categoryID, bool active) { diff --git a/src/modinforegular.h b/src/modinforegular.h index f70487a2..cfe713ca 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -464,6 +464,7 @@ private: mutable std::vector m_CachedContent; mutable QTime m_LastContentCheck; + bool needsDescriptionUpdate() const; }; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2cad9ce8..e073924b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2204,6 +2204,21 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap f) +{ + if (NexusInterface::instance(m_PluginContainer)->getAccessManager()->validated()) { + f(); + } else { + QString apiKey; + if (settings().getNexusApiKey(apiKey)) { + doAfterLogin([f]{ f(); }); + NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus"), parent); + } + } +} + void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) { if (m_PluginContainer != nullptr) { diff --git a/src/organizercore.h b/src/organizercore.h index a4a57496..4dd11831 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -146,6 +146,7 @@ public: void updateModsInDirectoryStructure(QMap modInfos); void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } + void loggedInAction(QWidget* parent, std::function f); static QString findJavaInstallation(const QString& jarFile={}); -- cgit v1.3.1 From cbdc4cc3284f13477bfbf292d15c4a5742627091 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 03:03:41 -0400 Subject: split notes tab added new HTMLEditor that triggers an editingFinished() on focus our, used by notes tab --- src/modinfodialog.cpp | 14 +------------- src/modinfodialog.ui | 17 +++++++++++------ src/modinfodialognexus.cpp | 2 -- src/modinfodialogtab.cpp | 39 +++++++++++++++++++++++++++++++++++++++ src/modinfodialogtab.h | 16 ++++++++++++++++ src/texteditor.cpp | 10 ++++++++++ src/texteditor.h | 17 +++++++++++++++++ 7 files changed, 94 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 30110d14..b78f4515 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -19,7 +19,6 @@ along with Mod Organizer. If not, see . #include "modinfodialog.h" #include "ui_modinfodialog.h" -#include "descriptionpage.h" #include "mainwindow.h" #include "modidlineedit.h" @@ -173,9 +172,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_RootPath = modInfo->absolutePath(); - ui->commentsEdit->setText(modInfo->comments()); - ui->notesEdit->setText(modInfo->notes()); - //TODO: No easy way to delegate links //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); @@ -238,14 +234,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ModInfoDialog::~ModInfoDialog() { - m_ModInfo->setComments(ui->commentsEdit->text()); - - //Avoid saving html stump if notes field is empty. - if (ui->notesEdit->toPlainText().isEmpty()) - m_ModInfo->setNotes(ui->notesEdit->toPlainText()); - else - m_ModInfo->setNotes(ui->notesEdit->toHtml()); - delete ui; } @@ -264,7 +252,7 @@ std::vector> ModInfoDialog::createTabs() { return createTabsImpl< TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, - ConflictsTab, CategoriesTab, NexusTab>( + ConflictsTab, CategoriesTab, NexusTab, NotesTab>( *m_OrganizerCore, *m_PluginContainer, this, ui); } diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 29a7400f..360ecc79 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -6,7 +6,7 @@ 0 0 - 790 + 735 534 @@ -20,7 +20,7 @@ QTabWidget::Rounded - 0 + 7 true @@ -180,7 +180,7 @@ 0 0 - 741 + 686 436 @@ -859,7 +859,7 @@ text-align: left;
    - + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png @@ -963,7 +963,7 @@ p, li { white-space: pre-wrap; }
    - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1071,7 +1071,7 @@ p, li { white-space: pre-wrap; } - + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. @@ -1210,6 +1210,11 @@ p, li { white-space: pre-wrap; } QPlainTextEdit
    texteditor.h
    + + HTMLEditor + QTextEdit +
    texteditor.h
    +
    diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 753d43de..55b55439 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -248,8 +248,6 @@ void NexusTab::refreshData(int modID) } else { onModChanged(); } - - //MessageDialog::showMessage(tr("Info requested, please wait"), this); } bool NexusTab::tryRefreshData(int modID) diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index ae0de5e8..b59f4dcc 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,4 +1,6 @@ #include "modinfodialogtab.h" +#include "ui_modinfodialog.h" +#include "texteditor.h" ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, @@ -73,3 +75,40 @@ void ModInfoDialogTab::emitModOpen(QString name) { emit modOpen(name); } + + +NotesTab::NotesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui) +{ + connect(ui->commentsEdit, &QLineEdit::editingFinished, [&]{ onComments(); }); + connect(ui->notesEdit, &HTMLEditor::editingFinished, [&]{ onNotes(); }); +} + +void NotesTab::clear() +{ + ui->commentsEdit->clear(); + ui->notesEdit->clear(); +} + +void NotesTab::update() +{ + ui->commentsEdit->setText(mod()->comments()); + ui->notesEdit->setText(mod()->notes()); +} + +void NotesTab::onComments() +{ + mod()->setComments(ui->commentsEdit->text()); +} + +void NotesTab::onNotes() +{ + // Avoid saving html stub if notes field is empty. + if (ui->notesEdit->toPlainText().isEmpty()) { + mod()->setNotes({}); + } else { + mod()->setNotes(ui->notesEdit->toHtml()); + } +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index dd851b31..60371954 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -60,4 +60,20 @@ private: MOShared::FilesOrigin* m_origin; }; + +class NotesTab : public ModInfoDialogTab +{ +public: + NotesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + void clear() override; + void update() override; + +private: + void onComments(); + void onNotes(); +}; + #endif // MODINFODIALOGTAB_H diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 99490b22..6c1685da 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -458,3 +458,13 @@ void TextEditorToolbar::onWordWrap(bool b) { m_wordWrap->setChecked(b); } + + +void HTMLEditor::focusOutEvent(QFocusEvent* e) +{ + if (document() && document()->isModified()) { + emit editingFinished(); + } + + QTextEdit::focusInEvent(e); +} diff --git a/src/texteditor.h b/src/texteditor.h index eef5ca52..f3031731 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -137,4 +137,21 @@ private: void paintLineNumbers(QPaintEvent* e, const QColor& textColor); }; + +class HTMLEditor : public QTextEdit +{ + Q_OBJECT; + +public: + using QTextEdit::QTextEdit; + +signals: + void editingFinished(); + +protected: + void focusOutEvent(QFocusEvent* e); + +private: +}; + #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From 895883571b2b71c891dbaad4662adc7b39256286 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 04:04:54 -0400 Subject: splitting filetree tab moved mod info dialog classes to a sub filter --- src/CMakeLists.txt | 19 +- src/modinfodialog.cpp | 409 +-------------------------------------- src/modinfodialog.h | 28 +-- src/modinfodialog.ui | 8 +- src/modinfodialogconflicts.cpp | 14 ++ src/modinfodialogconflicts.h | 16 +- src/modinfodialogfiletree.cpp | 420 +++++++++++++++++++++++++++++++++++++++++ src/modinfodialogfiletree.h | 39 ++++ 8 files changed, 502 insertions(+), 451 deletions(-) create mode 100644 src/modinfodialogfiletree.cpp create mode 100644 src/modinfodialogfiletree.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7dc39fd9..1d27f444 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ SET(organizer_SRCS modinfodialogcategories.cpp modinfodialogconflicts.cpp modinfodialogesps.cpp + modinfodialogfiletree.cpp modinfodialogimages.cpp modinfodialognexus.cpp modinfodialogtab.cpp @@ -164,6 +165,7 @@ SET(organizer_HDRS modinfodialogcategories.h modinfodialogconflicts.h modinfodialogesps.h + modinfodialogfiletree.h modinfodialogimages.h modinfodialognexus.h modinfodialogtab.h @@ -364,21 +366,24 @@ set(locking set(modinfo modinfo modinfobackup + modinfoforeign + modinfooverwrite + modinforegular + modinfoseparator + modinfowithconflictinfo +) + +set(modinfo\\dialog modinfodialog modinfodialogcategories modinfodialogconflicts modinfodialogesps + modinfodialogfiletree modinfodialogimages modinfodialognexus modinfodialogtab modinfodialogtextfiles - modinfoforeign - modinfooverwrite - modinforegular - modinfoseparator - modinfowithconflictinfo ) - set(modlist modlist modlistsortproxy @@ -443,7 +448,7 @@ set(widgets ) set(src_filters - application core browser dialogs downloads executables locking modinfo + application core browser dialogs downloads executables locking modinfo modinfo\\dialog modlist plugins previews profiles settings utilities widgets ) diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b78f4515..6463a5a6 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -43,6 +43,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogconflicts.h" #include "modinfodialogcategories.h" #include "modinfodialognexus.h" +#include "modinfodialogfiletree.h" #include #include @@ -193,7 +194,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - //ui->tabWidget->setTabEnabled(TAB_NOTES, false); + ui->tabWidget->setTabEnabled(TAB_NOTES, true); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); } else if (unmanaged) @@ -205,8 +206,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); + ui->tabWidget->setTabEnabled(TAB_FILETREE, false); } else { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); ui->tabWidget->setTabEnabled(TAB_INIFILES, true); @@ -215,8 +216,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); ui->tabWidget->setTabEnabled(TAB_NEXUS, true); - - initFiletree(modInfo); + ui->tabWidget->setTabEnabled(TAB_NOTES, true); + ui->tabWidget->setTabEnabled(TAB_FILETREE, true); } // activate first enabled tab @@ -252,7 +253,7 @@ std::vector> ModInfoDialog::createTabs() { return createTabsImpl< TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, - ConflictsTab, CategoriesTab, NexusTab, NotesTab>( + ConflictsTab, CategoriesTab, NexusTab, NotesTab, FileTreeTab>( *m_OrganizerCore, *m_PluginContainer, this, ui); } @@ -262,35 +263,6 @@ int ModInfoDialog::exec() return TutorableDialog::exec(); } -void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) -{ - ui->fileTree = findChild("fileTree"); - - m_FileSystemModel = new QFileSystemModel(this); - m_FileSystemModel->setReadOnly(false); - m_FileSystemModel->setRootPath(m_RootPath); - ui->fileTree->setModel(m_FileSystemModel); - ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath)); - ui->fileTree->setColumnWidth(0, 300); - - m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); - m_OpenAction = new QAction(tr("&Open"), ui->fileTree); - m_PreviewAction = new QAction(tr("&Preview"), ui->fileTree); - m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); - m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); - m_HideAction = new QAction(tr("&Hide"), ui->fileTree); - m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); - - connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); - connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); - connect(m_PreviewAction, SIGNAL(triggered()), this, SLOT(previewTriggered())); - connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); - connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); - connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); - connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); -} - - int ModInfoDialog::tabIndex(const QString &tabId) { for (int i = 0; i < ui->tabWidget->count(); ++i) { @@ -301,7 +273,6 @@ int ModInfoDialog::tabIndex(const QString &tabId) return -1; } - void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); @@ -340,6 +311,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) m_RealTabPos[newPos] = newPos; } } + // then actually move the tabs QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad ui->tabWidget->blockSignals(true); @@ -407,377 +379,10 @@ void ModInfoDialog::openTab(int tab) } } -QString ModInfoDialog::getFileCategory(int categoryID) -{ - switch (categoryID) { - case 1: return tr("Main"); - case 2: return tr("Update"); - case 3: return tr("Optional"); - case 4: return tr("Old"); - case 5: return tr("Miscellaneous"); - case 6: return tr("Deleted"); - default: return tr("Unknown"); - } -} - void ModInfoDialog::on_tabWidget_currentChanged(int index) { } -bool ModInfoDialog::recursiveDelete(const QModelIndex &index) -{ - for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { - QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); - if (m_FileSystemModel->isDir(childIndex)) { - if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); - return false; - } - return true; -} - - -void ModInfoDialog::on_openInExplorerButton_clicked() -{ - shell::ExploreFile(m_ModInfo->absolutePath()); -} - -void ModInfoDialog::deleteFile(const QModelIndex &index) -{ - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) - : m_FileSystemModel->remove(index); - if (!res) { - QString fileName = m_FileSystemModel->fileName(index); - reportError(tr("Failed to delete %1").arg(fileName)); - } -} - -void ModInfoDialog::delete_activated() -{ - if (ui->fileTree->hasFocus()) { - QItemSelectionModel *selection = ui->fileTree->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() >= 1) { - - if (selection->selectedRows().count() == 0) { - return; - } - else if (selection->selectedRows().count() == 1) { - QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, selection->selectedRows()) { - deleteFile(index); - } - } - } -} - -void ModInfoDialog::deleteTriggered() -{ - if (m_FileSelection.count() == 0) { - return; - } else if (m_FileSelection.count() == 1) { - QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, m_FileSelection) { - deleteFile(index); - } -} - - -void ModInfoDialog::renameTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = selection.sibling(selection.row(), 0); - if (!index.isValid() || m_FileSystemModel->isReadOnly()) { - return; - } - - ui->fileTree->edit(index); -} - - -void ModInfoDialog::hideTriggered() -{ - changeFiletreeVisibility(false); -} - - -void ModInfoDialog::unhideTriggered() -{ - changeFiletreeVisibility(true); -} - -void ModInfoDialog::changeFiletreeVisibility(bool visible) -{ - bool changed = false; - bool stop = false; - - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << m_FileSelection.size() << " filetree files"; - - QFlags flags = - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - - if (m_FileSelection.size() > 1) { - flags |= FileRenamer::MULTIPLE; - } - - FileRenamer renamer(this, flags); - - for (const auto& index : m_FileSelection) { - if (stop) { - break; - } - - const QString path = m_FileSystemModel->filePath(index); - auto result = FileRenamer::RESULT_CANCEL; - - if (visible) { - if (!canUnhideFile(false, path)) { - qDebug().nospace() << "cannot unhide " << path << ", skipping"; - continue; - } - result = unhideFile(renamer, path); - } else { - if (!canHideFile(false, path)) { - qDebug().nospace() << "cannot hide " << path << ", skipping"; - continue; - } - result = hideFile(renamer, path); - } - - switch (result) { - case FileRenamer::RESULT_OK: { - // will trigger a refresh at the end - changed = true; - break; - } - - case FileRenamer::RESULT_SKIP: { - // nop - break; - } - - case FileRenamer::RESULT_CANCEL: { - // stop right now, but make sure to refresh if needed - stop = true; - break; - } - } - } - - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; - - if (changed) { - qDebug().nospace() << "triggering refresh"; - if (m_Origin) { - emit originModified(m_Origin->getID()); - } - refreshLists(); - } -} - - -void ModInfoDialog::openTriggered() -{ - if (m_FileSelection.size() == 1) { - const auto index = m_FileSelection.at(0); - if (!index.isValid()) { - return; - } - - QString fileName = m_FileSystemModel->filePath(index); - shell::OpenFile(fileName); - } -} - -void ModInfoDialog::previewTriggered() -{ - if (m_FileSelection.size() == 1) { - const auto index = m_FileSelection.at(0); - if (!index.isValid()) { - return; - } - - QString fileName = m_FileSystemModel->filePath(index); - m_OrganizerCore->previewFile(this, m_ModInfo->name(), fileName); - } -} - -void ModInfoDialog::createDirectoryTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection - : selection.parent(); - index = index.sibling(index.row(), 0); - - QString name = tr("New Folder"); - QString path = m_FileSystemModel->filePath(index).append("/"); - - QModelIndex existingIndex = m_FileSystemModel->index(path + name); - int suffix = 1; - while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); - existingIndex = m_FileSystemModel->index(path + name); - } - - QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); - if (!newIndex.isValid()) { - reportError(tr("Failed to create \"%1\"").arg(name)); - return; - } - - ui->fileTree->setCurrentIndex(newIndex); - ui->fileTree->edit(newIndex); -} - - -void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - - QMenu menu(ui->fileTree); - - menu.addAction(m_NewFolderAction); - - if (selectionModel->hasSelection()) { - bool enableOpen = true; - bool enablePreview = true; - bool enableRename = true; - bool enableDelete = true; - bool enableHide = true; - bool enableUnhide = true; - - if (m_FileSelection.size() == 1) { - // single selection - - // only enable open action if a file is selected - bool hasFiles = false; - - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } - } - - if (!hasFiles) { - enableOpen = false; - enablePreview = false; - } - - const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - - if (!canPreviewFile(*m_PluginContainer, false, fileName)) { - enablePreview = false; - } - - if (!canHideFile(false, fileName)) { - enableHide = false; - } - - if (!canUnhideFile(false, fileName)) { - enableUnhide = false; - } - } else { - // this is a multiple selection, don't show open action so users don't open - // a thousand files - enableOpen = false; - enablePreview = false; - enableRename = false; - - if (m_FileSelection.size() < max_scan_for_context_menu) { - // if the number of selected items is low, checking them to accurately - // show the menu items is worth it - enableHide = false; - enableUnhide = false; - - for (const auto& index : m_FileSelection) { - const QString fileName = m_FileSystemModel->fileName(index); - - if (canHideFile(false, fileName)) { - enableHide = true; - } - - if (canUnhideFile(false, fileName)) { - enableUnhide = true; - } - - if (enableHide && enableUnhide) { - // found both, no need to check more - break; - } - } - } - } - - if (enableOpen) { - menu.addAction(m_OpenAction); - } - - if (enablePreview) { - menu.addAction(m_PreviewAction); - } - - if (enableRename) { - menu.addAction(m_RenameAction); - } - - if (enableDelete) { - menu.addAction(m_DeleteAction); - } - - if (enableHide) { - menu.addAction(m_HideAction); - } - - if (enableUnhide) { - menu.addAction(m_UnhideAction); - } - } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); - } - - menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); -} - void ModInfoDialog::on_nextButton_clicked() { int currentTab = ui->tabWidget->currentIndex(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 7b96d21a..b367f647 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -54,19 +54,6 @@ class TextEditor; class ModInfoDialogTab; -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; - } -}; - bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); @@ -145,12 +132,6 @@ signals: void originModified(int originID); private: - - void initFiletree(ModInfo::Ptr modInfo); - - void refreshLists(); - - QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); @@ -189,17 +170,9 @@ private: OrganizerCore *m_OrganizerCore; PluginContainer *m_PluginContainer; - QFileSystemModel *m_FileSystemModel; QTreeView *m_FileTree; QModelIndexList m_FileSelection; - QAction *m_NewFolderAction; - QAction *m_OpenAction; - QAction *m_PreviewAction; - QAction *m_RenameAction; - QAction *m_DeleteAction; - QAction *m_HideAction; - QAction *m_UnhideAction; const MOShared::DirectoryEntry *m_Directory; MOShared::FilesOrigin *m_Origin; @@ -209,6 +182,7 @@ private: std::vector> createTabs(); + void refreshLists(); void refreshFiles(); void restoreTabState(const QByteArray &state); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 360ecc79..04282e81 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -20,7 +20,7 @@ QTabWidget::Rounded - 7 + 8 true @@ -1088,7 +1088,7 @@ p, li { white-space: pre-wrap; }
    - + Filetree @@ -1096,7 +1096,7 @@ p, li { white-space: pre-wrap; } - + 0 @@ -1124,7 +1124,7 @@ p, li { white-space: pre-wrap; } - + Qt::CustomContextMenu diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 4176ac3e..e7f189c2 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -24,6 +24,20 @@ 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; + } +}; + + class ConflictItem : public QTreeWidgetItem { public: diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 20362575..9c011163 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -115,18 +115,12 @@ public: private: struct Actions { - QAction* hide; - QAction* unhide; - QAction* open; - QAction* preview; - QMenu* gotoMenu; + QAction* hide = nullptr; + QAction* unhide = nullptr; + QAction* open = nullptr; + QAction* preview = nullptr; + QMenu* gotoMenu = nullptr; std::vector gotoActions; - - Actions() : - hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), - gotoMenu(nullptr) - { - } }; GeneralConflictsTab m_general; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp new file mode 100644 index 00000000..e277b04b --- /dev/null +++ b/src/modinfodialogfiletree.cpp @@ -0,0 +1,420 @@ +#include "modinfodialogfiletree.h" +#include "ui_modinfodialog.h" +#include "organizercore.h" +#include +#include + +using MOBase::reportError; +namespace shell = MOBase::shell; + +FileTreeTab::FileTreeTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui), m_fs(nullptr) +{ + m_fs = new QFileSystemModel(this); + m_fs->setReadOnly(false); + ui->fileTree1->setModel(m_fs); + ui->fileTree1->setColumnWidth(0, 300); + + m_actions.newFolder = new QAction(tr("&New Folder"), ui->fileTree1); + m_actions.open = new QAction(tr("&Open"), ui->fileTree1); + m_actions.preview = new QAction(tr("&Preview"), ui->fileTree1); + m_actions.rename = new QAction(tr("&Rename"), ui->fileTree1); + m_actions.del = new QAction(tr("&Delete"), ui->fileTree1); + m_actions.hide = new QAction(tr("&Hide"), ui->fileTree1); + m_actions.unhide = new QAction(tr("&Unhide"), ui->fileTree1); + + connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); + connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); + connect(m_actions.preview, &QAction::triggered, [&]{ onPreview(); }); + connect(m_actions.rename, &QAction::triggered, [&]{ onRename(); }); + connect(m_actions.del, &QAction::triggered, [&]{ onDelete(); }); + connect(m_actions.hide, &QAction::triggered, [&]{ onHide(); }); + connect(m_actions.unhide, &QAction::triggered, [&]{ onUnhide(); }); +} + +void FileTreeTab::clear() +{ + m_fs->setRootPath({}); + //ui->fileTree1-> +} + +void FileTreeTab::update() +{ + const auto rootPath = mod()->absolutePath(); + + m_fs->setRootPath(rootPath); + ui->fileTree1->setRootIndex(m_fs->index(rootPath)); +} + +QModelIndex FileTreeTab::singleSelection() const +{ + const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + if (rows.size() != 1) { + return {}; + } + + return rows[0]; +} + +void FileTreeTab::onCreateDirectory() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + QModelIndex index = m_fs->isDir(selection) ? selection : selection.parent(); + index = index.sibling(index.row(), 0); + + QString name = tr("New Folder"); + QString path = m_fs->filePath(index).append("/"); + + QModelIndex existingIndex = m_fs->index(path + name); + int suffix = 1; + while (existingIndex.isValid()) { + name = tr("New Folder") + QString::number(suffix++); + existingIndex = m_fs->index(path + name); + } + + QModelIndex newIndex = m_fs->mkdir(index, name); + if (!newIndex.isValid()) { + reportError(tr("Failed to create \"%1\"").arg(name)); + return; + } + + ui->fileTree1->setCurrentIndex(newIndex); + ui->fileTree1->edit(newIndex); +} + +void FileTreeTab::onOpen() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + shell::OpenFile(m_fs->filePath(selection)); +} + +void FileTreeTab::onPreview() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + core().previewFile(parentWidget(), mod()->name(), m_fs->filePath(selection)); +} + +void FileTreeTab::onRename() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + QModelIndex index = selection.sibling(selection.row(), 0); + if (!index.isValid() || m_fs->isReadOnly()) { + return; + } + + ui->fileTree1->edit(index); +} + +void FileTreeTab::onDelete() +{ + const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + if (rows.count() == 0) { + return; + } + + QString message; + + if (rows.count() == 1) { + QString fileName = m_fs->fileName(rows[0]); + message = tr("Are sure you want to delete \"%1\"?").arg(fileName); + } else { + message = tr("Are sure you want to delete the selected files?"); + } + + if (QMessageBox::question(parentWidget(), tr("Confirm"), message) != QMessageBox::Yes) { + return; + } + + foreach(QModelIndex index, m_FileSelection) { + deleteFile(index); + } +} + + +bool FileTreeTab::recursiveDelete(const QModelIndex &index) +{ + for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { + QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); + if (m_FileSystemModel->isDir(childIndex)) { + if (!recursiveDelete(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } + } + if (!m_FileSystemModel->remove(index)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + return false; + } + return true; +} + + +void ModInfoDialog::on_openInExplorerButton_clicked() +{ + shell::ExploreFile(m_ModInfo->absolutePath()); +} + +void ModInfoDialog::deleteFile(const QModelIndex &index) +{ + bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) + : m_FileSystemModel->remove(index); + if (!res) { + QString fileName = m_FileSystemModel->fileName(index); + reportError(tr("Failed to delete %1").arg(fileName)); + } +} + +void ModInfoDialog::delete_activated() +{ + if (ui->fileTree->hasFocus()) { + QItemSelectionModel *selection = ui->fileTree->selectionModel(); + + if (selection->hasSelection() && selection->selectedRows().count() >= 1) { + + if (selection->selectedRows().count() == 0) { + return; + } + else if (selection->selectedRows().count() == 1) { + QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + else { + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + foreach(QModelIndex index, selection->selectedRows()) { + deleteFile(index); + } + } + } +} + + + + + +void ModInfoDialog::hideTriggered() +{ + changeFiletreeVisibility(false); +} + + +void ModInfoDialog::unhideTriggered() +{ + changeFiletreeVisibility(true); +} + +void ModInfoDialog::changeFiletreeVisibility(bool visible) +{ + bool changed = false; + bool stop = false; + + qDebug().nospace() + << (visible ? "unhiding" : "hiding") << " " + << m_FileSelection.size() << " filetree files"; + + QFlags flags = + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + + if (m_FileSelection.size() > 1) { + flags |= FileRenamer::MULTIPLE; + } + + FileRenamer renamer(this, flags); + + for (const auto& index : m_FileSelection) { + if (stop) { + break; + } + + const QString path = m_FileSystemModel->filePath(index); + auto result = FileRenamer::RESULT_CANCEL; + + if (visible) { + if (!canUnhideFile(false, path)) { + qDebug().nospace() << "cannot unhide " << path << ", skipping"; + continue; + } + result = unhideFile(renamer, path); + } else { + if (!canHideFile(false, path)) { + qDebug().nospace() << "cannot hide " << path << ", skipping"; + continue; + } + result = hideFile(renamer, path); + } + + switch (result) { + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } + + case FileRenamer::RESULT_SKIP: { + // nop + break; + } + + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; + } + } + } + + qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; + + if (changed) { + qDebug().nospace() << "triggering refresh"; + if (m_Origin) { + emit originModified(m_Origin->getID()); + } + refreshLists(); + } +} + + + + +void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); + + QMenu menu(ui->fileTree); + + menu.addAction(m_NewFolderAction); + + if (selectionModel->hasSelection()) { + bool enableOpen = true; + bool enablePreview = true; + bool enableRename = true; + bool enableDelete = true; + bool enableHide = true; + bool enableUnhide = true; + + if (m_FileSelection.size() == 1) { + // single selection + + // only enable open action if a file is selected + bool hasFiles = false; + + foreach(QModelIndex idx, m_FileSelection) { + if (m_FileSystemModel->fileInfo(idx).isFile()) { + hasFiles = true; + break; + } + } + + if (!hasFiles) { + enableOpen = false; + enablePreview = false; + } + + const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + + if (!canPreviewFile(*m_PluginContainer, false, fileName)) { + enablePreview = false; + } + + if (!canHideFile(false, fileName)) { + enableHide = false; + } + + if (!canUnhideFile(false, fileName)) { + enableUnhide = false; + } + } else { + // this is a multiple selection, don't show open action so users don't open + // a thousand files + enableOpen = false; + enablePreview = false; + enableRename = false; + + if (m_FileSelection.size() < max_scan_for_context_menu) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; + + for (const auto& index : m_FileSelection) { + const QString fileName = m_FileSystemModel->fileName(index); + + if (canHideFile(false, fileName)) { + enableHide = true; + } + + if (canUnhideFile(false, fileName)) { + enableUnhide = true; + } + + if (enableHide && enableUnhide) { + // found both, no need to check more + break; + } + } + } + } + + if (enableOpen) { + menu.addAction(m_OpenAction); + } + + if (enablePreview) { + menu.addAction(m_PreviewAction); + } + + if (enableRename) { + menu.addAction(m_RenameAction); + } + + if (enableDelete) { + menu.addAction(m_DeleteAction); + } + + if (enableHide) { + menu.addAction(m_HideAction); + } + + if (enableUnhide) { + menu.addAction(m_UnhideAction); + } + } else { + m_FileSelection.clear(); + m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); + } + + menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); +} diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h new file mode 100644 index 00000000..e7708ab8 --- /dev/null +++ b/src/modinfodialogfiletree.h @@ -0,0 +1,39 @@ +#ifndef MODINFODIALOGFILETREE_H +#define MODINFODIALOGFILETREE_H + +#include "modinfodialogtab.h" + +class FileTreeTab : public ModInfoDialogTab +{ +public: + FileTreeTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + void clear() override; + void update() override; + +private: + struct Actions + { + QAction *newFolder = nullptr; + QAction *open = nullptr; + QAction *preview = nullptr; + QAction *rename = nullptr; + QAction *del = nullptr; + QAction *hide = nullptr; + QAction *unhide = nullptr; + }; + + QFileSystemModel* m_fs; + Actions m_actions; + + QModelIndex singleSelection() const; + void onCreateDirectory(); + void onOpen(); + void onPreview(); + void onRename(); + void onDelete(); +}; + +#endif // MODINFODIALOGFILETREE_H -- cgit v1.3.1 From 3edad124635291b5d07794ad088ff8840391257f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 06:00:17 -0400 Subject: finished splitting filetree tab forward delete shortcut to tabs --- src/mainwindow.cpp | 45 ++++---- src/modinfodialog.cpp | 32 +++--- src/modinfodialog.h | 42 +------- src/modinfodialog.ui | 13 ++- src/modinfodialogconflicts.cpp | 6 +- src/modinfodialogfiletree.cpp | 232 ++++++++++++++++++++--------------------- src/modinfodialogfiletree.h | 11 +- src/modinfodialogtab.cpp | 15 ++- src/modinfodialogtab.h | 3 +- 9 files changed, 197 insertions(+), 202 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8eb0838e..f9bfaafb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3222,34 +3222,37 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } } else { modInfo->saveMeta(); - ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), &m_OrganizerCore, &m_PluginContainer, this); - connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); + + 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); 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.openTab(tab); - } + //Open the tab first if we want to use the standard indexes of the tabs. + if (tab != -1) { + dialog.openTab(tab); + } - dialog.restoreState(m_OrganizerCore.settings()); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } + dialog.restoreState(m_OrganizerCore.settings()); + QSettings &settings = m_OrganizerCore.settings().directInterface(); + QString key = QString("geometry/%1").arg(dialog.objectName()); + if (settings.contains(key)) { + dialog.restoreGeometry(settings.value(key).toByteArray()); + } - //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; - } - } - } + //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()); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 6463a5a6..71e514b2 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -144,12 +144,9 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) +ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_PreviewAction(nullptr), - m_RenameAction(nullptr), m_DeleteAction(nullptr), m_HideAction(nullptr), - m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), - m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) + m_Origin(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) { ui->setupUi(this); @@ -173,13 +170,12 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_RootPath = modInfo->absolutePath(); - //TODO: No easy way to delegate links - //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); + auto* sc = new QShortcut(QKeySequence::Delete, this); + connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); - new QShortcut(QKeySequence::Delete, this, SLOT(delete_activated())); - - if (directory->originExists(ToWString(modInfo->name()))) { - m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); + auto* ds = m_OrganizerCore->directoryStructure(); + if (ds->originExists(ToWString(modInfo->name()))) { + m_Origin = &ds->getOriginByName(ToWString(modInfo->name())); if (m_Origin->isDisabled()) { m_Origin = nullptr; } @@ -335,17 +331,21 @@ QByteArray ModInfoDialog::saveTabState() const return result; } +void ModInfoDialog::onDeleteShortcut() +{ + for (auto& t : m_tabs) { + if (t->deleteRequested()) { + break; + } + } +} + void ModInfoDialog::refreshLists() { for (auto& tab : m_tabs) { tab->update(); } - refreshFiles(); -} - -void ModInfoDialog::refreshFiles() -{ if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index b367f647..49007c87 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -73,7 +73,6 @@ class ModInfoDialog : public MOBase::TutorableDialog Q_OBJECT public: - enum ETabs { TAB_TEXTFILES, TAB_INIFILES, @@ -87,14 +86,16 @@ public: }; public: - /** * @brief constructor * * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent = 0); + explicit ModInfoDialog( + ModInfo::Ptr modInfo, + bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, + QWidget *parent = 0); ~ModInfoDialog(); @@ -125,34 +126,17 @@ public: void restoreState(const Settings& s); signals: - void downloadRequest(const QString &link); void modOpen(const QString &modName, int tab); void modOpenNext(int tab=-1); void modOpenPrev(int tab=-1); void originModified(int originID); private: - bool recursiveDelete(const QModelIndex &index); - void deleteFile(const QModelIndex &index); - int tabIndex(const QString &tabId); private slots: - void delete_activated(); - - void createDirectoryTriggered(); - void openTriggered(); - void previewTriggered(); - void renameTriggered(); - void deleteTriggered(); - void hideTriggered(); - void unhideTriggered(); - - void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); void on_tabWidget_currentChanged(int index); - void on_fileTree_customContextMenuRequested(const QPoint &pos); - void on_nextButton_clicked(); void on_prevButton_clicked(); @@ -160,35 +144,19 @@ private: using FileEntry = MOShared::FileEntry; Ui::ModInfoDialog *ui; - ModInfo::Ptr m_ModInfo; - std::vector> m_tabs; - QString m_RootPath; - OrganizerCore *m_OrganizerCore; PluginContainer *m_PluginContainer; - - QTreeView *m_FileTree; - QModelIndexList m_FileSelection; - - - const MOShared::DirectoryEntry *m_Directory; MOShared::FilesOrigin *m_Origin; - std::map m_RealTabPos; - std::vector> createTabs(); - void refreshLists(); - void refreshFiles(); - void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; - - void changeFiletreeVisibility(bool visible); + void onDeleteShortcut(); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 04282e81..65b89621 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -1096,7 +1096,7 @@ p, li { white-space: pre-wrap; } - + 0 @@ -1124,7 +1124,7 @@ p, li { white-space: pre-wrap; } - + Qt::CustomContextMenu @@ -1161,6 +1161,9 @@ p, li { white-space: pre-wrap; } Previous + + false + @@ -1168,6 +1171,9 @@ p, li { white-space: pre-wrap; } Next + + false + @@ -1188,6 +1194,9 @@ p, li { white-space: pre-wrap; } Close + + false +
    diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index e7f189c2..adde27ca 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -6,8 +6,8 @@ using namespace MOShared; using namespace MOBase; -// if there are more than 50 selected items in the conflict tree or filetree, -// don't bother checking whether menu items apply to them, just show all of them +// if there are more than 50 selected items in the conflict tree, don't bother +// checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; @@ -248,7 +248,7 @@ void ConflictsTab::changeItemsVisibility( qDebug().nospace() << "triggering refresh"; if (origin()) { - emit originModified(origin()->getID()); + emitOriginModified(); } update(); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index e277b04b..b73a9e24 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -1,12 +1,18 @@ #include "modinfodialogfiletree.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include "organizercore.h" +#include "filerenamer.h" #include #include using MOBase::reportError; namespace shell = MOBase::shell; +// if there are more than 50 selected items in the filetree, don't bother +// checking whether menu items apply to them, just show all of them +const int max_scan_for_context_menu = 50; + FileTreeTab::FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui) @@ -14,16 +20,16 @@ FileTreeTab::FileTreeTab( { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); - ui->fileTree1->setModel(m_fs); - ui->fileTree1->setColumnWidth(0, 300); + ui->filetree->setModel(m_fs); + ui->filetree->setColumnWidth(0, 300); - m_actions.newFolder = new QAction(tr("&New Folder"), ui->fileTree1); - m_actions.open = new QAction(tr("&Open"), ui->fileTree1); - m_actions.preview = new QAction(tr("&Preview"), ui->fileTree1); - m_actions.rename = new QAction(tr("&Rename"), ui->fileTree1); - m_actions.del = new QAction(tr("&Delete"), ui->fileTree1); - m_actions.hide = new QAction(tr("&Hide"), ui->fileTree1); - m_actions.unhide = new QAction(tr("&Unhide"), ui->fileTree1); + m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); + m_actions.open = new QAction(tr("&Open"), ui->filetree); + m_actions.preview = new QAction(tr("&Preview"), ui->filetree); + m_actions.rename = new QAction(tr("&Rename"), ui->filetree); + m_actions.del = new QAction(tr("&Delete"), ui->filetree); + m_actions.hide = new QAction(tr("&Hide"), ui->filetree); + m_actions.unhide = new QAction(tr("&Unhide"), ui->filetree); connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); @@ -32,12 +38,18 @@ FileTreeTab::FileTreeTab( connect(m_actions.del, &QAction::triggered, [&]{ onDelete(); }); connect(m_actions.hide, &QAction::triggered, [&]{ onHide(); }); connect(m_actions.unhide, &QAction::triggered, [&]{ onUnhide(); }); + + connect(ui->openInExplorer, &QToolButton::clicked, [&]{ onOpenInExplorer(); }); + + connect( + ui->filetree, &QTreeView::customContextMenuRequested, + [&](const QPoint& pos){ onContextMenu(pos); }); } void FileTreeTab::clear() { m_fs->setRootPath({}); - //ui->fileTree1-> + //ui->filetree-> } void FileTreeTab::update() @@ -45,12 +57,22 @@ void FileTreeTab::update() const auto rootPath = mod()->absolutePath(); m_fs->setRootPath(rootPath); - ui->fileTree1->setRootIndex(m_fs->index(rootPath)); + ui->filetree->setRootIndex(m_fs->index(rootPath)); +} + +bool FileTreeTab::deleteRequested() +{ + if (!ui->filetree->hasFocus()) { + return false; + } + + onDelete(); + return true; } QModelIndex FileTreeTab::singleSelection() const { - const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + const auto rows = ui->filetree->selectionModel()->selectedRows(); if (rows.size() != 1) { return {}; } @@ -60,11 +82,19 @@ QModelIndex FileTreeTab::singleSelection() const void FileTreeTab::onCreateDirectory() { - auto selection = singleSelection(); - if (!selection.isValid()) { + const auto selectedRows = ui->filetree->selectionModel()->selectedRows(); + if (selectedRows.size() > 1) { return; } + QModelIndex selection; + + if (selectedRows.size() == 0) { + selection = m_fs->index(m_fs->rootPath(), 0); + } else { + selection = selectedRows[0]; + } + QModelIndex index = m_fs->isDir(selection) ? selection : selection.parent(); index = index.sibling(index.row(), 0); @@ -84,8 +114,8 @@ void FileTreeTab::onCreateDirectory() return; } - ui->fileTree1->setCurrentIndex(newIndex); - ui->fileTree1->edit(newIndex); + ui->filetree->setCurrentIndex(newIndex); + ui->filetree->edit(newIndex); } void FileTreeTab::onOpen() @@ -120,12 +150,12 @@ void FileTreeTab::onRename() return; } - ui->fileTree1->edit(index); + ui->filetree->edit(index); } void FileTreeTab::onDelete() { - const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + const auto rows = ui->filetree->selectionModel()->selectedRows(); if (rows.count() == 0) { return; } @@ -143,121 +173,95 @@ void FileTreeTab::onDelete() return; } - foreach(QModelIndex index, m_FileSelection) { + for (const auto& index : rows) { deleteFile(index); } } - -bool FileTreeTab::recursiveDelete(const QModelIndex &index) +void FileTreeTab::onHide() { - for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { - QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); - if (m_FileSystemModel->isDir(childIndex)) { - if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); - return false; - } - return true; + changeVisibility(false); } +void FileTreeTab::onUnhide() +{ + changeVisibility(true); +} -void ModInfoDialog::on_openInExplorerButton_clicked() +void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(m_ModInfo->absolutePath()); + shell::ExploreFile(mod()->absolutePath()); } -void ModInfoDialog::deleteFile(const QModelIndex &index) +bool FileTreeTab::deleteFile(const QModelIndex& index) { - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) - : m_FileSystemModel->remove(index); + bool res = false; + + if (m_fs->isDir(index)) { + res = deleteFileRecursive(index); + } else { + res = m_fs->remove(index); + } + if (!res) { - QString fileName = m_FileSystemModel->fileName(index); - reportError(tr("Failed to delete %1").arg(fileName)); + reportError(tr("Failed to delete %1").arg(m_fs->fileName(index))); } + + return res; } -void ModInfoDialog::delete_activated() +bool FileTreeTab::deleteFileRecursive(const QModelIndex& parent) { - if (ui->fileTree->hasFocus()) { - QItemSelectionModel *selection = ui->fileTree->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() >= 1) { + for (int row = 0; rowrowCount(parent); ++row) { + QModelIndex index = m_fs->index(row, 0, parent); - if (selection->selectedRows().count() == 0) { - return; - } - else if (selection->selectedRows().count() == 1) { - QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } + if (m_fs->isDir(index)) { + if (!deleteFileRecursive(index)) { + qCritical() << "failed to delete" << m_fs->fileName(index); + return false; } - - foreach(QModelIndex index, selection->selectedRows()) { - deleteFile(index); + } else { + if (!m_fs->remove(index)) { + qCritical() << "failed to delete", m_fs->fileName(index); + return false; } } } -} - - - + if (!m_fs->remove(parent)) { + qCritical() << "failed to delete" << m_fs->fileName(parent); + return false; + } -void ModInfoDialog::hideTriggered() -{ - changeFiletreeVisibility(false); + return true; } - -void ModInfoDialog::unhideTriggered() +void FileTreeTab::changeVisibility(bool visible) { - changeFiletreeVisibility(true); -} + const auto selection = ui->filetree->selectionModel()->selectedRows(); -void ModInfoDialog::changeFiletreeVisibility(bool visible) -{ bool changed = false; bool stop = false; qDebug().nospace() << (visible ? "unhiding" : "hiding") << " " - << m_FileSelection.size() << " filetree files"; + << selection.size() << " filetree files"; QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - if (m_FileSelection.size() > 1) { + if (selection.size() > 1) { flags |= FileRenamer::MULTIPLE; } - FileRenamer renamer(this, flags); + FileRenamer renamer(parentWidget(), flags); - for (const auto& index : m_FileSelection) { + for (const auto& index : selection) { if (stop) { break; } - const QString path = m_FileSystemModel->filePath(index); + const QString path = m_fs->filePath(index); auto result = FileRenamer::RESULT_CANCEL; if (visible) { @@ -298,26 +302,21 @@ void ModInfoDialog::changeFiletreeVisibility(bool visible) if (changed) { qDebug().nospace() << "triggering refresh"; - if (m_Origin) { - emit originModified(m_Origin->getID()); + if (origin()) { + emitOriginModified(); } - refreshLists(); } } - - - -void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) +void FileTreeTab::onContextMenu(const QPoint &pos) { - QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); + const auto selection = ui->filetree->selectionModel()->selectedRows(); - QMenu menu(ui->fileTree); + QMenu menu(ui->filetree); - menu.addAction(m_NewFolderAction); + menu.addAction(m_actions.newFolder); - if (selectionModel->hasSelection()) { + if (selection.size() > 0) { bool enableOpen = true; bool enablePreview = true; bool enableRename = true; @@ -325,14 +324,14 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) bool enableHide = true; bool enableUnhide = true; - if (m_FileSelection.size() == 1) { + if (selection.size() == 1) { // single selection // only enable open action if a file is selected bool hasFiles = false; - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { + for (auto index : selection) { + if (m_fs->fileInfo(index).isFile()) { hasFiles = true; break; } @@ -343,9 +342,9 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) enablePreview = false; } - const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + const QString fileName = m_fs->fileName(selection[0]); - if (!canPreviewFile(*m_PluginContainer, false, fileName)) { + if (!canPreviewFile(plugin(), false, fileName)) { enablePreview = false; } @@ -363,14 +362,14 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) enablePreview = false; enableRename = false; - if (m_FileSelection.size() < max_scan_for_context_menu) { + if (selection.size() < max_scan_for_context_menu) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it enableHide = false; enableUnhide = false; - for (const auto& index : m_FileSelection) { - const QString fileName = m_FileSystemModel->fileName(index); + for (const auto& index : selection) { + const QString fileName = m_fs->fileName(index); if (canHideFile(false, fileName)) { enableHide = true; @@ -389,32 +388,29 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) } if (enableOpen) { - menu.addAction(m_OpenAction); + menu.addAction(m_actions.open); } if (enablePreview) { - menu.addAction(m_PreviewAction); + menu.addAction(m_actions.preview); } if (enableRename) { - menu.addAction(m_RenameAction); + menu.addAction(m_actions.rename); } if (enableDelete) { - menu.addAction(m_DeleteAction); + menu.addAction(m_actions.del); } if (enableHide) { - menu.addAction(m_HideAction); + menu.addAction(m_actions.hide); } if (enableUnhide) { - menu.addAction(m_UnhideAction); + menu.addAction(m_actions.unhide); } - } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); } - menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); + menu.exec(ui->filetree->viewport()->mapToGlobal(pos)); } diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index e7708ab8..dcc096fe 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -12,6 +12,7 @@ public: void clear() override; void update() override; + bool deleteRequested() override; private: struct Actions @@ -28,12 +29,20 @@ private: QFileSystemModel* m_fs; Actions m_actions; - QModelIndex singleSelection() const; void onCreateDirectory(); void onOpen(); void onPreview(); void onRename(); void onDelete(); + void onHide(); + void onUnhide(); + void onOpenInExplorer(); + void onContextMenu(const QPoint &pos); + + QModelIndex singleSelection() const; + bool deleteFile(const QModelIndex& index); + bool deleteFileRecursive(const QModelIndex& index); + void changeVisibility(bool visible); }; #endif // MODINFODIALOGFILETREE_H diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index b59f4dcc..58745220 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,6 +1,7 @@ #include "modinfodialogtab.h" #include "ui_modinfodialog.h" #include "texteditor.h" +#include "directoryentry.h" ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, @@ -9,6 +10,11 @@ ModInfoDialogTab::ModInfoDialogTab( { } +void ModInfoDialogTab::update() +{ + // no-op +} + bool ModInfoDialogTab::feedFile(const QString&, const QString&) { // no-op @@ -30,9 +36,10 @@ void ModInfoDialogTab::restoreState(const Settings& s) // no-op } -void ModInfoDialogTab::update() +bool ModInfoDialogTab::deleteRequested() { // no-op + return false; } void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) @@ -66,9 +73,11 @@ QWidget* ModInfoDialogTab::parentWidget() return m_parent; } -void ModInfoDialogTab::emitOriginModified(int originID) +void ModInfoDialogTab::emitOriginModified() { - emit originModified(originID); + if (m_origin) { + emit originModified(m_origin->getID()); + } } void ModInfoDialogTab::emitModOpen(QString name) diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 60371954..0dc977a8 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -27,6 +27,7 @@ public: virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); + virtual bool deleteRequested(); virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); @@ -49,7 +50,7 @@ protected: QWidget* parentWidget(); - void emitOriginModified(int originID); + void emitOriginModified(); void emitModOpen(QString name); private: -- cgit v1.3.1 From 949e451379d63fe4c6bff82a7a059c6792fbebb5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 06:48:35 -0400 Subject: added missing icons now passing tab index to allow enabling/disabling depending on mod type modinfodialog cleanup --- src/modinfodialog.cpp | 173 +++++++++++---------------------- src/modinfodialog.h | 48 +++------ src/modinfodialog.ui | 5 +- src/modinfodialogcategories.cpp | 9 +- src/modinfodialogcategories.h | 3 +- src/modinfodialogconflicts.cpp | 11 ++- src/modinfodialogconflicts.h | 4 +- src/modinfodialogesps.cpp | 4 +- src/modinfodialogesps.h | 2 +- src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogfiletree.h | 2 +- src/modinfodialogimages.cpp | 5 +- src/modinfodialogimages.h | 2 +- src/modinfodialognexus.cpp | 4 +- src/modinfodialognexus.h | 2 +- src/modinfodialogtab.cpp | 29 +++++- src/modinfodialogtab.h | 11 ++- src/modinfodialogtextfiles.cpp | 12 +-- src/modinfodialogtextfiles.h | 6 +- src/resources.qrc | 210 ++++++++++++++++++++-------------------- 20 files changed, 258 insertions(+), 288 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 71e514b2..be7d4aa4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -19,24 +19,8 @@ along with Mod Organizer. If not, see . #include "modinfodialog.h" #include "ui_modinfodialog.h" -#include "mainwindow.h" - -#include "modidlineedit.h" -#include "iplugingame.h" -#include "nexusinterface.h" -#include "report.h" -#include "utility.h" -#include "messagedialog.h" -#include "bbcode.h" -#include "questionboxmemory.h" -#include "settings.h" -#include "categories.h" +#include "plugincontainer.h" #include "organizercore.h" -#include "pluginlistsortproxy.h" -#include "previewgenerator.h" -#include "previewdialog.h" -#include "texteditor.h" - #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" @@ -45,23 +29,6 @@ along with Mod Organizer. If not, see . #include "modinfodialognexus.h" #include "modinfodialogfiletree.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - - using namespace MOBase; using namespace MOShared; @@ -144,13 +111,32 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) - : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_Origin(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) +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) { 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; i(i)); }); - } - this->setWindowTitle(modInfo->name()); - this->setWindowModality(Qt::WindowModal); + bool enabled = true; - m_RootPath = modInfo->absolutePath(); - - auto* sc = new QShortcut(QKeySequence::Delete, this); - connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); - - auto* ds = m_OrganizerCore->directoryStructure(); - if (ds->originExists(ToWString(modInfo->name()))) { - m_Origin = &ds->getOriginByName(ToWString(modInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = nullptr; + if (unmanaged) { + enabled = m_tabs[i]->canHandleUnmanaged(); + } else if (m_ModInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { + enabled = m_tabs[i]->canHandleSeparators(); } - } - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) - { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, false); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, true); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - } - else if (unmanaged) - { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, false); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - } else { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); - ui->tabWidget->setTabEnabled(TAB_INIFILES, true); - ui->tabWidget->setTabEnabled(TAB_IMAGES, true); - ui->tabWidget->setTabEnabled(TAB_ESPS, true); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); - ui->tabWidget->setTabEnabled(TAB_NEXUS, true); - ui->tabWidget->setTabEnabled(TAB_NOTES, true); - ui->tabWidget->setTabEnabled(TAB_FILETREE, true); - } + ui->tabWidget->setTabEnabled(static_cast(i), enabled); - // activate first enabled tab - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->isTabEnabled(i)) { - ui->tabWidget->setCurrentIndex(i); - break; + if (!tabSelected && enabled) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + tabSelected = true; } } @@ -234,23 +176,21 @@ ModInfoDialog::~ModInfoDialog() delete ui; } -template -std::vector> createTabsImpl( - OrganizerCore& oc, PluginContainer& plugin, - ModInfoDialog* self, Ui::ModInfoDialog* ui) +std::vector> ModInfoDialog::createTabs() { std::vector> v; - (v.push_back(std::make_unique(oc, plugin, self, ui)), ...); - return v; -} + v.push_back(createTab(TAB_TEXTFILES)); + v.push_back(createTab(TAB_INIFILES)); + v.push_back(createTab(TAB_IMAGES)); + v.push_back(createTab(TAB_ESPS)); + v.push_back(createTab(TAB_CONFLICTS)); + v.push_back(createTab(TAB_CATEGORIES)); + v.push_back(createTab(TAB_NEXUS)); + v.push_back(createTab(TAB_NOTES)); + v.push_back(createTab(TAB_FILETREE)); -std::vector> ModInfoDialog::createTabs() -{ - return createTabsImpl< - TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, - ConflictsTab, CategoriesTab, NexusTab, NotesTab, FileTreeTab>( - *m_OrganizerCore, *m_PluginContainer, this, ui); + return v; } int ModInfoDialog::exec() @@ -259,16 +199,6 @@ int ModInfoDialog::exec() return TutorableDialog::exec(); } -int ModInfoDialog::tabIndex(const QString &tabId) -{ - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->widget(i)->objectName() == tabId) { - return i; - } - } - return -1; -} - void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); @@ -309,7 +239,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) } // then actually move the tabs - QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad + QTabBar *tabBar = ui->tabWidget->tabBar(); ui->tabWidget->blockSignals(true); for (int newPos = 0; newPos < count; ++newPos) { QString tabId = tabIds.at(newPos); @@ -331,6 +261,16 @@ QByteArray ModInfoDialog::saveTabState() const return result; } +int ModInfoDialog::tabIndex(const QString& tabId) +{ + for (int i = 0; i < ui->tabWidget->count(); ++i) { + if (ui->tabWidget->widget(i)->objectName() == tabId) { + return i; + } + } + return -1; +} + void ModInfoDialog::onDeleteShortcut() { for (auto& t : m_tabs) { @@ -373,9 +313,8 @@ void ModInfoDialog::on_closeButton_clicked() void ModInfoDialog::openTab(int tab) { - QTabWidget *tabWidget = findChild("tabWidget"); - if (tabWidget->isTabEnabled(tab)) { - tabWidget->setCurrentIndex(tab); + if (ui->tabWidget->isTabEnabled(tab)) { + ui->tabWidget->setCurrentIndex(tab); } } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 49007c87..020e7958 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -23,36 +23,15 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "tutorabledialog.h" -#include "plugincontainer.h" -#include "organizercore.h" -#include "filterwidget.h" #include "filerenamer.h" -#include "expanderwidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Ui { - class ModInfoDialog; -} - -class QFileSystemModel; -class QTreeView; -class CategoryFactory; -class TextEditor; -class ModInfoDialogTab; +namespace Ui { class ModInfoDialog; } +namespace MOShared { class FilesOrigin; } + +class PluginContainer; +class OrganizerCore; +class Settings; +class ModInfoDialogTab; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); @@ -131,9 +110,6 @@ signals: void modOpenPrev(int tab=-1); void originModified(int originID); -private: - int tabIndex(const QString &tabId); - private slots: void on_closeButton_clicked(); void on_tabWidget_currentChanged(int index); @@ -141,8 +117,6 @@ private slots: void on_prevButton_clicked(); private: - using FileEntry = MOShared::FileEntry; - Ui::ModInfoDialog *ui; ModInfo::Ptr m_ModInfo; std::vector> m_tabs; @@ -157,6 +131,14 @@ private: void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; void onDeleteShortcut(); + int tabIndex(const QString &tabId); + + template + std::unique_ptr createTab(int index) + { + return std::make_unique( + *m_OrganizerCore, *m_PluginContainer, this, ui, index); + } }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 65b89621..93550de3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -20,7 +20,7 @@ QTabWidget::Rounded - 8 + 0 true @@ -1148,6 +1148,9 @@ p, li { white-space: pre-wrap; } QAbstractItemView::ExtendedSelection + + true +
    diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 69c7c6b5..321c22b8 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -5,8 +5,8 @@ CategoriesTab::CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { connect( ui->categories, &QTreeWidget::itemChanged, @@ -35,6 +35,11 @@ void CategoriesTab::update() updatePrimary(); } +bool CategoriesTab::canHandleSeparators() const +{ + return true; +} + void CategoriesTab::add( const CategoryFactory &factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel) diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 76426a5d..29d0b2a5 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -7,10 +7,11 @@ class CategoriesTab : public ModInfoDialogTab public: CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; + bool canHandleSeparators() const override; private: void add( diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index adde27ca..15bb7ed4 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -2,6 +2,8 @@ #include "ui_modinfodialog.h" #include "modinfodialog.h" #include "utility.h" +#include "settings.h" +#include "organizercore.h" using namespace MOShared; using namespace MOBase; @@ -131,8 +133,8 @@ public: ConflictsTab::ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) : - ModInfoDialogTab(oc, plugin, parent, ui), + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ModInfoDialogTab(oc, plugin, parent, ui, index), m_general(this, ui, oc), m_advanced(this, ui, oc) { connect( @@ -174,6 +176,11 @@ void ConflictsTab::restoreState(const Settings& s) m_advanced.restoreState(s); } +bool ConflictsTab::canHandleUnmanaged() const +{ + return true; +} + void ConflictsTab::changeItemsVisibility( const QList& items, bool visible) { diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 9c011163..a05682ba 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -5,6 +5,7 @@ #include "expanderwidget.h" #include "filterwidget.h" #include "directoryentry.h" +#include class ConflictsTab; class OrganizerCore; @@ -96,13 +97,14 @@ class ConflictsTab : public ModInfoDialogTab public: ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void update() override; void clear() override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; + bool canHandleUnmanaged() const override; void openItems(const QList& items); void previewItems(const QList& items); diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index ea7eb3b0..dd4fff0b 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -124,8 +124,8 @@ private: ESPsTab::ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index e1a7a4f7..d8c8997e 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -12,7 +12,7 @@ class ESPsTab : public ModInfoDialogTab public: ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index b73a9e24..3e233ccc 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -15,8 +15,8 @@ const int max_scan_for_context_menu = 50; FileTreeTab::FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_fs(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index dcc096fe..d0c36edc 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -8,7 +8,7 @@ class FileTreeTab : public ModInfoDialogTab public: FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1c7dcc1f..332a0984 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -82,8 +82,9 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_image(new ScalableImage) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ModInfoDialogTab(oc, plugin, parent, ui, index), + m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); ui->imagesThumbnails->setLayout(new QVBoxLayout); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 7853935a..689b8e93 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -36,7 +36,7 @@ class ImagesTab : public ModInfoDialogTab public: ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 55b55439..9d51871c 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -9,8 +9,8 @@ NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_requestStarted(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 2e328c6d..7fe10171 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -35,7 +35,7 @@ class NexusTab : public ModInfoDialogTab public: NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 58745220..1b7fadbb 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -5,8 +5,9 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), m_origin(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), + m_origin(nullptr), m_tabIndex(index) { } @@ -42,6 +43,16 @@ bool ModInfoDialogTab::deleteRequested() return false; } +bool ModInfoDialogTab::canHandleSeparators() const +{ + return false; +} + +bool ModInfoDialogTab::canHandleUnmanaged() const +{ + return false; +} + void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; @@ -58,6 +69,11 @@ MOShared::FilesOrigin* ModInfoDialogTab::origin() const return m_origin; } +int ModInfoDialogTab::tabIndex() const +{ + return m_tabIndex; +} + OrganizerCore& ModInfoDialogTab::core() { return m_core; @@ -88,8 +104,8 @@ void ModInfoDialogTab::emitModOpen(QString name) NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { connect(ui->commentsEdit, &QLineEdit::editingFinished, [&]{ onComments(); }); connect(ui->notesEdit, &HTMLEditor::editingFinished, [&]{ onNotes(); }); @@ -107,6 +123,11 @@ void NotesTab::update() ui->notesEdit->setText(mod()->notes()); } +bool NotesTab::canHandleSeparators() const +{ + return true; +} + void NotesTab::onComments() { mod()->setComments(ui->commentsEdit->text()); diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 0dc977a8..1f99344f 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -29,11 +29,16 @@ public: virtual void restoreState(const Settings& s); virtual bool deleteRequested(); + virtual bool canHandleSeparators() const; + virtual bool canHandleUnmanaged() const; + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); ModInfo::Ptr mod() const; MOShared::FilesOrigin* origin() const; + int tabIndex() const; + signals: void originModified(int originID); void modOpen(QString name); @@ -43,7 +48,7 @@ protected: ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); OrganizerCore& core(); PluginContainer& plugin(); @@ -59,6 +64,7 @@ private: QWidget* m_parent; ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; + int m_tabIndex; }; @@ -67,10 +73,11 @@ class NotesTab : public ModInfoDialogTab public: NotesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; + bool canHandleSeparators() const override; private: void onComments(); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index f48557b0..fddfafba 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -23,9 +23,9 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, + QWidget* parent, Ui::ModInfoDialog* ui, int index, QListWidget* list, QSplitter* sp, TextEditor* e) - : ModInfoDialogTab(oc, plugin, parent, ui), m_list(list), m_editor(e) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -113,9 +113,9 @@ void GenericFilesTab::select(FileListItem* item) TextFilesTab::TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : GenericFilesTab( - oc, plugin, parent, ui, + oc, plugin, parent, ui, index, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -137,9 +137,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c IniFilesTab::IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : GenericFilesTab( - oc, plugin, parent, ui, + oc, plugin, parent, ui, index, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 0dc5ec89..f618a6bb 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -23,7 +23,7 @@ protected: GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, + QWidget* parent, Ui::ModInfoDialog* ui, int index, QListWidget* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -39,7 +39,7 @@ class TextFilesTab : public GenericFilesTab public: TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -51,7 +51,7 @@ class IniFilesTab : public GenericFilesTab public: IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; diff --git a/src/resources.qrc b/src/resources.qrc index 8645b27e..6fc33293 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,106 +1,108 @@ - - resources/help-browser.png - resources/list-add.png - resources/document-save.png - resources/edit-find-replace.png - resources/go-jump.png - resources/media-playback-start.png - resources/process-stop.png - resources/system-search.png - resources/view-refresh.png - resources/system-installer.png - resources/start-here.png - resources/list-remove.png - resources/document-properties.png - resources/go-up.png - resources/go-down.png - resources/switch-instance-icon.png - resources/contact-new.png - resources/preferences-system.png - resources/application-x-executable.png - resources/dialog-information.png - resources/emblem-readonly.png - resources/go-next_16.png - resources/go-previous_16.png - resources/view-refresh_16.png - resources/software-update-available.png - resources/emblem-important.png - resources/check.png - resources/dialog-warning.png - resources/symbol-backup.png - resources/applications-accessories.png - resources/emblem-unreadable.png - resources/internet-web-browser.png - resources/system-software-update.png - resources/help-browser_32.png - resources/system-installer.png - resources/function.png - resources/plugins.png - resources/edit-clear.png - resources/dynamic-blue-right.png - resources/icon-favorite.png - resources/emblem-favorite.png - resources/error.png - resources/show.png - splash.png - resources/conflict-mixed.png - resources/conflict-overwrite.png - resources/conflict-overwritten.png - resources/conflict-redundant.png - resources/conflict-mixed-blue.png - resources/conflict-overwrite-blue.png - resources/red-archive-conflict-loser.png - resources/accessories-text-editor.png - resources/x-office-calendar.png - resources/dialog-warning_16.png - resources/mail-attachment.png - resources/document-save_32.png - resources/edit-undo.png - resources/arrange-boxes.png - resources/badge_1.png - resources/badge_2.png - resources/badge_3.png - resources/badge_4.png - resources/badge_5.png - resources/badge_6.png - resources/badge_7.png - resources/badge_8.png - resources/badge_9.png - resources/badge_more.png - resources/status_active.png - resources/status_awaiting.png - resources/status_inactive.png - resources/mo_icon.png - resources/package.png - resources/switch-instance-icon.png - resources/open-Folder-Icon.png - resources/multiply-red.png - resources/archive-conflict-loser.png - resources/archive-conflict-mixed.png - resources/archive-conflict-neutral.png - resources/archive-conflict-winner.png - resources/game-warning.png - resources/game-warning-16.png - resources/tracked.png - - - resources/contents/jigsaw-piece.png - resources/contents/hand-of-god.png - resources/contents/empty-chessboard.png - resources/contents/double-quaver.png - resources/contents/lyre.png - resources/contents/usable.png - resources/contents/checkbox-tree.png - resources/contents/tinker.png - resources/contents/breastplate.png - resources/contents/conversation.png - resources/contents/locked-chest.png - resources/contents/config.png - resources/contents/feather-and-scroll.png - resources/contents/xedit.png - - - qt.conf - + + resources/save.svg + resources/word-wrap.svg + resources/help-browser.png + resources/list-add.png + resources/document-save.png + resources/edit-find-replace.png + resources/go-jump.png + resources/media-playback-start.png + resources/process-stop.png + resources/system-search.png + resources/view-refresh.png + resources/system-installer.png + resources/start-here.png + resources/list-remove.png + resources/document-properties.png + resources/go-up.png + resources/go-down.png + resources/switch-instance-icon.png + resources/contact-new.png + resources/preferences-system.png + resources/application-x-executable.png + resources/dialog-information.png + resources/emblem-readonly.png + resources/go-next_16.png + resources/go-previous_16.png + resources/view-refresh_16.png + resources/software-update-available.png + resources/emblem-important.png + resources/check.png + resources/dialog-warning.png + resources/symbol-backup.png + resources/applications-accessories.png + resources/emblem-unreadable.png + resources/internet-web-browser.png + resources/system-software-update.png + resources/help-browser_32.png + resources/system-installer.png + resources/function.png + resources/plugins.png + resources/edit-clear.png + resources/dynamic-blue-right.png + resources/icon-favorite.png + resources/emblem-favorite.png + resources/error.png + resources/show.png + splash.png + resources/conflict-mixed.png + resources/conflict-overwrite.png + resources/conflict-overwritten.png + resources/conflict-redundant.png + resources/conflict-mixed-blue.png + resources/conflict-overwrite-blue.png + resources/red-archive-conflict-loser.png + resources/accessories-text-editor.png + resources/x-office-calendar.png + resources/dialog-warning_16.png + resources/mail-attachment.png + resources/document-save_32.png + resources/edit-undo.png + resources/arrange-boxes.png + resources/badge_1.png + resources/badge_2.png + resources/badge_3.png + resources/badge_4.png + resources/badge_5.png + resources/badge_6.png + resources/badge_7.png + resources/badge_8.png + resources/badge_9.png + resources/badge_more.png + resources/status_active.png + resources/status_awaiting.png + resources/status_inactive.png + resources/mo_icon.png + resources/package.png + resources/switch-instance-icon.png + resources/open-Folder-Icon.png + resources/multiply-red.png + resources/archive-conflict-loser.png + resources/archive-conflict-mixed.png + resources/archive-conflict-neutral.png + resources/archive-conflict-winner.png + resources/game-warning.png + resources/game-warning-16.png + resources/tracked.png + + + resources/contents/jigsaw-piece.png + resources/contents/hand-of-god.png + resources/contents/empty-chessboard.png + resources/contents/double-quaver.png + resources/contents/lyre.png + resources/contents/usable.png + resources/contents/checkbox-tree.png + resources/contents/tinker.png + resources/contents/breastplate.png + resources/contents/conversation.png + resources/contents/locked-chest.png + resources/contents/config.png + resources/contents/feather-and-scroll.png + resources/contents/xedit.png + + + qt.conf + -- 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') 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 ad4a90692b73bd73fa7a88690a95daa348839add Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 07:37:26 -0400 Subject: fixed crash in conflict tabs --- src/modinfodialogconflicts.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index dde00354..ad3b5e5f 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -481,8 +481,8 @@ std::vector ConflictsTab::createGotoActions( GeneralConflictsTab::GeneralConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) + : m_tab(tab), ui(pui), m_core(oc) { m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); @@ -692,8 +692,8 @@ void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) AdvancedConflictsTab::AdvancedConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) + : m_tab(tab), ui(pui), m_core(oc) { // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( -- cgit v1.3.1 From 581cfacbbdee17f2b4df8195487e5934702a430e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 09:29:41 -0400 Subject: changed "tab index" to "tab id", this was confusing the order in the widget and the id from the enum fixed reordering --- src/mainwindow.cpp | 2 +- src/modinfodialog.cpp | 208 +++++++++++++++++++++++++++------------- src/modinfodialog.h | 17 ++-- src/modinfodialog.ui | 2 +- src/modinfodialogcategories.cpp | 4 +- src/modinfodialogcategories.h | 2 +- src/modinfodialogconflicts.cpp | 4 +- src/modinfodialogconflicts.h | 2 +- src/modinfodialogesps.cpp | 4 +- src/modinfodialogesps.h | 2 +- src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogfiletree.h | 2 +- src/modinfodialogimages.cpp | 4 +- src/modinfodialogimages.h | 2 +- src/modinfodialognexus.cpp | 4 +- src/modinfodialognexus.h | 2 +- src/modinfodialogtab.cpp | 8 +- src/modinfodialogtab.h | 6 +- src/modinfodialogtextfiles.cpp | 12 +-- src/modinfodialogtextfiles.h | 6 +- 20 files changed, 184 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 67dc8418..d75e8d9d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3230,7 +3230,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { - dialog.setTab(tab); + dialog.setTab(ModInfoDialog::ETabs(tab)); } dialog.restoreState(m_OrganizerCore.settings()); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ad704ce8..c03739ca 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -104,7 +104,7 @@ ModInfoDialog::ModInfoDialog( 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) + m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)) { ui->setupUi(this); @@ -123,7 +123,6 @@ ModInfoDialog::ModInfoDialog( tabInfo.widget = ui->tabWidget->widget(i); tabInfo.caption = ui->tabWidget->tabText(i); tabInfo.icon = ui->tabWidget->tabIcon(i); - tabInfo.realPos = i; connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, @@ -159,7 +158,16 @@ std::vector ModInfoDialog::createTabs() int ModInfoDialog::exec() { - update(); + const auto selectFirst = (m_initialTab == -1); + + update(true); + + if (selectFirst) { + if (ui->tabWidget->count() > 0) { + ui->tabWidget->setCurrentIndex(0); + } + } + return TutorableDialog::exec(); } @@ -185,33 +193,34 @@ void ModInfoDialog::setMod(const QString& name) setMod(mod); } -void ModInfoDialog::setTab(int index) +void ModInfoDialog::setTab(ETabs id) { if (!isVisible()) { - m_initialTab = index; + m_initialTab = id; return; } - switchToTab(index); + switchToTab(id); } -void ModInfoDialog::update() +void ModInfoDialog::update(bool firstTime) { setWindowTitle(m_mod->name()); - setTabsVisibility(); + setTabsVisibility(firstTime); updateTabs(); feedFiles(); setTabsColors(); if (m_initialTab >= 0) { switchToTab(m_initialTab); - m_initialTab = -1; + m_initialTab = ETabs(-1); } } -void ModInfoDialog::setTabsVisibility() +void ModInfoDialog::setTabsVisibility(bool firstTime) { std::vector visibility(m_tabs.size()); + bool changed = false; for (std::size_t i=0; itabWidget->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); + const int selIndex = ui->tabWidget->currentIndex(); + ETabs sel = ETabs(-1); - if (static_cast(i) == sel) { - ui->tabWidget->setCurrentIndex(static_cast(i)); - } + for (const auto& tabInfo : m_tabs) { + if (tabInfo.realPos == selIndex) { + sel = ETabs(tabInfo.tab->tabID()); + break; } } + + reAddTabs(visibility, sel); } void ModInfoDialog::updateTabs() @@ -296,19 +301,16 @@ void ModInfoDialog::setTabsColors() } } -void ModInfoDialog::switchToTab(std::size_t index) +void ModInfoDialog::switchToTab(ETabs id) { - 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; + for (const auto& tabInfo : m_tabs) { + if (tabInfo.tab->tabID() == id) { + ui->tabWidget->setCurrentIndex(tabInfo.realPos); + return; + } } - ui->tabWidget->setCurrentIndex(m_tabs[index].realPos); + qDebug() << "can't switch to tab " << id << ", not available"; } MOShared::FilesOrigin* ModInfoDialog::getOrigin() @@ -328,7 +330,10 @@ MOShared::FilesOrigin* ModInfoDialog::getOrigin() void ModInfoDialog::saveState(Settings& s) const { - //s.directInterface().setValue("mod_info_tabs", saveTabState()); + const auto tabState = saveTabState(); + if (!tabState.isEmpty()) { + s.directInterface().setValue("mod_info_tabs", tabState); + } for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); @@ -337,55 +342,120 @@ void ModInfoDialog::saveState(Settings& s) const void ModInfoDialog::restoreState(const Settings& s) { - //restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - for (const auto& tabInfo : m_tabs) { tabInfo.tab->restoreState(s); } } -QByteArray ModInfoDialog::saveTabState() const +QString 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(); - }*/ + if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { + // only save tab state when all tabs are visible + return {}; + } + + QString result; + QTextStream stream(&result); + + for (int i=0; itabWidget->count(); ++i) { + stream << ui->tabWidget->widget(i)->objectName() << " "; + } + + return result.trimmed(); +} + +std::vector ModInfoDialog::getOrderedTabNames() const +{ + const auto value = Settings::instance() + .directInterface().value("mod_info_tabs"); + + std::vector v; + + if (value.type() == QVariant::ByteArray) { + // old byte array + QDataStream stream(value.toByteArray()); + + int count = 0; + stream >> count; + + for (int i=0; i> s; + v.emplace_back(std::move(s)); + } + } else { + // string list + QString string = value.toString(); + QTextStream stream(&string); + + while (!stream.atEnd()) { + QString s; + stream >> s; + v.emplace_back(std::move(s)); + } + } - return result; + return v; } -void ModInfoDialog::restoreTabState(const QByteArray &state) +void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) { - /*QDataStream stream(state); - int count = 0; - stream >> count; - - QStringList tabIds; - - // first, only determine the new mapping - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId; - stream >> tabId; - tabIds.append(tabId); - int oldPos = tabIndex(tabId); - if (oldPos != -1) { - m_realTabPos[newPos] = oldPos; - } else { - m_realTabPos[newPos] = newPos; + Q_ASSERT(visibility.size() == m_tabs.size()); + + // ordered tab names from settings + const auto orderedNames = getOrderedTabNames(); + + bool canSort = true; + + // gathering visible tabs + std::vector visibleTabs; + for (std::size_t i=0; iobjectName(); + auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); + if (itor == orderedNames.end()) { + qCritical() << "can't sort tabs, '" << objectName << "' not found"; + canSort = false; + } + } } } - // then actually move the tabs - QTabBar *tabBar = ui->tabWidget->tabBar(); - ui->tabWidget->blockSignals(true); - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId = tabIds.at(newPos); - int oldPos = tabIndex(tabId); - tabBar->moveTab(oldPos, newPos); + // sorting tabs + if (canSort) { + std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ + auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); + auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); + + // this was checked above + Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); + + return (aItor < bItor); + }); + } + + + ui->tabWidget->clear(); + + // reset real positions + for (auto& tabInfo : m_tabs) { + tabInfo.realPos = -1; + } + + // add visible tabs + for (std::size_t i=0; i(i); + ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); + + if (tabInfo.tab->tabID() == sel) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + } } - ui->tabWidget->blockSignals(false);*/ } int ModInfoDialog::tabIndex(const QString& tabId) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 1cefc71a..54e056b8 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -64,7 +64,6 @@ public: TAB_FILETREE }; -public: /** * @brief constructor * @@ -91,7 +90,7 @@ public: void setMod(ModInfo::Ptr mod); void setMod(const QString& name); - void setTab(int index); + void setTab(ETabs id); int exec() override; @@ -125,20 +124,22 @@ private: OrganizerCore* m_core; PluginContainer* m_plugin; std::vector m_tabs; - int m_initialTab; + ETabs m_initialTab; std::vector createTabs(); - void restoreTabState(const QByteArray &state); - QByteArray saveTabState() const; - void update(); + void restoreTabState(const QString& state); + QString saveTabState() const; + void update(bool firstTime=false); void onDeleteShortcut(); int tabIndex(const QString &tabId); MOShared::FilesOrigin* getOrigin(); - void setTabsVisibility(); + void setTabsVisibility(bool firstTime); void updateTabs(); void feedFiles(); void setTabsColors(); - void switchToTab(std::size_t index); + void switchToTab(ETabs id); + void reAddTabs(const std::vector& visibility, ETabs sel); + std::vector getOrderedTabNames() const; template std::unique_ptr createTab(int index) diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 93550de3..40b3c7b4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -1088,7 +1088,7 @@ p, li { white-space: pre-wrap; }
    - + Filetree diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index bce1162b..4bd10028 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -5,8 +5,8 @@ CategoriesTab::CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id) { connect( ui->categories, &QTreeWidget::itemChanged, diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 29d0b2a5..738b4e4d 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -7,7 +7,7 @@ class CategoriesTab : public ModInfoDialogTab public: CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; void update() override; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index ad3b5e5f..7f297ec3 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -133,8 +133,8 @@ public: ConflictsTab::ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) : - ModInfoDialogTab(oc, plugin, parent, ui, index), + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), m_general(this, ui, oc), m_advanced(this, ui, oc) { connect( diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 38fa6a74..1f82a7c0 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -97,7 +97,7 @@ class ConflictsTab : public ModInfoDialogTab public: ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void update() override; void clear() override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index d0dcaf2b..6c4fc4dd 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -124,8 +124,8 @@ private: ESPsTab::ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id) { QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index d8c8997e..e82ed368 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -12,7 +12,7 @@ class ESPsTab : public ModInfoDialogTab public: ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index dae37f25..b57f6b5d 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -15,8 +15,8 @@ const int max_scan_for_context_menu = 50; FileTreeTab::FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index), m_fs(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index d0c36edc..2145f298 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -8,7 +8,7 @@ class FileTreeTab : public ModInfoDialogTab public: FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 9a60fc8e..f4cdade8 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -82,8 +82,8 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) : - ModInfoDialogTab(oc, plugin, parent, ui, index), + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 60271da0..6603660a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -36,7 +36,7 @@ class ImagesTab : public ModInfoDialogTab public: ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 61b868d1..172968ab 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -9,8 +9,8 @@ NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 7fe10171..ce1ef426 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -35,7 +35,7 @@ class NexusTab : public ModInfoDialogTab public: NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index e50aec29..2f5fbdb8 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -5,9 +5,9 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) : + QWidget* parent, Ui::ModInfoDialog* ui, int id) : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabIndex(index), m_hasData(false) + m_origin(nullptr), m_tabID(id), m_hasData(false) { } @@ -69,9 +69,9 @@ MOShared::FilesOrigin* ModInfoDialogTab::origin() const return m_origin; } -int ModInfoDialogTab::tabIndex() const +int ModInfoDialogTab::tabID() const { - return m_tabIndex; + return m_tabID; } bool ModInfoDialogTab::hasData() const diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 8fe7d2d4..fae5bc41 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -37,7 +37,7 @@ public: ModInfo::Ptr mod() const; MOShared::FilesOrigin* origin() const; - int tabIndex() const; + int tabID() const; bool hasData() const; signals: @@ -49,7 +49,7 @@ protected: ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); OrganizerCore& core(); PluginContainer& plugin(); @@ -66,7 +66,7 @@ private: QWidget* m_parent; ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; - int m_tabIndex; + int m_tabID; bool m_hasData; }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index bd175c24..7c8e84c7 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -23,9 +23,9 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index, + QWidget* parent, Ui::ModInfoDialog* ui, int id, QListWidget* list, QSplitter* sp, TextEditor* e) - : ModInfoDialogTab(oc, plugin, parent, ui, index), m_list(list), m_editor(e) + : ModInfoDialogTab(oc, plugin, parent, ui, id), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -115,9 +115,9 @@ void GenericFilesTab::select(FileListItem* item) TextFilesTab::TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( - oc, plugin, parent, ui, index, + oc, plugin, parent, ui, id, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -139,9 +139,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c IniFilesTab::IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( - oc, plugin, parent, ui, index, + oc, plugin, parent, ui, id, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index f618a6bb..75f31d88 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -23,7 +23,7 @@ protected: GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index, + QWidget* parent, Ui::ModInfoDialog* ui, int id, QListWidget* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -39,7 +39,7 @@ class TextFilesTab : public GenericFilesTab public: TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -51,7 +51,7 @@ class IniFilesTab : public GenericFilesTab public: IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- cgit v1.3.1 From f14f2bad3ba7440d6f36657f32d00c89cae54623 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 10:35:31 -0400 Subject: don't update invisible tabs update tabs when origin changes change setting name because 2.2.0 can't handle the text list --- src/modinfodialog.cpp | 57 ++++++++++++++++++++++++++++------------- src/modinfodialog.h | 7 ++--- src/modinfodialogcategories.cpp | 5 ++++ src/modinfodialogcategories.h | 1 + src/modinfodialognexus.cpp | 5 ++++ src/modinfodialognexus.h | 1 + src/modinfodialogtab.cpp | 10 ++++++++ src/modinfodialogtab.h | 3 +++ 8 files changed, 68 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c03739ca..4c169cc4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -100,6 +100,12 @@ ModInfoDialog::TabInfo::TabInfo(std::unique_ptr tab) { } +bool ModInfoDialog::TabInfo::isVisible() const +{ + return (realPos != -1); +} + + ModInfoDialog::ModInfoDialog( MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : TutorableDialog("ModInfoDialog", mw), @@ -126,7 +132,9 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, - [&](int originID){ emit originModified(originID); }); + [this, i](int originID) { + onOriginModified(static_cast(i), originID); + }); connect( tabInfo.tab.get(), &ModInfoDialogTab::modOpen, @@ -207,9 +215,8 @@ void ModInfoDialog::update(bool firstTime) { setWindowTitle(m_mod->name()); setTabsVisibility(firstTime); + updateTabs(); - feedFiles(); - setTabsColors(); if (m_initialTab >= 0) { switchToTab(m_initialTab); @@ -261,18 +268,29 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) reAddTabs(visibility, sel); } -void ModInfoDialog::updateTabs() +void ModInfoDialog::updateTabs(bool becauseOriginChanged) { auto* origin = getOrigin(); for (auto& tabInfo : m_tabs) { + if (!tabInfo.isVisible()) { + continue; + } + + if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { + continue; + } + tabInfo.tab->setMod(m_mod, origin); tabInfo.tab->clear(); tabInfo.tab->update(); } + + feedFiles(becauseOriginChanged); + setTabsColors(); } -void ModInfoDialog::feedFiles() +void ModInfoDialog::feedFiles(bool becauseOriginChanged) { const auto rootPath = m_mod->absolutePath(); @@ -282,6 +300,14 @@ void ModInfoDialog::feedFiles() QString fileName = dirIterator.next(); for (auto& tabInfo : m_tabs) { + if (!tabInfo.isVisible()) { + continue; + } + + if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { + continue; + } + if (tabInfo.tab->feedFile(rootPath, fileName)) { break; } @@ -332,7 +358,7 @@ void ModInfoDialog::saveState(Settings& s) const { const auto tabState = saveTabState(); if (!tabState.isEmpty()) { - s.directInterface().setValue("mod_info_tabs", tabState); + s.directInterface().setValue("mod_info_tab_order", tabState); } for (const auto& tabInfo : m_tabs) { @@ -366,14 +392,13 @@ QString ModInfoDialog::saveTabState() const std::vector ModInfoDialog::getOrderedTabNames() const { - const auto value = Settings::instance() - .directInterface().value("mod_info_tabs"); + const auto& settings = Settings::instance().directInterface(); std::vector v; - if (value.type() == QVariant::ByteArray) { + if (settings.contains("mod_info_tabs")) { // old byte array - QDataStream stream(value.toByteArray()); + QDataStream stream(settings.value("mod_info_tabs").toByteArray()); int count = 0; stream >> count; @@ -385,7 +410,7 @@ std::vector ModInfoDialog::getOrderedTabNames() const } } else { // string list - QString string = value.toString(); + QString string = settings.value("mod_info_tab_order").toString(); QTextStream stream(&string); while (!stream.atEnd()) { @@ -458,14 +483,10 @@ void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) } } -int ModInfoDialog::tabIndex(const QString& tabId) +void ModInfoDialog::onOriginModified(std::size_t tabIndex, int originID) { - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->widget(i)->objectName() == tabId) { - return i; - } - } - return -1; + emit originModified(originID); + updateTabs(true); } void ModInfoDialog::onDeleteShortcut() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 54e056b8..31ea5536 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -116,6 +116,7 @@ private: QIcon icon; TabInfo(std::unique_ptr tab); + bool isVisible() const; }; std::unique_ptr ui; @@ -131,15 +132,15 @@ private: QString saveTabState() const; void update(bool firstTime=false); void onDeleteShortcut(); - int tabIndex(const QString &tabId); MOShared::FilesOrigin* getOrigin(); void setTabsVisibility(bool firstTime); - void updateTabs(); - void feedFiles(); + void updateTabs(bool becauseOriginChanged=false); + void feedFiles(bool becauseOriginChanged); void setTabsColors(); void switchToTab(ETabs id); void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; + void onOriginModified(std::size_t tabIndex, int originID); template std::unique_ptr createTab(int index) diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 4bd10028..0d739d1f 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -42,6 +42,11 @@ bool CategoriesTab::canHandleSeparators() const return true; } +bool CategoriesTab::usesOriginFiles() const +{ + return false; +} + void CategoriesTab::add( const CategoryFactory &factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel) diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 738b4e4d..392023e7 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -12,6 +12,7 @@ public: void clear() override; void update() override; bool canHandleSeparators() const override; + bool usesOriginFiles() const override; private: void add( diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 172968ab..d296e000 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -102,6 +102,11 @@ void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) mod.data(), &ModInfo::modDetailsUpdated, [&]{ onModChanged(); }); } +bool NexusTab::usesOriginFiles() const +{ + return false; +} + void NexusTab::updateVersionColor() { if (mod()->getVersion() != mod()->getNewestVersion()) { diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index ce1ef426..a09f4316 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -42,6 +42,7 @@ public: void clear() override; void update() override; void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + bool usesOriginFiles() const override; private: QMetaObject::Connection m_modConnection; diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 2f5fbdb8..009fb804 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -53,6 +53,11 @@ bool ModInfoDialogTab::canHandleUnmanaged() const return false; } +bool ModInfoDialogTab::usesOriginFiles() const +{ + return true; +} + void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; @@ -158,3 +163,8 @@ void NotesTab::onNotes() mod()->setNotes(ui->notesEdit->toHtml()); } } + +bool NotesTab::usesOriginFiles() const +{ + return false; +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index fae5bc41..c85d2ded 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -27,10 +27,12 @@ public: virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); + virtual bool deleteRequested(); virtual bool canHandleSeparators() const; virtual bool canHandleUnmanaged() const; + virtual bool usesOriginFiles() const; virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); @@ -81,6 +83,7 @@ public: void clear() override; void update() override; bool canHandleSeparators() const override; + bool usesOriginFiles() const override; private: void onComments(); -- cgit v1.3.1 From 4ee9f29cdcb3df2a085e02fa29c8ae3ddbd27f1c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 12:49:08 -0400 Subject: performance optimizations: text/ini tabs and advanced conflict list use views and custom models call update() after feedFiles() to allow some tabs to have post-processing after feedFiles() --- src/modinfodialog.cpp | 20 +++- src/modinfodialog.h | 2 + src/modinfodialog.ui | 48 +++------ src/modinfodialogconflicts.cpp | 216 +++++++++++++++++++++++++++++++++++------ src/modinfodialogconflicts.h | 6 +- src/modinfodialogtextfiles.cpp | 133 +++++++++++++++++++------ src/modinfodialogtextfiles.h | 13 ++- 7 files changed, 338 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4c169cc4..a407e9b7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -35,6 +35,19 @@ using namespace MOShared; const int max_scan_for_context_menu = 50; + +int naturalCompare(const QString& a, const QString& b) +{ + static QCollator c = []{ + QCollator c; + c.setNumericMode(true); + c.setCaseSensitivity(Qt::CaseInsensitive); + return c; + }(); + + return c.compare(a, b); +} + bool canPreviewFile( PluginContainer& pluginContainer, bool isArchive, const QString& filename) { @@ -283,10 +296,15 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) tabInfo.tab->setMod(m_mod, origin); tabInfo.tab->clear(); - tabInfo.tab->update(); } + feedFiles(becauseOriginChanged); + + for (auto& tabInfo : m_tabs) { + tabInfo.tab->update(); + } + setTabsColors(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 31ea5536..e814dcf4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -42,6 +42,8 @@ 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); + /** * this is a larger dialog used to visualise information about the mod. diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 40b3c7b4..5defcd57 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -60,13 +60,16 @@ - + A list of text-files in the mod directory. A list of text-files in the mod directory like readmes. + + true + @@ -117,13 +120,16 @@ - + This is a list of .ini files in the mod. This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. + + true + @@ -251,6 +257,9 @@ They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. + + true +
    @@ -375,6 +384,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + + true + @@ -477,9 +489,6 @@ text-align: left; true - - true - 2 @@ -553,9 +562,6 @@ text-align: left; true - - true - 2 @@ -629,9 +635,6 @@ text-align: left; true - - true - 1 @@ -696,7 +699,7 @@ text-align: left; 0 - + Qt::CustomContextMenu @@ -709,27 +712,6 @@ text-align: left; true - - 3 - - - 243 - - - - Overwrites - - - - - File - - - - - Overwritten by - - diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 7f297ec3..55fbf2e5 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -13,19 +13,6 @@ using namespace MOBase; const int max_scan_for_context_menu = 50; -int naturalCompare(const QString& a, const QString& b) -{ - static QCollator c = []{ - QCollator c; - c.setNumericMode(true); - c.setCaseSensitivity(Qt::CaseInsensitive); - return c; - }(); - - return c.compare(a, b); -} - - class ElideLeftDelegate : public QStyledItemDelegate { public: @@ -581,6 +568,10 @@ bool GeneralConflictsTab::update() int numOverwrite = 0; int numOverwritten = 0; + QList overwriteItems; + QList overwrittenItems; + QList noConflictItems; + if (m_tab->origin() != nullptr) { const auto rootPath = m_tab->mod()->absolutePath(); @@ -594,19 +585,19 @@ bool GeneralConflictsTab::update() if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { - ui->overwriteTree->addTopLevelItem(createOverwriteItem( + overwriteItems.append(createOverwriteItem( file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree - ui->noConflictTree->addTopLevelItem(createNoConflictItem( + noConflictItems.append(createNoConflictItem( file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { - ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( + overwrittenItems.append(createOverwrittenItem( file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; @@ -614,6 +605,10 @@ bool GeneralConflictsTab::update() } } + ui->overwriteTree->addTopLevelItems(overwriteItems); + ui->overwrittenTree->addTopLevelItems(overwrittenItems); + ui->noConflictTree->addTopLevelItems(noConflictItems); + ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); ui->noConflictCount->display(numNonConflicting); @@ -691,10 +686,166 @@ void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) } +class AdvancedListModel : public QAbstractItemModel +{ +public: + struct Item + { + QString before, relativeName, after; + FileEntry::Index index; + QString fileName; + bool hasAltOrigins; + QString altOrigin; + bool archive; + }; + + void clear() + { + m_items.clear(); + } + + void reserve(std::size_t s) + { + m_items.reserve(s); + } + + QModelIndex index(int row, int col, const QModelIndex& ={}) const override + { + return createIndex(row, col); + } + + QModelIndex parent(const QModelIndex&) const override + { + return {}; + } + + int rowCount(const QModelIndex& parent={}) const override + { + if (parent.isValid()) { + return 0; + } + + return static_cast(m_items.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return 3; + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole) { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_items.size()) { + return {}; + } + + const auto& item = m_items[i]; + + if (index.column() == 0) { + return item.before; + } else if (index.column() == 1) { + return item.relativeName; + } else if (index.column() == 2) { + return item.after; + } + } + + return {}; + } + + QVariant headerData(int col, Qt::Orientation, int role) const + { + if (role == Qt::DisplayRole) { + if (col == 0) { + return tr("Overwrites"); + } else if (col == 1) { + return tr("File"); + } else if (col == 2) { + return tr("Overwritten by"); + } + } + + return {}; + } + + void sort(int col, Qt::SortOrder order=Qt::AscendingOrder) + { + // avoids branching on column/sort order while sorting + auto sortBeforeAsc = [](const auto& a, const auto& b) { + return (naturalCompare(a.before, b.before) < 0); + }; + + auto sortBeforeDesc = [](const auto& a, const auto& b) { + return (naturalCompare(a.before, b.before) > 0); + }; + + auto sortFileAsc = [](const auto& a, const auto& b) { + return (naturalCompare(a.relativeName, b.relativeName) < 0); + }; + + auto sortFileDesc = [](const auto& a, const auto& b) { + return (naturalCompare(a.relativeName, b.relativeName) > 0); + }; + + auto sortAfterAsc = [](const auto& a, const auto& b) { + return (naturalCompare(a.after, b.after) < 0); + }; + + auto sortAfterDesc = [](const auto& a, const auto& b) { + return (naturalCompare(a.after, b.after) > 0); + }; + + if (col == 0) { + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortBeforeAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortBeforeDesc); + } + } else if (col == 1) { + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortFileAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortFileDesc); + } + } else if (col == 2) { + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortAfterAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortAfterDesc); + } + } + + emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); + } + + void add(Item item) + { + m_items.emplace_back(std::move(item)); + } + + void finished() + { + emit dataChanged(index(0, 0), index(0, rowCount())); + } + +private: + std::vector m_items; +}; + + AdvancedConflictsTab::AdvancedConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) - : m_tab(tab), ui(pui), m_core(oc) + : m_tab(tab), ui(pui), m_core(oc), m_model(new AdvancedListModel) { + ui->conflictsAdvancedList->setModel(m_model); + // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); @@ -717,9 +868,9 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); - QObject::connect( - ui->conflictsAdvancedList, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); + //QObject::connect( + // ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, + // [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); m_filter.set(ui->conflictsAdvancedFilter); m_filter.changed = [&]{ update(); }; @@ -727,7 +878,7 @@ AdvancedConflictsTab::AdvancedConflictsTab( void AdvancedConflictsTab::clear() { - ui->conflictsAdvancedList->clear(); + m_model->clear(); } void AdvancedConflictsTab::saveState(Settings& s) @@ -776,7 +927,10 @@ void AdvancedConflictsTab::update() if (m_tab->origin() != nullptr) { const auto rootPath = m_tab->mod()->absolutePath(); - for (const auto& file : m_tab->origin()->getFiles()) { + const auto& files = m_tab->origin()->getFiles(); + m_model->reserve(files.size()); + + for (const auto& file : files) { const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); const QString fileName = relativeName.mid(0).prepend(rootPath); @@ -784,18 +938,16 @@ void AdvancedConflictsTab::update() const int fileOrigin = file->getOrigin(archive); const auto& alternatives = file->getAlternatives(); - auto* advancedItem = createItem( + addItem( file->getIndex(), fileOrigin, archive, fileName, relativeName, alternatives); - - if (advancedItem) { - ui->conflictsAdvancedList->addTopLevelItem(advancedItem); - } } + + m_model->finished(); } } -QTreeWidgetItem* AdvancedConflictsTab::createItem( +void AdvancedConflictsTab::addItem( FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) @@ -888,7 +1040,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it if (!hasAlts) { - return nullptr; + return; } } @@ -900,9 +1052,9 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( }); if (!matched) { - return nullptr; + return; } - return new ConflictItem( - {before, relativeName, after}, index, fileName, hasAlts, "", archive); + m_model->add( + {before, relativeName, after, index, fileName, hasAlts, "", archive}); } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 1f82a7c0..f499d1a4 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -9,6 +9,7 @@ class ConflictsTab; class OrganizerCore; +class ConflictItem; class GeneralConflictsTab : public QObject { @@ -60,6 +61,8 @@ private: }; +class AdvancedListModel; + class AdvancedConflictsTab : public QObject { Q_OBJECT; @@ -82,8 +85,9 @@ private: Ui::ModInfoDialog* ui; OrganizerCore& m_core; FilterWidget m_filter; + AdvancedListModel* m_model; - QTreeWidgetItem* createItem( + void addItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 7c8e84c7..08b8c988 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -1,32 +1,109 @@ #include "modinfodialogtextfiles.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include -class FileListItem : public QListWidgetItem +class FileListModel : public QAbstractItemModel { public: - FileListItem(const QString& rootPath, QString fullPath) - : m_fullPath(std::move(fullPath)) + void clear() { - setText(m_fullPath.mid(rootPath.length() + 1)); + m_files.clear(); } - const QString& fullPath() const + QModelIndex index(int row, int col, const QModelIndex& ={}) const override { - return m_fullPath; + return createIndex(row, col); + } + + QModelIndex parent(const QModelIndex&) const override + { + return {}; + } + + int rowCount(const QModelIndex& ={}) const override + { + return static_cast(m_files.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return 1; + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole) { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_files.size()) { + return {}; + } + + return m_files[i].text; + } + + return {}; + } + + void add(const QString& rootPath, QString fullPath) + { + QString text = fullPath.mid(rootPath.length() + 1); + m_files.emplace_back(std::move(fullPath), std::move(text)); + } + + void finished() + { + std::sort(m_files.begin(), m_files.end(), [](const auto& a, const auto& b) { + return (naturalCompare(a.text, b.text) < 0); + }); + + emit dataChanged(index(0, 0), index(0, rowCount())); + } + + QString fullPath(const QModelIndex& index) const + { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_files.size()) { + return {}; + } + + return m_files[i].fullPath; } private: - QString m_fullPath; + struct File + { + QString fullPath; + QString text; + + File(QString fp, QString t) + : fullPath(std::move(fp)), text(std::move(t)) + { + } + }; + + std::deque m_files; }; GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListWidget* list, QSplitter* sp, TextEditor* e) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_list(list), m_editor(e) + QListView* list, QSplitter* sp, TextEditor* e) : + ModInfoDialogTab(oc, plugin, parent, ui, id), + m_list(list), m_editor(e), m_model(new FileListModel) { + m_list->setModel(m_model); m_editor->setupToolbar(); sp->setSizes({200, 1}); @@ -34,14 +111,14 @@ GenericFilesTab::GenericFilesTab( sp->setStretchFactor(1, 1); QObject::connect( - m_list, &QListWidget::currentItemChanged, - [&](auto* current, auto* previous){ onSelection(current, previous); }); + m_list->selectionModel(), &QItemSelectionModel::currentRowChanged, + [&](auto current, auto previous){ onSelection(current, previous); }); } void GenericFilesTab::clear() { - m_list->clear(); - select(nullptr); + m_model->clear(); + select({}); setHasData(false); } @@ -76,7 +153,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)); + m_model->add(rootPath, fullPath); setHasData(true); return true; } @@ -85,31 +162,31 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) return false; } -void GenericFilesTab::onSelection( - QListWidgetItem* current, QListWidgetItem* previous) +void GenericFilesTab::update() { - auto* item = dynamic_cast(current); - if (!item) { - qCritical("TextFilesTab: item is not a FileListItem"); - return; - } + m_model->finished(); +} +void GenericFilesTab::onSelection( + const QModelIndex& current, const QModelIndex& previous) +{ if (!canClose()) { - m_list->setCurrentItem(previous, QItemSelectionModel::Current); + m_list->selectionModel()->select(previous, QItemSelectionModel::Current); return; } - select(item); + select(current); } -void GenericFilesTab::select(FileListItem* item) +void GenericFilesTab::select(const QModelIndex& index) { - if (item) { - m_editor->setEnabled(true); - m_editor->load(item->fullPath()); - } else { + if (!index.isValid()) { m_editor->setEnabled(false); + return; } + + m_editor->setEnabled(true); + m_editor->load(m_model->fullPath(index)); } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 75f31d88..d879c2bd 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -3,9 +3,10 @@ #include "modinfodialogtab.h" #include -#include +#include class FileListItem; +class FileListModel; class TextEditor; class GenericFilesTab : public ModInfoDialogTab @@ -16,21 +17,23 @@ public: void clear() override; bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; + void update() override; protected: - QListWidget* m_list; + QListView* m_list; TextEditor* m_editor; + FileListModel* m_model; GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListWidget* list, QSplitter* splitter, TextEditor* editor); + QListView* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; private: - void onSelection(QListWidgetItem* current, QListWidgetItem* previous); - void select(FileListItem* item); + void onSelection(const QModelIndex& current, const QModelIndex& previous); + void select(const QModelIndex& index); }; -- cgit v1.3.1 From 1e54b4b57682b544bca0bb9d563fdb3d747073f2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 14:50:55 -0400 Subject: switched all conflict lists to QTreeView with custom model --- src/modinfodialog.ui | 56 +-- src/modinfodialogconflicts.cpp | 766 ++++++++++++++++++++++++----------------- src/modinfodialogconflicts.h | 37 +- 3 files changed, 471 insertions(+), 388 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 5defcd57..cdf4c80f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -406,7 +406,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 1 + 0 @@ -421,7 +421,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e 100 - + 0 @@ -473,7 +473,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -489,25 +489,6 @@ text-align: left; true - - 2 - - - 200 - - - 365 - - - - File - - - - - Overwritten Mods - - @@ -546,7 +527,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -562,25 +543,6 @@ text-align: left; true - - 2 - - - 200 - - - 365 - - - - File - - - - - Providing Mod - - @@ -619,7 +581,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -635,14 +597,6 @@ text-align: left; true - - 1 - - - - File - - diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 55fbf2e5..d1384d7a 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -10,7 +10,7 @@ using namespace MOBase; // if there are more than 50 selected items in the conflict tree, don't bother // checking whether menu items apply to them, just show all of them -const int max_scan_for_context_menu = 50; +const std::size_t max_small_selection = 50; class ElideLeftDelegate : public QStyledItemDelegate @@ -27,60 +27,62 @@ protected: }; -class ConflictItem : public QTreeWidgetItem +class ConflictItem { public: - static const int FILENAME_USERROLE = Qt::UserRole + 1; - static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2; - static const int ARCHIVE_USERROLE = Qt::UserRole + 3; - static const int INDEX_USERROLE = Qt::UserRole + 4; - static const int HAS_ALTS_USERROLE = Qt::UserRole + 5; - ConflictItem( - QStringList columns, FileEntry::Index index, const QString& fileName, - bool hasAltOrigins, const QString& altOrigin, bool archive) - : QTreeWidgetItem(columns) + QString before, QString relativeName, QString after, + FileEntry::Index index, QString fileName, + bool hasAltOrigins, QString altOrigin, bool archive) : + m_before(std::move(before)), + m_relativeName(std::move(relativeName)), + m_after(std::move(after)), + m_index(index), + m_fileName(std::move(fileName)), + m_hasAltOrigins(hasAltOrigins), + m_altOrigin(std::move(altOrigin)), + m_isArchive(archive) { - setData(0, FILENAME_USERROLE, fileName); - setData(0, ALT_ORIGIN_USERROLE, altOrigin); - setData(0, ARCHIVE_USERROLE, archive); - setData(0, INDEX_USERROLE, index); - setData(0, HAS_ALTS_USERROLE, hasAltOrigins); - - if (archive) { - QFont f = font(0); - f.setItalic(true); - - for (int i=0; i); - return data(0, INDEX_USERROLE).toUInt(); + return m_index; } bool canHide() const @@ -103,21 +105,276 @@ public: return canPreviewFile(pluginContainer, isArchive(), fileName()); } - bool operator<(const QTreeWidgetItem& other) const +private: + QString m_before; + QString m_relativeName; + QString m_after; + FileEntry::Index m_index; + QString m_fileName; + bool m_hasAltOrigins; + QString m_altOrigin; + bool m_isArchive; +}; + + +class ConflictListModel : public QAbstractItemModel +{ +public: + struct Column { - const int column = treeWidget()->sortColumn(); + QString caption; + const QString& (ConflictItem::*getText)() const; + }; - if (column >= columnCount() || column >= other.columnCount()) { - // shouldn't happen - qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count"; - return false; + ConflictListModel(QTreeView* tree, std::vector columns) + : m_tree(tree), m_columns(std::move(columns)) + { + m_tree->setModel(this); + } + + void clear() + { + m_items.clear(); + } + + void reserve(std::size_t s) + { + m_items.reserve(s); + } + + QModelIndex index(int row, int col, const QModelIndex& ={}) const override + { + return createIndex(row, col); + } + + QModelIndex parent(const QModelIndex&) const override + { + return {}; + } + + int rowCount(const QModelIndex& parent={}) const override + { + if (parent.isValid()) { + return 0; + } + + return static_cast(m_items.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return static_cast(m_columns.size()); + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole || role == Qt::FontRole) { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_items.size()) { + return {}; + } + + const auto col = index.column(); + if (col < 0) { + return {}; + } + + const auto c = static_cast(col); + if (c >= m_columns.size()) { + return {}; + } + + const auto& item = m_items[i]; + + if (role == Qt::DisplayRole) { + return (item.*m_columns[c].getText)(); + } else if (role == Qt::FontRole) { + if (item.isArchive()) { + QFont f = m_tree->font(); + f.setItalic(true); + return f; + } + } + } + + return {}; + } + + QVariant headerData(int col, Qt::Orientation, int role) const + { + if (role == Qt::DisplayRole) { + if (col < 0) { + return {}; + } + + const auto i = static_cast(col); + if (i >= m_columns.size()) { + return {}; + } + + return m_columns[i].caption; } - return (naturalCompare(text(column), other.text(column)) < 0); + return {}; + } + + void sort(int colIndex, Qt::SortOrder order=Qt::AscendingOrder) + { + if (colIndex < 0) { + return; + } + + const auto c = static_cast(colIndex); + if (c >= m_columns.size()) { + return; + } + + const auto& col = m_columns[c]; + + // avoids branching on sort order while sorting + auto sortAsc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); + }; + + auto sortDesc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); + }; + + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortDesc); + } + + emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); + } + + void add(ConflictItem item) + { + m_items.emplace_back(std::move(item)); + } + + void finished() + { + beginResetModel(); + endResetModel(); + } + + const ConflictItem* getItem(std::size_t row) const + { + if (row >= m_items.size()) { + return nullptr; + } + + return &m_items[row]; + } + +private: + QTreeView* m_tree; + std::vector m_columns; + std::vector m_items; +}; + + +class OverwriteConflictListModel : public ConflictListModel +{ +public: + OverwriteConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("File"), &ConflictItem::relativeName}, + {tr("Overwritten Mods"), &ConflictItem::before} + }) + { + } +}; + + +class OverwrittenConflictListModel : public ConflictListModel +{ +public: + OverwrittenConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("File"), &ConflictItem::relativeName}, + {tr("Providing Mod"), &ConflictItem::after} + }) + { + } +}; + + +class NoConflictListModel : public ConflictListModel +{ +public: + NoConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("File"), &ConflictItem::relativeName} + }) + { + } +}; + + +class AdvancedConflictListModel : public ConflictListModel +{ +public: + AdvancedConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("Overwrites"), &ConflictItem::before}, + {tr("File"), &ConflictItem::relativeName}, + {tr("Overwritten By"), &ConflictItem::after} + }) + { } }; +std::size_t smallSelectionSize(const QTreeView* tree) +{ + const std::size_t too_many = std::numeric_limits::max(); + + std::size_t n = 0; + const auto* sel = tree->selectionModel(); + + for (const auto& range : sel->selection()) { + n += range.height(); + + if (n >= max_small_selection) { + return too_many; + } + } + + return n; +} + +template +void for_each_in_selection(QTreeView* tree, F&& f) +{ + const auto* sel = tree->selectionModel(); + const auto* model = dynamic_cast(tree->model()); + + if (!model) { + qCritical() << "tree doesn't have a ConflictListModel"; + return; + } + + for (const auto& range : sel->selection()) { + // ranges are inclusive + for (int row=range.top(); row<=range.bottom(); ++row) { + if (auto* item=model->getItem(static_cast(row))) { + if (!f(item)) { + return; + } + } + } + } +} + + ConflictsTab::ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : @@ -169,52 +426,55 @@ bool ConflictsTab::canHandleUnmanaged() const return true; } -void ConflictsTab::changeItemsVisibility( - const QList& items, bool visible) +void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) { bool changed = false; bool stop = false; - qDebug().nospace() + const auto n = smallSelectionSize(tree); + + qDebug().nospace().noquote() << (visible ? "unhiding" : "hiding") << " " - << items.size() << " conflict files"; + << (n > max_small_selection ? "a lot of" : QString("%1").arg(n)) + << " conflict files"; QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - if (items.size() > 1) { + if (n > 1) { flags |= FileRenamer::MULTIPLE; } FileRenamer renamer(parentWidget(), flags); - for (const auto* item : items) { - if (stop) { - break; - } + auto* model = dynamic_cast(tree->model()); + if (!model) { + qCritical() << "list doesn't have a ConflictListModel"; + return; + } - const auto* ci = dynamic_cast(item); - if (!ci) { - continue; + for_each_in_selection(tree, [&](const ConflictItem* item) { + if (stop) { + return false; } auto result = FileRenamer::RESULT_CANCEL; if (visible) { - if (!ci->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; - continue; + if (!item->canUnhide()) { + qDebug().nospace() << "cannot unhide " << item->relativeName() << ", skipping"; + return true; } - result = unhideFile(renamer, ci->fileName()); + result = unhideFile(renamer, item->fileName()); } else { - if (!ci->canHide()) { - qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; - continue; + if (!item->canHide()) { + qDebug().nospace() << "cannot hide " << item->relativeName() << ", skipping"; + return true; } - result = hideFile(renamer, ci->fileName()); + result = hideFile(renamer, item->fileName()); } switch (result) { @@ -235,7 +495,9 @@ void ConflictsTab::changeItemsVisibility( break; } } - } + + return true; + }); qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; @@ -250,38 +512,36 @@ void ConflictsTab::changeItemsVisibility( } } -void ConflictsTab::openItems(const QList& items) +void ConflictsTab::openItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - core().executeFileVirtualized(parentWidget(), ci->fileName()); - } - } + for_each_in_selection(tree, [&](const ConflictItem* item) { + core().executeFileVirtualized(parentWidget(), item->fileName()); + return true; + }); } -void ConflictsTab::previewItems(const QList& items) +void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - core().previewFileWithAlternatives(parentWidget(), ci->fileName()); - } - } + for_each_in_selection(tree, [&](const ConflictItem* item) { + core().previewFileWithAlternatives(parentWidget(), item->fileName()); + return true; + }); } -void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) +void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) { - auto actions = createMenuActions(tree->selectedItems()); + auto actions = createMenuActions(tree); QMenu menu; // open if (actions.open) { connect(actions.open, &QAction::triggered, [&]{ - openItems(tree->selectedItems()); + openItems(tree); }); menu.addAction(actions.open); @@ -290,7 +550,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) // preview if (actions.preview) { connect(actions.preview, &QAction::triggered, [&]{ - previewItems(tree->selectedItems()); + previewItems(tree); }); menu.addAction(actions.preview); @@ -299,7 +559,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ - changeItemsVisibility(tree->selectedItems(), false); + changeItemsVisibility(tree, false); }); menu.addAction(actions.hide); @@ -308,7 +568,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) // unhide if (actions.unhide) { connect(actions.unhide, &QAction::triggered, [&]{ - changeItemsVisibility(tree->selectedItems(), true); + changeItemsVisibility(tree, true); }); menu.addAction(actions.unhide); @@ -332,10 +592,9 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) } } -ConflictsTab::Actions ConflictsTab::createMenuActions( - const QList& selection) +ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) { - if (selection.empty()) { + if (tree->selectionModel()->selection().isEmpty()) { return {}; } @@ -345,18 +604,28 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( bool enablePreview = true; bool enableGoto = true; - if (selection.size() == 1) { + const auto n = smallSelectionSize(tree); + + const auto* model = dynamic_cast(tree->model()); + if (!model) { + qCritical() << "tree doesn't have a ConflictListModel"; + return {}; + } + + if (n == 1) { // this is a single selection - const auto* ci = dynamic_cast(selection[0]); - if (!ci) { + const auto* item = model->getItem(static_cast( + tree->selectionModel()->selectedRows()[0].row())); + + if (!item) { return {}; } - enableHide = ci->canHide(); - enableUnhide = ci->canUnhide(); - enableOpen = ci->canOpen(); - enablePreview = ci->canPreview(plugin()); - enableGoto = ci->hasAlts(); + enableHide = item->canHide(); + enableUnhide = item->canUnhide(); + enableOpen = item->canOpen(); + enablePreview = item->canPreview(plugin()); + enableGoto = item->hasAlts(); } else { // this is a multiple selection, don't show open/preview so users don't open @@ -367,28 +636,28 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( // don't bother with this on multiple selection, at least for now enableGoto = false; - if (selection.size() < max_scan_for_context_menu) { + if (n <= max_small_selection) { // if the number of selected items is low, checking them to accurately // show the menu items is worth it enableHide = false; enableUnhide = false; - for (const auto* item : selection) { - if (const auto* ci=dynamic_cast(item)) { - if (ci->canHide()) { - enableHide = true; - } + for_each_in_selection(tree, [&](const ConflictItem* item) { + if (item->canHide()) { + enableHide = true; + } - if (ci->canUnhide()) { - enableUnhide = true; - } + if (item->canUnhide()) { + enableUnhide = true; + } - if (enableHide && enableUnhide && enableGoto) { - // found all, no need to check more - break; - } + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more + return false; } - } + + return true; + }); } } @@ -411,22 +680,19 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( actions.gotoMenu = new QMenu(tr("Go to..."), parentWidget()); actions.gotoMenu->setEnabled(enableGoto); - if (enableGoto) { - actions.gotoActions = createGotoActions(selection); + if (enableGoto && n == 1) { + const auto* item = model->getItem(static_cast( + tree->selectionModel()->selectedRows()[0].row())); + + actions.gotoActions = createGotoActions(item); } return actions; } -std::vector ConflictsTab::createGotoActions( - const QList& selection) +std::vector ConflictsTab::createGotoActions(const ConflictItem* item) { - if (!origin() || selection.size() != 1) { - return {}; - } - - const auto* item = dynamic_cast(selection[0]); - if (!item) { + if (!origin()) { return {}; } @@ -468,39 +734,42 @@ std::vector ConflictsTab::createGotoActions( GeneralConflictsTab::GeneralConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) - : m_tab(tab), ui(pui), m_core(oc) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) : + m_tab(tab), ui(pui), m_core(oc), + m_overwriteModel(new OverwriteConflictListModel(ui->overwriteTree)), + m_overwrittenModel(new OverwrittenConflictListModel(ui->overwrittenTree)), + m_noConflictModel(new NoConflictListModel(ui->noConflictTree)) { m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); m_expanders.nonconflict.set(ui->noConflictExpander, ui->noConflictTree); QObject::connect( - ui->overwriteTree, &QTreeWidget::itemDoubleClicked, - [&](auto* item, int col){ onOverwriteActivated(item, col); }); + ui->overwriteTree, &QTreeView::doubleClicked, + [&](auto&& item){ onOverwriteActivated(item); }); QObject::connect( - ui->overwrittenTree, &QTreeWidget::itemDoubleClicked, - [&](auto* item, int col){ onOverwrittenActivated(item, col); }); + ui->overwrittenTree, &QTreeView::doubleClicked, + [&](auto&& item){ onOverwrittenActivated(item); }); QObject::connect( - ui->overwriteTree, &QTreeWidget::customContextMenuRequested, + ui->overwriteTree, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree); }); QObject::connect( - ui->overwrittenTree, &QTreeWidget::customContextMenuRequested, + ui->overwrittenTree, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree); }); QObject::connect( - ui->noConflictTree, &QTreeWidget::customContextMenuRequested, + ui->noConflictTree, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree); }); } void GeneralConflictsTab::clear() { - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - ui->noConflictTree->clear(); + m_overwriteModel->clear(); + m_overwrittenModel->clear(); + m_noConflictModel->clear(); ui->overwriteCount->display(0); ui->overwrittenCount->display(0); @@ -568,10 +837,6 @@ bool GeneralConflictsTab::update() int numOverwrite = 0; int numOverwritten = 0; - QList overwriteItems; - QList overwrittenItems; - QList noConflictItems; - if (m_tab->origin() != nullptr) { const auto rootPath = m_tab->mod()->absolutePath(); @@ -585,19 +850,19 @@ bool GeneralConflictsTab::update() if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { - overwriteItems.append(createOverwriteItem( - file->getIndex(), archive, fileName, relativeName, alternatives)); + m_overwriteModel->add(createOverwriteItem( + file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree - noConflictItems.append(createNoConflictItem( + m_noConflictModel->add(createNoConflictItem( file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { - overwrittenItems.append(createOverwrittenItem( + m_overwrittenModel->add(createOverwrittenItem( file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; @@ -605,9 +870,9 @@ bool GeneralConflictsTab::update() } } - ui->overwriteTree->addTopLevelItems(overwriteItems); - ui->overwrittenTree->addTopLevelItems(overwrittenItems); - ui->noConflictTree->addTopLevelItems(noConflictItems); + m_overwriteModel->finished(); + m_overwrittenModel->finished(); + m_noConflictModel->finished(); ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); @@ -616,7 +881,7 @@ bool GeneralConflictsTab::update() return (numOverwrite > 0 || numOverwritten > 0); } -QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( +ConflictItem GeneralConflictsTab::createOverwriteItem( FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName, const FileEntry::AlternativesVector& alternatives) @@ -638,17 +903,17 @@ QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); - return new ConflictItem(fields, index, fileName, true, origin, archive); + return {altString, relativeName, "", index, fileName, true, origin, archive}; } -QTreeWidgetItem* GeneralConflictsTab::createNoConflictItem( +ConflictItem GeneralConflictsTab::createNoConflictItem( FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName) { - return new ConflictItem({relativeName}, index, fileName, false, "", archive); + return {"", relativeName, "", index, fileName, false, "", archive}; } -QTreeWidgetItem* GeneralConflictsTab::createOverwrittenItem( +ConflictItem GeneralConflictsTab::createOverwrittenItem( FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName) { @@ -659,193 +924,53 @@ QTreeWidgetItem* GeneralConflictsTab::createOverwrittenItem( QStringList fields(relativeName); fields.append(ToQString(realOrigin.getName())); - return new ConflictItem( - fields, index, fileName, true, ToQString(realOrigin.getName()), archive); + return { + "", relativeName, ToQString(realOrigin.getName()), + index, fileName, true, ToQString(realOrigin.getName()), archive}; } -void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int) +void GeneralConflictsTab::onOverwriteActivated(const QModelIndex& index) { - if (auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); - - if (!origin.isEmpty()) { - emit modOpen(origin); - } + auto* model = dynamic_cast(ui->overwriteTree->model()); + if (!model) { + return; } -} -void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) -{ - if (const auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); + auto* item = model->getItem(static_cast(index.row())); + if (!item) { + return; + } - if (!origin.isEmpty()) { - emit modOpen(origin); - } + const auto origin = item->altOrigin(); + if (!origin.isEmpty()) { + emit modOpen(origin); } } - -class AdvancedListModel : public QAbstractItemModel +void GeneralConflictsTab::onOverwrittenActivated(const QModelIndex& index) { -public: - struct Item - { - QString before, relativeName, after; - FileEntry::Index index; - QString fileName; - bool hasAltOrigins; - QString altOrigin; - bool archive; - }; - - void clear() - { - m_items.clear(); - } - - void reserve(std::size_t s) - { - m_items.reserve(s); - } - - QModelIndex index(int row, int col, const QModelIndex& ={}) const override - { - return createIndex(row, col); - } - - QModelIndex parent(const QModelIndex&) const override - { - return {}; - } - - int rowCount(const QModelIndex& parent={}) const override - { - if (parent.isValid()) { - return 0; - } - - return static_cast(m_items.size()); - } - - int columnCount(const QModelIndex& ={}) const override - { - return 3; - } - - QVariant data(const QModelIndex& index, int role) const override - { - if (role == Qt::DisplayRole) { - const auto row = index.row(); - if (row < 0) { - return {}; - } - - const auto i = static_cast(row); - if (i >= m_items.size()) { - return {}; - } - - const auto& item = m_items[i]; - - if (index.column() == 0) { - return item.before; - } else if (index.column() == 1) { - return item.relativeName; - } else if (index.column() == 2) { - return item.after; - } - } - - return {}; - } - - QVariant headerData(int col, Qt::Orientation, int role) const - { - if (role == Qt::DisplayRole) { - if (col == 0) { - return tr("Overwrites"); - } else if (col == 1) { - return tr("File"); - } else if (col == 2) { - return tr("Overwritten by"); - } - } - - return {}; - } - - void sort(int col, Qt::SortOrder order=Qt::AscendingOrder) - { - // avoids branching on column/sort order while sorting - auto sortBeforeAsc = [](const auto& a, const auto& b) { - return (naturalCompare(a.before, b.before) < 0); - }; - - auto sortBeforeDesc = [](const auto& a, const auto& b) { - return (naturalCompare(a.before, b.before) > 0); - }; - - auto sortFileAsc = [](const auto& a, const auto& b) { - return (naturalCompare(a.relativeName, b.relativeName) < 0); - }; - - auto sortFileDesc = [](const auto& a, const auto& b) { - return (naturalCompare(a.relativeName, b.relativeName) > 0); - }; - - auto sortAfterAsc = [](const auto& a, const auto& b) { - return (naturalCompare(a.after, b.after) < 0); - }; - - auto sortAfterDesc = [](const auto& a, const auto& b) { - return (naturalCompare(a.after, b.after) > 0); - }; - - if (col == 0) { - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortBeforeAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortBeforeDesc); - } - } else if (col == 1) { - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortFileAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortFileDesc); - } - } else if (col == 2) { - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortAfterAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortAfterDesc); - } - } - - emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); + auto* model = dynamic_cast(ui->overwrittenTree->model()); + if (!model) { + return; } - void add(Item item) - { - m_items.emplace_back(std::move(item)); + auto* item = model->getItem(static_cast(index.row())); + if (!item) { + return; } - void finished() - { - emit dataChanged(index(0, 0), index(0, rowCount())); + const auto origin = item->altOrigin(); + if (!origin.isEmpty()) { + emit modOpen(origin); } - -private: - std::vector m_items; -}; +} AdvancedConflictsTab::AdvancedConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) - : m_tab(tab), ui(pui), m_core(oc), m_model(new AdvancedListModel) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) : + m_tab(tab), ui(pui), m_core(oc), + m_model(new AdvancedConflictListModel(ui->conflictsAdvancedList)) { - ui->conflictsAdvancedList->setModel(m_model); - // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); @@ -868,9 +993,9 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); - //QObject::connect( - // ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, - // [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); + QObject::connect( + ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); m_filter.set(ui->conflictsAdvancedFilter); m_filter.changed = [&]{ update(); }; @@ -938,16 +1063,20 @@ void AdvancedConflictsTab::update() const int fileOrigin = file->getOrigin(archive); const auto& alternatives = file->getAlternatives(); - addItem( + auto item = createItem( file->getIndex(), fileOrigin, archive, fileName, relativeName, alternatives); + + if (item) { + m_model->add(std::move(*item)); + } } m_model->finished(); } } -void AdvancedConflictsTab::addItem( +std::optional AdvancedConflictsTab::createItem( FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) @@ -1040,7 +1169,7 @@ void AdvancedConflictsTab::addItem( // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it if (!hasAlts) { - return; + return {}; } } @@ -1052,9 +1181,8 @@ void AdvancedConflictsTab::addItem( }); if (!matched) { - return; + return {}; } - m_model->add( - {before, relativeName, after, index, fileName, hasAlts, "", archive}); + return ConflictItem{before, relativeName, after, index, fileName, hasAlts, "", archive}; } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index f499d1a4..f0c0c589 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -6,10 +6,12 @@ #include "filterwidget.h" #include "directoryentry.h" #include +#include class ConflictsTab; class OrganizerCore; class ConflictItem; +class ConflictListModel; class GeneralConflictsTab : public QObject { @@ -39,21 +41,25 @@ private: OrganizerCore& m_core; Expanders m_expanders; - QTreeWidgetItem* createOverwriteItem( + ConflictListModel* m_overwriteModel; + ConflictListModel* m_overwrittenModel; + ConflictListModel* m_noConflictModel; + + ConflictItem createOverwriteItem( MOShared::FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); - QTreeWidgetItem* createNoConflictItem( + ConflictItem createNoConflictItem( MOShared::FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName); - QTreeWidgetItem* createOverwrittenItem( + ConflictItem createOverwrittenItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName); - void onOverwriteActivated(QTreeWidgetItem* item, int); - void onOverwrittenActivated(QTreeWidgetItem *item, int); + void onOverwriteActivated(const QModelIndex& index); + void onOverwrittenActivated(const QModelIndex& index); void onOverwriteTreeContext(const QPoint &pos); void onOverwrittenTreeContext(const QPoint &pos); @@ -61,8 +67,6 @@ private: }; -class AdvancedListModel; - class AdvancedConflictsTab : public QObject { Q_OBJECT; @@ -85,9 +89,9 @@ private: Ui::ModInfoDialog* ui; OrganizerCore& m_core; FilterWidget m_filter; - AdvancedListModel* m_model; + ConflictListModel* m_model; - void addItem( + std::optional createItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); @@ -109,13 +113,12 @@ public: void restoreState(const Settings& s) override; bool canHandleUnmanaged() const override; - void openItems(const QList& items); - void previewItems(const QList& items); + void openItems(QTreeView* tree); + void previewItems(QTreeView* tree); - void changeItemsVisibility( - const QList& items, bool visible); + void changeItemsVisibility(QTreeView* tree, bool visible); - void showContextMenu(const QPoint &pos, QTreeWidget* tree); + void showContextMenu(const QPoint &pos, QTreeView* tree); private: struct Actions @@ -131,10 +134,8 @@ private: GeneralConflictsTab m_general; AdvancedConflictsTab m_advanced; - Actions createMenuActions(const QList& selection); - - std::vector createGotoActions( - const QList& selection); + Actions createMenuActions(QTreeView* tree); + std::vector createGotoActions(const ConflictItem* item); }; #endif // MODINFODIALOGCONFLICTS_H -- cgit v1.3.1 From 1ef0620f6e2d8a4e7d48b3b2b8eb96c3a8d3cd0d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 08:10:17 -0400 Subject: rework of the images tab to avoid using widgets at all, painting is done manually --- src/modinfodialog.ui | 19 +++- src/modinfodialogconflicts.cpp | 8 -- src/modinfodialogimages.cpp | 237 +++++++++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 71 ++++++++++-- 4 files changed, 268 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index cdf4c80f..bcdf1007 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -177,11 +177,8 @@ 0 - - - true - - + + 0 @@ -1163,6 +1160,18 @@ p, li { white-space: pre-wrap; } QTextEdit
    texteditor.h
    + + ImagesScrollArea + QScrollArea +
    modinfodialogimages.h
    + 1 +
    + + ImagesThumbnails + QWidget +
    modinfodialogimages.h
    + 1 +
    diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d1384d7a..05b69f05 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -887,7 +887,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( const FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString altString; for (const auto& alt : alternatives) { @@ -898,9 +897,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( altString += ToQString(ds.getOriginByID(alt.first).getName()); } - QStringList fields(relativeName); - fields.append(altString); - const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); return {altString, relativeName, "", index, fileName, true, origin, archive}; @@ -918,12 +914,8 @@ ConflictItem GeneralConflictsTab::createOverwrittenItem( const QString& fileName, const QString& relativeName) { const auto& ds = *m_core.directoryStructure(); - const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); - return { "", relativeName, ToQString(realOrigin.getName()), index, fileName, true, ToQString(realOrigin.getName()), archive}; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index f4cdade8..1f5b258c 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,25 +1,68 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" -ScalableImage::ScalableImage(QImage original) - : m_original(std::move(original)), m_border(1) +void ImagesScrollArea::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesScrollArea::resizeEvent(QResizeEvent* e) +{ + if (m_tab) { + m_tab->scrollAreaResized(e->size()); + } +} + + +void ImagesThumbnails::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesThumbnails::paintEvent(QPaintEvent* e) +{ + if (m_tab) { + m_tab->paintThumbnails(e); + } +} + +void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +{ + if (m_tab) { + m_tab->thumbnailsMouseEvent(e); + } +} + + +ScalableImage::ScalableImage(QString path) + : m_path(std::move(path)), m_border(1) { auto sp = sizePolicy(); sp.setHeightForWidth(true); setSizePolicy(sp); } +void ScalableImage::setImage(const QString& path) +{ + m_path = path; + m_original = {}; + m_scaled = {}; + + update(); +} + void ScalableImage::setImage(QImage image) { + m_path.clear(); m_original = std::move(image); m_scaled = {}; update(); } -const QImage& ScalableImage::image() const +void ScalableImage::clear() { - return m_original; + setImage(QImage()); } bool ScalableImage::hasHeightForWidth() const @@ -35,7 +78,15 @@ int ScalableImage::heightForWidth(int w) const void ScalableImage::paintEvent(QPaintEvent* e) { if (m_original.isNull()) { - return; + if (m_path.isNull()) { + return; + } + + m_original.load(m_path); + + if (m_original.isNull()) { + return; + } } const QRect widgetRect = rect(); @@ -72,83 +123,177 @@ void ScalableImage::paintEvent(QPaintEvent* e) painter.drawImage(drawImageRect, m_scaled); } -void ScalableImage::mousePressEvent(QMouseEvent* e) -{ - if (e->button() == Qt::LeftButton) { - emit clicked(m_original); - } -} - ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage) + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) { ui->imagesImage->layout()->addWidget(m_image); - ui->imagesThumbnails->setLayout(new QVBoxLayout); + delete ui->imagesThumbnails->layout(); ui->tabImagesSplitter->setSizes({128, 1}); ui->tabImagesSplitter->setStretchFactor(0, 0); ui->tabImagesSplitter->setStretchFactor(1, 1); + + ui->imagesScrollArea->setWidgetResizable(false); + ui->imagesScrollArea->setTab(this); + ui->imagesThumbnails->setTab(this); } void ImagesTab::clear() { - m_image->setImage({}); + m_image->clear(); + m_files.clear(); + setHasData(false); +} - while (ui->imagesThumbnails->layout()->count() > 0) { - auto* item = ui->imagesThumbnails->layout()->takeAt(0); - delete item->widget(); - delete item; +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + QImageReader reader(fullPath); + if (reader.canRead()) { + m_files.push_back({fullPath}); + return true; } - static_cast(ui->imagesThumbnails->layout())->addStretch(1); - setHasData(false); + return false; } -bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +void ImagesTab::update() +{ + setHasData(!m_files.empty()); +} + +void ImagesTab::scrollAreaResized(const QSize& s) { - static constexpr const char* extensions[] = { - ".png", ".jpg" - }; + const int availableWidth = s.width(); - for (const auto* e : extensions) { - if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - if (add(fullPath)) { - setHasData(true); + const int thumbSize = availableWidth - (m_margins * 2); + + int height = 0; + if (!m_files.empty()) { + height = thumbSize + static_cast((m_padding + thumbSize) * (m_files.size() - 1)); + } + + height += (m_margins * 2); + + qDebug() << "new size: " << availableWidth << "x" << height; + + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, height)); +} + +void ImagesTab::paintThumbnails(QPaintEvent* e) +{ + const auto availableRect = ui->imagesThumbnails->rect(); + const int thumbSize = availableRect.width() - (m_margins * 2); + + const QRect topRect( + availableRect.left() + m_margins, + availableRect.top() + m_margins, + thumbSize, thumbSize); + + const std::size_t begin = e->rect().top() / (thumbSize + m_padding); + const std::size_t end = e->rect().bottom() / (thumbSize + m_padding) + 1; + + QPainter painter(ui->imagesThumbnails); + + for (std::size_t i=begin; i(topRect.top() + (i * (thumbSize + m_padding))), + thumbSize, thumbSize); + + painter.setPen(QColor(Qt::black)); + painter.drawRect(borderRect); + + auto& file = m_files[i]; + if (file.failed) { + continue; + } + + const QRect thumbRect = borderRect.adjusted( + m_border, m_border, -m_border, -m_border); + + if (needsReload(file, thumbRect.size())) { + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + continue; } - return true; + file.thumbnail = file.original.scaled( + scaledImageSize(file.original.size(), thumbRect.size()), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - } - return false; + if (file.thumbnail.isNull()) { + continue; + } + + const QRect scaledThumbRect( + (thumbRect.left()+thumbRect.width()/2) - file.thumbnail.width()/2, + (thumbRect.top()+thumbRect.height()/2) - file.thumbnail.height()/2, + file.thumbnail.width(), file.thumbnail.height()); + + painter.drawImage(scaledThumbRect, file.thumbnail); + } } -bool ImagesTab::add(const QString& fullPath) +void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) { - QImage image = QImage(fullPath); + if (e->button() != Qt::LeftButton) { + return; + } - if (image.isNull()) { - qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; - return false; + const auto availableRect = ui->imagesThumbnails->rect(); + const int thumbSize = availableRect.width() - (m_margins * 2); + + const QRect topRect( + availableRect.left() + m_margins, + availableRect.top() + m_margins, + thumbSize, thumbSize); + + const std::size_t i = e->y() / (thumbSize + m_padding); + if (i >= m_files.size()) { + return; } - auto* thumbnail = new ScalableImage(std::move(image)); + if (e->x() < topRect.left() || e->x() > (topRect.right() + 1)) { + return; + } - QObject::connect( - thumbnail, &ScalableImage::clicked, - [&](const QImage& image){ onClicked(image); }); + m_image->setImage(m_files[i].original); +} - static_cast(ui->imagesThumbnails->layout())->insertWidget( - ui->imagesThumbnails->layout()->count() - 1, thumbnail); +bool ImagesTab::needsReload(const File& file, const QSize& thumbSize) const +{ + if (file.failed) { + return false; + } - return true; + if (file.original.isNull() || file.thumbnail.isNull()) { + return true; + } + + if (file.thumbnail.size() != scaledImageSize(file.original.size(), thumbSize)) { + return true; + } + + return false; } -void ImagesTab::onClicked(const QImage& original) +QSize ImagesTab::scaledImageSize( + const QSize& originalSize, const QSize& thumbSize) const { - m_image->setImage(original); + const auto ratio = std::min({ + 1.0, + static_cast(thumbSize.width()) / originalSize.width(), + static_cast(thumbSize.height()) / originalSize.height()}); + + const QSize scaledSize( + static_cast(std::round(originalSize.width() * ratio)), + static_cast(std::round(originalSize.height() * ratio))); + + return scaledSize; } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 6603660a..1c65d8bd 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -2,28 +2,62 @@ #define MODINFODIALOGIMAGES_H #include "modinfodialogtab.h" +#include + +class ImagesTab; + +class ImagesScrollArea : public QScrollArea +{ + Q_OBJECT; + +public: + using QScrollArea::QScrollArea; + void setTab(ImagesTab* tab); + +protected: + void resizeEvent(QResizeEvent* e) override; + +private: + ImagesTab* m_tab = nullptr; +}; + + +class ImagesThumbnails : public QWidget +{ + Q_OBJECT; + +public: + using QWidget::QWidget; + void setTab(ImagesTab* tab); + +protected: + void paintEvent(QPaintEvent* e) override; + void mousePressEvent(QMouseEvent* e) override; + +private: + ImagesTab* m_tab = nullptr; +}; + class ScalableImage : public QWidget { Q_OBJECT; public: - ScalableImage(QImage image={}); + ScalableImage(QString path={}); + void setImage(const QString& path); void setImage(QImage image); - const QImage& image() const; + void clear(); bool hasHeightForWidth() const; int heightForWidth(int w) const; -signals: - void clicked(const QImage& image); - protected: void paintEvent(QPaintEvent* e) override; - void mousePressEvent(QMouseEvent* e) override; private: + QString m_path; QImage m_original, m_scaled; int m_border; }; @@ -32,6 +66,8 @@ private: class ImagesTab : public ModInfoDialogTab { Q_OBJECT; + friend class ImagesScrollArea; + friend class ImagesThumbnails; public: ImagesTab( @@ -40,12 +76,31 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; + void update() override; private: + struct File + { + QString path; + QImage original, thumbnail; + bool failed = false; + + File(QString path) + : path(std::move(path)) + { + } + }; + ScalableImage* m_image; + std::vector m_files; + int m_margins, m_padding, m_border; + + void scrollAreaResized(const QSize& s); + void paintThumbnails(QPaintEvent* e); + void thumbnailsMouseEvent(QMouseEvent* e); - bool add(const QString& fullPath); - void onClicked(const QImage& image); + bool needsReload(const File& file, const QSize& thumbSize) const; + QSize scaledImageSize(const QSize& originalSize, const QSize& thumbSize) const; }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From 24b9f050c10d039be13a266d41088ae0fcc068f4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 08:30:11 -0400 Subject: check supported extensions instead of using QImageReader, less accurate but much faster --- src/modinfodialogimages.cpp | 21 +++++++++++++++++---- src/modinfodialogimages.h | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1f5b258c..4acc7838 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -140,6 +140,18 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setWidgetResizable(false); ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + + const auto list = QImageReader::supportedImageFormats(); + for (const auto& entry : list) { + QString s(entry); + if (!s.isEmpty()) { + if (s[0] != ".") { + s = "." + s; + } + + m_supportedFormats.emplace_back(std::move(s)); + } + } } void ImagesTab::clear() @@ -151,10 +163,11 @@ void ImagesTab::clear() bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) { - QImageReader reader(fullPath); - if (reader.canRead()) { - m_files.push_back({fullPath}); - return true; + for (const auto& ext : m_supportedFormats) { + if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { + m_files.push_back({fullPath}); + return true; + } } return false; diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 1c65d8bd..88e22a6a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -93,6 +93,7 @@ private: ScalableImage* m_image; std::vector m_files; + std::vector m_supportedFormats; int m_margins, m_padding, m_border; void scrollAreaResized(const QSize& s); -- cgit v1.3.1 From 9e3214a73a5cc38a9b4f0313d8a48c0374cb57a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 09:13:38 -0400 Subject: refactored calculations --- src/modinfodialogimages.cpp | 467 ++++++++++++++++++++++++++------------------ src/modinfodialogimages.h | 36 +++- 2 files changed, 313 insertions(+), 190 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4acc7838..5b35a9b1 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,6 +1,285 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +ImagesTab::ImagesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) +{ + ui->imagesImage->layout()->addWidget(m_image); + delete ui->imagesThumbnails->layout(); + + ui->tabImagesSplitter->setSizes({128, 1}); + ui->tabImagesSplitter->setStretchFactor(0, 0); + ui->tabImagesSplitter->setStretchFactor(1, 1); + + ui->imagesScrollArea->setWidgetResizable(false); + ui->imagesScrollArea->setTab(this); + ui->imagesThumbnails->setTab(this); + + getSupportedFormats(); +} + +void ImagesTab::clear() +{ + m_image->clear(); + m_files.clear(); + setHasData(false); +} + +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + for (const auto& ext : m_supportedFormats) { + if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { + m_files.push_back({fullPath}); + return true; + } + } + + return false; +} + +void ImagesTab::update() +{ + setHasData(!m_files.empty()); + resizeWidget(); + ui->imagesThumbnails->update(); +} + +void ImagesTab::getSupportedFormats() +{ + for (const auto& entry : QImageReader::supportedImageFormats()) { + QString s(entry); + if (s.isNull() || s.isEmpty()) { + continue; + } + + // make sure it starts with a dot + if (s[0] != ".") { + s = "." + s; + } + + m_supportedFormats.emplace_back(std::move(s)); + } +} + +int ImagesTab::calcThumbSize(int availableWidth) const +{ + return availableWidth - (m_margins * 2); +} + +int ImagesTab::calcWidgetHeight(int availableWidth) const +{ + if (m_files.empty()) { + return 0; + } + + const auto thumbSize = calcThumbSize(availableWidth); + + int h = 0; + + // first thumb + h = thumbSize; + + // subsequent thumbs with padding before each + const auto thumbWithPadding = m_padding + thumbSize; + h += static_cast(thumbWithPadding * (m_files.size() - 1)); + + // margin top and bottom + h += m_margins * 2; + + return h; +} + +QRect ImagesTab::calcTopThumbRect(int thumbSize) const +{ + return {m_margins, m_margins, thumbSize, thumbSize}; +} + +std::pair ImagesTab::calcVisibleRange( + int top, int bottom, int thumbSize) const +{ + const std::size_t begin = top / (thumbSize + m_padding); + const std::size_t end = bottom / (thumbSize + m_padding) + 1; + + return {begin, end}; +} + +QRect ImagesTab::calcBorderRect( + const QRect& topRect, int thumbSize, std::size_t i) const +{ + return { + topRect.left(), + static_cast(topRect.top() + (i * (thumbSize + m_padding))), + thumbSize, + thumbSize + }; +} + +QRect ImagesTab::calcImageRect( + const QRect& topRect, int thumbSize, std::size_t i) const +{ + return calcBorderRect(topRect, thumbSize, i).adjusted( + m_border, m_border, -m_border, -m_border); +} + +QSize ImagesTab::calcScaledImageSize( + const QSize& originalSize, const QSize& imageSize) const +{ + const auto ratio = std::min({ + 1.0, + static_cast(imageSize.width()) / originalSize.width(), + static_cast(imageSize.height()) / originalSize.height()}); + + const QSize scaledSize( + static_cast(std::round(originalSize.width() * ratio)), + static_cast(std::round(originalSize.height() * ratio))); + + return scaledSize; +} + +void ImagesTab::paintThumbnails(QPaintEvent* e) +{ + PaintContext cx(ui->imagesThumbnails); + cx.thumbSize = calcThumbSize(ui->imagesThumbnails->width()); + cx.topRect = calcTopThumbRect(cx.thumbSize); + + const auto [begin, end] = calcVisibleRange( + e->rect().top(), e->rect().bottom(), cx.thumbSize); + + for (std::size_t i=begin; iimagesThumbnails->width()); + + // calculate index purely based on y position + const std::size_t i = p.y() / (thumbSize + m_padding); + if (i >= m_files.size()) { + return nullptr; + } + + // get actual rect + const auto topRect = calcTopThumbRect(thumbSize); + const auto rect = calcBorderRect(topRect, thumbSize, i); + + if (!rect.contains(p)) { + return nullptr; + } + + return &m_files[i]; +} + +void ImagesTab::scrollAreaResized(const QSize&) +{ + resizeWidget(); +} + +void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) +{ + if (e->button() != Qt::LeftButton) { + return; + } + + if (const auto* file=fileAtPos(e->pos())) { + m_image->setImage(file->original); + } +} + +bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const +{ + if (file.failed) { + return false; + } + + if (file.original.isNull() || file.thumbnail.isNull()) { + return true; + } + + const auto scaledSize = calcScaledImageSize(file.original.size(), imageSize); + if (file.thumbnail.size() != scaledSize) { + return true; + } + + return false; +} + +void ImagesTab::reload(File& file, const QSize& scaledSize) +{ + file.original = {}; + file.thumbnail = {}; + file.failed = false; + + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + return; + } + + file.thumbnail = file.original.scaled( + calcScaledImageSize(file.original.size(), scaledSize), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + +void ImagesTab::resizeWidget() +{ + if (m_files.empty()) { + ui->imagesThumbnails->setGeometry(QRect()); + return; + } + + const auto availableWidth = ui->imagesScrollArea->viewport()->width(); + + const int widgetHeight = calcWidgetHeight(availableWidth); + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); +} + + void ImagesScrollArea::setTab(ImagesTab* tab) { m_tab = tab; @@ -122,191 +401,3 @@ void ScalableImage::paintEvent(QPaintEvent* e) painter.drawRect(drawBorderRect); painter.drawImage(drawImageRect, m_scaled); } - - -ImagesTab::ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) -{ - ui->imagesImage->layout()->addWidget(m_image); - delete ui->imagesThumbnails->layout(); - - ui->tabImagesSplitter->setSizes({128, 1}); - ui->tabImagesSplitter->setStretchFactor(0, 0); - ui->tabImagesSplitter->setStretchFactor(1, 1); - - ui->imagesScrollArea->setWidgetResizable(false); - ui->imagesScrollArea->setTab(this); - ui->imagesThumbnails->setTab(this); - - const auto list = QImageReader::supportedImageFormats(); - for (const auto& entry : list) { - QString s(entry); - if (!s.isEmpty()) { - if (s[0] != ".") { - s = "." + s; - } - - m_supportedFormats.emplace_back(std::move(s)); - } - } -} - -void ImagesTab::clear() -{ - m_image->clear(); - m_files.clear(); - setHasData(false); -} - -bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) -{ - for (const auto& ext : m_supportedFormats) { - if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { - m_files.push_back({fullPath}); - return true; - } - } - - return false; -} - -void ImagesTab::update() -{ - setHasData(!m_files.empty()); -} - -void ImagesTab::scrollAreaResized(const QSize& s) -{ - const int availableWidth = s.width(); - - const int thumbSize = availableWidth - (m_margins * 2); - - int height = 0; - if (!m_files.empty()) { - height = thumbSize + static_cast((m_padding + thumbSize) * (m_files.size() - 1)); - } - - height += (m_margins * 2); - - qDebug() << "new size: " << availableWidth << "x" << height; - - ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, height)); -} - -void ImagesTab::paintThumbnails(QPaintEvent* e) -{ - const auto availableRect = ui->imagesThumbnails->rect(); - const int thumbSize = availableRect.width() - (m_margins * 2); - - const QRect topRect( - availableRect.left() + m_margins, - availableRect.top() + m_margins, - thumbSize, thumbSize); - - const std::size_t begin = e->rect().top() / (thumbSize + m_padding); - const std::size_t end = e->rect().bottom() / (thumbSize + m_padding) + 1; - - QPainter painter(ui->imagesThumbnails); - - for (std::size_t i=begin; i(topRect.top() + (i * (thumbSize + m_padding))), - thumbSize, thumbSize); - - painter.setPen(QColor(Qt::black)); - painter.drawRect(borderRect); - - auto& file = m_files[i]; - if (file.failed) { - continue; - } - - const QRect thumbRect = borderRect.adjusted( - m_border, m_border, -m_border, -m_border); - - if (needsReload(file, thumbRect.size())) { - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - continue; - } - - file.thumbnail = file.original.scaled( - scaledImageSize(file.original.size(), thumbRect.size()), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - - if (file.thumbnail.isNull()) { - continue; - } - - const QRect scaledThumbRect( - (thumbRect.left()+thumbRect.width()/2) - file.thumbnail.width()/2, - (thumbRect.top()+thumbRect.height()/2) - file.thumbnail.height()/2, - file.thumbnail.width(), file.thumbnail.height()); - - painter.drawImage(scaledThumbRect, file.thumbnail); - } -} - -void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) -{ - if (e->button() != Qt::LeftButton) { - return; - } - - const auto availableRect = ui->imagesThumbnails->rect(); - const int thumbSize = availableRect.width() - (m_margins * 2); - - const QRect topRect( - availableRect.left() + m_margins, - availableRect.top() + m_margins, - thumbSize, thumbSize); - - const std::size_t i = e->y() / (thumbSize + m_padding); - if (i >= m_files.size()) { - return; - } - - if (e->x() < topRect.left() || e->x() > (topRect.right() + 1)) { - return; - } - - m_image->setImage(m_files[i].original); -} - -bool ImagesTab::needsReload(const File& file, const QSize& thumbSize) const -{ - if (file.failed) { - return false; - } - - if (file.original.isNull() || file.thumbnail.isNull()) { - return true; - } - - if (file.thumbnail.size() != scaledImageSize(file.original.size(), thumbSize)) { - return true; - } - - return false; -} - -QSize ImagesTab::scaledImageSize( - const QSize& originalSize, const QSize& thumbSize) const -{ - const auto ratio = std::min({ - 1.0, - static_cast(thumbSize.width()) / originalSize.width(), - static_cast(thumbSize.height()) / originalSize.height()}); - - const QSize scaledSize( - static_cast(std::round(originalSize.width() * ratio)), - static_cast(std::round(originalSize.height() * ratio))); - - return scaledSize; -} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 88e22a6a..9c3e5746 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -91,17 +91,49 @@ private: } }; + struct PaintContext + { + QPainter painter; + int thumbSize; + QRect topRect; + + PaintContext(QWidget* w) + : painter(w), thumbSize(0) + { + } + }; + ScalableImage* m_image; std::vector m_files; std::vector m_supportedFormats; int m_margins, m_padding, m_border; + void getSupportedFormats(); + void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); - bool needsReload(const File& file, const QSize& thumbSize) const; - QSize scaledImageSize(const QSize& originalSize, const QSize& thumbSize) const; + int calcThumbSize(int availableWidth) const; + int calcWidgetHeight(int availableWidth) const; + QRect calcTopThumbRect(int thumbSize) const; + std::pair calcVisibleRange( + int top, int bottom, int thumbSize) const; + + QRect calcBorderRect(const QRect& topRect, int thumbSize, std::size_t i) const; + QRect calcImageRect(const QRect& topRect, int thumbSize, std::size_t i) const; + QSize calcScaledImageSize( + const QSize& originalSize, const QSize& imageSize) const; + + void paintThumbnail(PaintContext& cx, std::size_t i); + void paintThumbnailBorder(PaintContext& cx, std::size_t i); + void paintThumbnailImage(PaintContext& cx, std::size_t i); + + const File* fileAtPos(const QPoint& p) const; + + bool needsReload(const File& file, const QSize& imageSize) const; + void reload(File& file, const QSize& imageSize); + void resizeWidget(); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From b6d8f4e479e42e401be60f522a0496c1f5624089 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:16:12 -0400 Subject: custom models for esp lists --- src/modinfodialog.ui | 4 +- src/modinfodialogconflicts.cpp | 2 +- src/modinfodialogesps.cpp | 245 +++++++++++++++++++++++++++++------------ src/modinfodialogesps.h | 9 +- src/modinfodialogtextfiles.cpp | 3 +- 5 files changed, 184 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index bcdf1007..a152c292 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -244,7 +244,7 @@
    - + List of esps, esms, and esls that can not be loaded by the game. @@ -374,7 +374,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + ESPs in the data directory and thus visible to the game. diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 05b69f05..03589030 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -135,6 +135,7 @@ public: void clear() { m_items.clear(); + endResetModel(); } void reserve(std::size_t s) @@ -261,7 +262,6 @@ public: void finished() { - beginResetModel(); endResetModel(); } diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 6c4fc4dd..3f742bae 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -1,13 +1,14 @@ #include "modinfodialogesps.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include using MOBase::reportError; -class ESP +class ESPItem { public: - ESP(QString rootPath, QString relativePath) + ESPItem(QString rootPath, QString relativePath) : m_rootPath(std::move(rootPath)), m_active(false) { if (relativePath.contains('/')) { @@ -16,6 +17,8 @@ public: m_activePath = relativePath; m_active = true; } + + updateFilename(); } const QString& rootPath() const @@ -32,6 +35,11 @@ public: } } + const QString& filename() const + { + return m_filename; + } + const QString& activePath() const { return m_activePath; @@ -65,6 +73,8 @@ public: m_inactivePath = QFileInfo(m_inactivePath).path() + QDir::separator() + newName; } + updateFilename(); + return true; } @@ -78,6 +88,7 @@ public: if (root.rename(m_activePath, newName)) { m_active = false; m_inactivePath = newName; + updateFilename(); return true; } @@ -88,45 +99,137 @@ private: QString m_rootPath; QString m_activePath; QString m_inactivePath; + QString m_filename; bool m_active; + + void updateFilename() + { + m_filename = fileInfo().fileName(); + } }; -class ESPItem : public QListWidgetItem +class ESPListModel : public QAbstractItemModel { public: - ESPItem(ESP esp) - : m_esp(std::move(esp)) + void clear() { - updateText(); + m_esps.clear(); + endResetModel(); } - const ESP& esp() const + QModelIndex index(int row, int col, const QModelIndex& ={}) const override { - return m_esp; + return createIndex(row, col); } - void setESP(ESP esp) + QModelIndex parent(const QModelIndex&) const override { - m_esp = esp; - updateText(); + return {}; } -private: - ESP m_esp; + int rowCount(const QModelIndex& ={}) const override + { + return static_cast(m_esps.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return 1; + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole) { + if (auto* esp=getESP(index)) { + return esp->filename(); + } + } + + return {}; + } + + + void add(ESPItem esp) + { + m_esps.emplace_back(std::move(esp)); + } + + void addOne(ESPItem esp) + { + const auto i = m_esps.size(); + + beginInsertRows({}, static_cast(i), static_cast(i)); + add(std::move(esp)); + endInsertRows(); + } + + bool removeRows(int row, int count, const QModelIndex& = {}) override + { + if (row < 0) { + return false; + } + + const auto start = static_cast(row); + if (start >= m_esps.size()) { + return false; + } + + const auto end = std::min( + start + static_cast(count), + m_esps.size()); + + beginRemoveRows({}, static_cast(start), static_cast(end)); + m_esps.erase(m_esps.begin() + start, m_esps.begin() + end); + endRemoveRows(); + + return true; + } - void updateText() + void finished() { - setText(m_esp.fileInfo().fileName()); + std::sort(m_esps.begin(), m_esps.end(), [](const auto& a, const auto& b) { + return (naturalCompare(a.filename(), b.filename()) < 0); + }); + + endResetModel(); + } + + const ESPItem* getESP(const QModelIndex& index) const + { + const auto row = index.row(); + if (row < 0) { + return nullptr; + } + + const auto i = static_cast(row); + if (i >= m_esps.size()) { + return nullptr; + } + + return &m_esps[i]; } + + ESPItem* getESP(const QModelIndex& index) + { + return const_cast(std::as_const(*this).getESP(index)); + } + +private: + std::deque m_esps; }; + ESPsTab::ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), + m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) { + ui->inactiveESPList->setModel(m_inactiveModel); + ui->activeESPList->setModel(m_activeModel); + QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); @@ -136,8 +239,8 @@ ESPsTab::ESPsTab( void ESPsTab::clear() { - ui->inactiveESPList->clear(); - ui->activeESPList->clear(); + m_inactiveModel->clear(); + m_activeModel->clear(); setHasData(false); } @@ -151,12 +254,12 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) if (fullPath.endsWith(e, Qt::CaseInsensitive)) { QString relativePath = fullPath.mid(rootPath.length() + 1); - auto* item = new ESPItem({rootPath, relativePath}); + ESPItem esp(rootPath, relativePath); - if (item->esp().isActive()) { - ui->activeESPList->addItem(item); + if (esp.isActive()) { + m_activeModel->add(std::move(esp)); } else { - ui->inactiveESPList->addItem(item); + m_inactiveModel->add(std::move(esp)); } setHasData(true); @@ -167,23 +270,31 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) return false; } +void ESPsTab::update() +{ + m_inactiveModel->finished(); + m_activeModel->finished(); +} + void ESPsTab::onActivate() { - auto* item = selectedInactive(); - if (!item) { - qWarning("ESPsTab::onActivate(): no selection"); + const auto index = ui->inactiveESPList->currentIndex(); + if (!index.isValid()) { return; } - ESP esp = item->esp(); + auto* esp = m_inactiveModel->getESP(index); + if (!esp) { + return; + } - if (esp.isActive()) { + if (esp->isActive()) { qWarning("ESPsTab::onActive(): item is already active"); return; } - QDir root(esp.rootPath()); - const QFileInfo file(esp.fileInfo()); + QDir root(esp->rootPath()); + const QFileInfo file(esp->fileInfo()); QString newName = file.fileName(); @@ -205,10 +316,12 @@ void ESPsTab::onActivate() } } - if (esp.activate(newName)) { - ui->inactiveESPList->takeItem(ui->inactiveESPList->row(item)); - ui->activeESPList->addItem(item); - item->setESP(esp); + if (esp->activate(newName)) { + // copy esp, original will be destroyed + auto copy = *esp; + m_inactiveModel->removeRow(index.row()); + m_activeModel->addOne(std::move(copy)); + selectRow(ui->inactiveESPList, index.row()); } else { reportError(QObject::tr("Failed to move file")); } @@ -216,26 +329,28 @@ void ESPsTab::onActivate() void ESPsTab::onDeactivate() { - auto* item = selectedActive(); - if (!item) { - qWarning("ESPsTab::onDeactivate(): no selection"); + const auto index = ui->activeESPList->currentIndex(); + if (!index.isValid()) { return; } - ESP esp = item->esp(); + auto* esp = m_activeModel->getESP(index); + if (!esp) { + return; + } - if (!esp.isActive()) { + if (!esp->isActive()) { qWarning("ESPsTab::onDeactivate(): item is already inactive"); return; } - QDir root(esp.rootPath()); + QDir root(esp->rootPath()); // if we moved the file from optional to active in this session, we move the // file back to where it came from. Otherwise, it is moved to the new folder // "optional" - QString newName = esp.inactivePath(); + QString newName = esp->inactivePath(); if (newName.isEmpty()) { if (!root.exists("optional")) { @@ -245,46 +360,32 @@ void ESPsTab::onDeactivate() } } - newName = QString("optional") + QDir::separator() + esp.fileInfo().fileName(); + newName = QString("optional") + QDir::separator() + esp->fileInfo().fileName(); } - if (esp.deactivate(newName)) { - ui->activeESPList->takeItem(ui->activeESPList->row(item)); - ui->inactiveESPList->addItem(item); - item->setESP(esp); + if (esp->deactivate(newName)) { + // copy esp, original will be destroyed + auto copy = *esp; + + m_activeModel->removeRow(index.row()); + m_inactiveModel->addOne(std::move(copy)); + selectRow(ui->activeESPList, index.row()); } else { reportError(QObject::tr("Failed to move file")); } } -ESPItem* ESPsTab::selectedInactive() -{ - auto* item = ui->inactiveESPList->currentItem(); - if (!item) { - return nullptr; - } - - auto* esp = dynamic_cast(item); - if (!esp) { - qCritical("ESPsTab: inactive item is not an ESPItem"); - return nullptr; - } - - return esp; -} - -ESPItem* ESPsTab::selectedActive() +void ESPsTab::selectRow(QListView* list, int row) { - auto* item = ui->activeESPList->currentItem(); - if (!item) { - return nullptr; + const auto* model = list->model(); + const auto count = model->rowCount(); + if (count == 0) { + return; } - auto* esp = dynamic_cast(item); - if (!esp) { - qCritical("ESPsTab: active item is not an ESPItem"); - return nullptr; + if (row >= count) { + list->setCurrentIndex(model->index(count - 1, 0)); + } else { + list->setCurrentIndex(model->index(row, 0)); } - - return esp; } diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index e82ed368..c972074a 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -4,6 +4,7 @@ #include "modinfodialogtab.h" class ESPItem; +class ESPListModel; class ESPsTab : public ModInfoDialogTab { @@ -16,13 +17,15 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; + void update(); private: + ESPListModel* m_inactiveModel; + ESPListModel* m_activeModel; + void onActivate(); void onDeactivate(); - - ESPItem* selectedInactive(); - ESPItem* selectedActive(); + void selectRow(QListView* list, int row); }; #endif // MODINFODIALOGESPS_H diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 08b8c988..25d799b1 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -9,6 +9,7 @@ public: void clear() { m_files.clear(); + endResetModel(); } QModelIndex index(int row, int col, const QModelIndex& ={}) const override @@ -62,7 +63,7 @@ public: return (naturalCompare(a.text, b.text) < 0); }); - emit dataChanged(index(0, 0), index(0, rowCount())); + endResetModel(); } QString fullPath(const QModelIndex& index) const -- cgit v1.3.1 From 86e4f65eb6a2e52d0513a8c2fe1e94cd1af9967d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:16:38 -0400 Subject: changed vsdark styles to remove left and right padding from list items --- src/stylesheets/vs15 Dark-Green.qss | 4 +++- src/stylesheets/vs15 Dark-Orange.qss | 4 +++- src/stylesheets/vs15 Dark-Purple.qss | 4 +++- src/stylesheets/vs15 Dark-Red.qss | 4 +++- src/stylesheets/vs15 Dark-Yellow.qss | 4 +++- src/stylesheets/vs15 Dark.qss | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 9c57d96e..9cf26a31 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -49,7 +49,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index bf7647b2..041f1b00 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index 1c9cb2b8..bc8bbcde 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 73a90b15..0c9143cd 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index 1d43091d..2eb42534 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index dbeaaf28..d67cce35 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -49,7 +49,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, -- cgit v1.3.1 From 139c33ccc4f529083b0288907caad2946a3f5a8f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 11:41:00 -0400 Subject: various optimizations and caching fixed conflict list not sorting when changing parameters switched from QDirIterator to std::filesystem, much faster FilterWidget precompiles the list --- src/filterwidget.cpp | 19 +++-- src/filterwidget.h | 3 + src/modinfodialog.cpp | 12 ++- src/modinfodialogconflicts.cpp | 186 ++++++++++++++++++++++++----------------- src/modinfodialogconflicts.h | 8 +- src/modinfodialogesps.cpp | 26 +++--- src/modinfodialogtextfiles.cpp | 15 ++-- 7 files changed, 155 insertions(+), 114 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 16a46b0e..82644658 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -30,21 +30,29 @@ void FilterWidget::clear() m_edit->clear(); } -bool FilterWidget::matches(std::function pred) const +void FilterWidget::compile() { + m_compiled.clear(); + const QStringList ORList = [&] { QString filterCopy = QString(m_text); filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); return filterCopy.split(";", QString::SkipEmptyParts); }(); - if (ORList.isEmpty() || !pred) { + // split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + m_compiled.push_back(ORSegment.split(" ", QString::SkipEmptyParts)); + } +} + +bool FilterWidget::matches(std::function pred) const +{ + if (m_compiled.isEmpty() || !pred) { return true; } - // split in ORSegments that internally use AND logic - for (auto& ORSegment : ORList) { - QStringList ANDKeywords = ORSegment.split(" ", QString::SkipEmptyParts); + for (auto& ANDKeywords : m_compiled) { bool segmentGood = true; // check each word in the segment for match, each word needs to be matched @@ -115,6 +123,7 @@ void FilterWidget::onTextChanged() if (text != m_text) { m_text = text; + compile(); if (changed) { changed(); diff --git a/src/filterwidget.h b/src/filterwidget.h index 4fee5983..762d9b15 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -20,6 +20,7 @@ private: EventFilter* m_eventFilter; QToolButton* m_clear; QString m_text; + QList> m_compiled; void unhook(); void createClear(); @@ -28,6 +29,8 @@ private: void onTextChanged(); void onResized(); + + void compile(); }; #endif // FILTERWIDGET_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a407e9b7..c6cdb961 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogcategories.h" #include "modinfodialognexus.h" #include "modinfodialogfiletree.h" +#include using namespace MOBase; using namespace MOShared; @@ -310,12 +311,17 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) void ModInfoDialog::feedFiles(bool becauseOriginChanged) { + namespace fs = std::filesystem; 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 (const auto& entry : fs::recursive_directory_iterator(rootPath.toStdString())) { + if (!entry.is_regular_file()) { + continue; + } + + const auto fileName = QString::fromWCharArray(entry.path().c_str()); for (auto& tabInfo : m_tabs) { if (!tabInfo.isVisible()) { diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 03589030..52d25954 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -126,8 +126,9 @@ public: const QString& (ConflictItem::*getText)() const; }; - ConflictListModel(QTreeView* tree, std::vector columns) - : m_tree(tree), m_columns(std::move(columns)) + ConflictListModel(QTreeView* tree, std::vector columns) : + m_tree(tree), m_columns(std::move(columns)), + m_sortColumn(-1), m_sortOrder(Qt::AscendingOrder) { m_tree->setModel(this); } @@ -226,32 +227,10 @@ public: void sort(int colIndex, Qt::SortOrder order=Qt::AscendingOrder) { - if (colIndex < 0) { - return; - } - - const auto c = static_cast(colIndex); - if (c >= m_columns.size()) { - return; - } - - const auto& col = m_columns[c]; - - // avoids branching on sort order while sorting - auto sortAsc = [&](const auto& a, const auto& b) { - return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); - }; - - auto sortDesc = [&](const auto& a, const auto& b) { - return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); - }; - - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortDesc); - } + m_sortColumn = colIndex; + m_sortOrder = order; + doSort(); emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); } @@ -263,6 +242,7 @@ public: void finished() { endResetModel(); + doSort(); } const ConflictItem* getItem(std::size_t row) const @@ -278,6 +258,37 @@ private: QTreeView* m_tree; std::vector m_columns; std::vector m_items; + int m_sortColumn; + Qt::SortOrder m_sortOrder; + + void doSort() + { + if (m_sortColumn < 0) { + return; + } + + const auto c = static_cast(m_sortColumn); + if (c >= m_columns.size()) { + return; + } + + const auto& col = m_columns[c]; + + // avoids branching on sort order while sorting + auto sortAsc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); + }; + + auto sortDesc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); + }; + + if (m_sortOrder == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortDesc); + } + } }; @@ -841,8 +852,9 @@ bool GeneralConflictsTab::update() const auto rootPath = m_tab->mod()->absolutePath(); for (const auto& file : m_tab->origin()->getFiles()) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(rootPath); + // careful: these two strings are moved into createXItem() below + QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString fileName = rootPath + relativeName; bool archive = false; const int fileOrigin = file->getOrigin(archive); @@ -851,28 +863,31 @@ bool GeneralConflictsTab::update() if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { m_overwriteModel->add(createOverwriteItem( - file->getIndex(), archive, fileName, relativeName, alternatives)); + file->getIndex(), archive, + std::move(fileName), std::move(relativeName), alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree m_noConflictModel->add(createNoConflictItem( - file->getIndex(), archive, fileName, relativeName)); + file->getIndex(), archive, + std::move(fileName), std::move(relativeName))); ++numNonConflicting; } } else { m_overwrittenModel->add(createOverwrittenItem( - file->getIndex(), fileOrigin, archive, fileName, relativeName)); + file->getIndex(), fileOrigin, archive, + std::move(fileName), std::move(relativeName))); ++numOverwritten; } } - } - m_overwriteModel->finished(); - m_overwrittenModel->finished(); - m_noConflictModel->finished(); + m_overwriteModel->finished(); + m_overwrittenModel->finished(); + m_noConflictModel->finished(); + } ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); @@ -882,43 +897,48 @@ bool GeneralConflictsTab::update() } ConflictItem GeneralConflictsTab::createOverwriteItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, + FileEntry::Index index, bool archive, QString fileName, QString relativeName, const FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString altString; + std::wstring altString; for (const auto& alt : alternatives) { - if (!altString.isEmpty()) { - altString += ", "; + if (!altString.empty()) { + altString += L", "; } - altString += ToQString(ds.getOriginByID(alt.first).getName()); + altString += ds.getOriginByID(alt.first).getName(); } - const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); + auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); - return {altString, relativeName, "", index, fileName, true, origin, archive}; + return ConflictItem( + ToQString(altString), std::move(relativeName), QString::null, index, + std::move(fileName), true, std::move(origin), archive); } ConflictItem GeneralConflictsTab::createNoConflictItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName) + FileEntry::Index index, bool archive, QString fileName, QString relativeName) { - return {"", relativeName, "", index, fileName, false, "", archive}; + return ConflictItem( + QString::null, std::move(relativeName), QString::null, index, + std::move(fileName), false, QString::null, archive); } ConflictItem GeneralConflictsTab::createOverwrittenItem( FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName) + QString fileName, QString relativeName) { const auto& ds = *m_core.directoryStructure(); - const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin); - return { - "", relativeName, ToQString(realOrigin.getName()), - index, fileName, true, ToQString(realOrigin.getName()), archive}; + QString after = ToQString(realOrigin.getName()); + QString altOrigin = after; + + return ConflictItem( + QString::null, std::move(relativeName), std::move(after), + index, std::move(fileName), true, std::move(altOrigin), archive); } void GeneralConflictsTab::onOverwriteActivated(const QModelIndex& index) @@ -1048,8 +1068,9 @@ void AdvancedConflictsTab::update() m_model->reserve(files.size()); for (const auto& file : files) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(rootPath); + // careful: these two strings are moved into createItem() below + QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString fileName = rootPath + relativeName; bool archive = false; const int fileOrigin = file->getOrigin(archive); @@ -1057,7 +1078,7 @@ void AdvancedConflictsTab::update() auto item = createItem( file->getIndex(), fileOrigin, archive, - fileName, relativeName, alternatives); + std::move(fileName), std::move(relativeName), alternatives); if (item) { m_model->add(std::move(*item)); @@ -1070,39 +1091,41 @@ void AdvancedConflictsTab::update() std::optional AdvancedConflictsTab::createItem( FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString before, after; + std::wstring before, after; if (!alternatives.empty()) { + const bool showAllAlts = ui->conflictsAdvancedShowAll->isChecked(); + int beforePrio = 0; int afterPrio = std::numeric_limits::max(); for (const auto& alt : alternatives) { - const auto altOrigin = ds.getOriginByID(alt.first); + const auto& altOrigin = ds.getOriginByID(alt.first); - if (ui->conflictsAdvancedShowAll->isChecked()) { + if (showAllAlts) { // fills 'before' and 'after' with all the alternatives that come // before and after this mod in terms of priority if (altOrigin.getPriority() < m_tab->origin()->getPriority()) { // add all the mods having a lower priority than this one - if (!before.isEmpty()) { - before += ", "; + if (!before.empty()) { + before += L", "; } - before += ToQString(altOrigin.getName()); + before += altOrigin.getName(); } else if (altOrigin.getPriority() > m_tab->origin()->getPriority()) { // add all the mods having a higher priority than this one - if (!after.isEmpty()) { - after += ", "; + if (!after.empty()) { + after += L", "; } - after += ToQString(altOrigin.getName()); + after += altOrigin.getName(); } } else { // keep track of the nearest mods that come before and after this one @@ -1114,7 +1137,7 @@ std::optional AdvancedConflictsTab::createItem( if (altOrigin.getPriority() > beforePrio) { // the alternative has a higher priority and therefore is closer // to this mod, use it - before = ToQString(altOrigin.getName()); + before = altOrigin.getName(); beforePrio = altOrigin.getPriority(); } } @@ -1125,7 +1148,7 @@ std::optional AdvancedConflictsTab::createItem( if (altOrigin.getPriority() < afterPrio) { // the alternative has a lower priority and there is closer // to this mod, use it - after = ToQString(altOrigin.getName()); + after = altOrigin.getName(); afterPrio = altOrigin.getPriority(); } } @@ -1140,41 +1163,46 @@ std::optional AdvancedConflictsTab::createItem( // nearest mods, it's not worth checking for the primary origin because it // will always have a higher priority than the alternatives (or it wouldn't // be the primary) - if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { - FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + if (after.empty() || showAllAlts) { + const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin); // if no mods overwrite this file, the primary origin is the same as this // mod, so ignore that if (realOrigin.getID() != m_tab->origin()->getID()) { - if (!after.isEmpty()) { - after += ", "; + if (!after.empty()) { + after += L", "; } - after += ToQString(realOrigin.getName()); + after += realOrigin.getName(); } } } - bool hasAlts = !before.isEmpty() || !after.isEmpty(); + const bool hasAlts = !before.empty() || !after.empty(); - if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { + if (!hasAlts) { // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it - if (!hasAlts) { + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { return {}; } } - bool matched = m_filter.matches([&](auto&& what) { + auto beforeQS = QString::fromStdWString(before); + auto afterQS = QString::fromStdWString(after); + + const bool matched = m_filter.matches([&](auto&& what) { return - before.contains(what, Qt::CaseInsensitive) || + beforeQS.contains(what, Qt::CaseInsensitive) || relativeName.contains(what, Qt::CaseInsensitive) || - after.contains(what, Qt::CaseInsensitive); + afterQS.contains(what, Qt::CaseInsensitive); }); if (!matched) { return {}; } - return ConflictItem{before, relativeName, after, index, fileName, hasAlts, "", archive}; + return ConflictItem( + std::move(beforeQS), std::move(relativeName), std::move(afterQS), + index, std::move(fileName), hasAlts, QString::null, archive); } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index f0c0c589..fc7dd32a 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -47,16 +47,16 @@ private: ConflictItem createOverwriteItem( MOShared::FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); ConflictItem createNoConflictItem( MOShared::FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName); + QString fileName, QString relativeName); ConflictItem createOverwrittenItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName); + QString fileName, QString relativeName); void onOverwriteActivated(const QModelIndex& index); void onOverwrittenActivated(const QModelIndex& index); @@ -93,7 +93,7 @@ private: std::optional createItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); }; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 3f742bae..ecb341e8 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -18,7 +18,7 @@ public: m_active = true; } - updateFilename(); + pathChanged(); } const QString& rootPath() const @@ -50,9 +50,9 @@ public: return m_inactivePath; } - QFileInfo fileInfo() const + const QFileInfo& fileInfo() const { - return m_rootPath + QDir::separator() + relativePath(); + return m_fileInfo; } bool isActive() const @@ -73,7 +73,7 @@ public: m_inactivePath = QFileInfo(m_inactivePath).path() + QDir::separator() + newName; } - updateFilename(); + pathChanged(); return true; } @@ -88,7 +88,7 @@ public: if (root.rename(m_activePath, newName)) { m_active = false; m_inactivePath = newName; - updateFilename(); + pathChanged(); return true; } @@ -100,11 +100,13 @@ private: QString m_activePath; QString m_inactivePath; QString m_filename; + QFileInfo m_fileInfo; bool m_active; - void updateFilename() + void pathChanged() { - m_filename = fileInfo().fileName(); + m_fileInfo.setFile(m_rootPath + QDir::separator() + relativePath()); + m_filename = m_fileInfo.fileName(); } }; @@ -246,15 +248,11 @@ void ESPsTab::clear() bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) { - static constexpr const char* extensions[] = { - ".esp", ".esm", ".esl" - }; + static const QString extensions[] = {".esp", ".esm", ".esl"}; - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - QString relativePath = fullPath.mid(rootPath.length() + 1); - - ESPItem esp(rootPath, relativePath); + ESPItem esp(rootPath, fullPath.mid(rootPath.length() + 1)); if (esp.isActive()) { m_activeModel->add(std::move(esp)); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 25d799b1..675a4c79 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -202,11 +202,9 @@ TextFilesTab::TextFilesTab( bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { - static constexpr const char* extensions[] = { - ".txt" - }; + static const QString extensions[] = {".txt"}; - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { return true; } @@ -226,13 +224,12 @@ IniFilesTab::IniFilesTab( bool IniFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { - static constexpr const char* extensions[] = { - ".ini", ".cfg" - }; + static const QString extensions[] = {".ini", ".cfg"}; + static const QString meta("meta.ini"); - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - if (!fullPath.endsWith("meta.ini")) { + if (!fullPath.endsWith(meta)) { return true; } } -- cgit v1.3.1 From 2b57172eb8ab23f2fab580be73c570f44500ed8d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 12:26:25 -0400 Subject: added path and explore button to images tab --- src/modinfodialog.h | 14 ++++++++++++++ src/modinfodialog.ui | 44 +++++++++++++++++++++++++++++++++++++++--- src/modinfodialogconflicts.cpp | 18 ++++------------- src/modinfodialogimages.cpp | 39 +++++++++++++++++++++++++++++++++---- src/modinfodialogimages.h | 3 +++ 5 files changed, 97 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.h b/src/modinfodialog.h index e814dcf4..8ddaf86c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -45,6 +45,20 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa 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 diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a152c292..9f4ae873 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -192,8 +192,11 @@ - - + + + + 3 + 0 @@ -206,6 +209,41 @@ 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Open in Explorer + + + + + + + true + + + + + + + + + @@ -403,7 +441,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 0 + 1 diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 52d25954..c053e64c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -13,20 +13,6 @@ using namespace MOBase; const std::size_t max_small_selection = 50; -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; - } -}; - - class ConflictItem { public: @@ -263,6 +249,10 @@ private: void doSort() { + if (m_items.empty()) { + return; + } + if (m_sortColumn < 0) { return; } diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 5b35a9b1..22c004e6 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,13 +1,18 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +#include "utility.h" ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), + m_selection(nullptr) { - ui->imagesImage->layout()->addWidget(m_image); + auto* ly = new QVBoxLayout(ui->imagesImage); + ly->setContentsMargins({0, 0, 0, 0}); + ly->addWidget(m_image); + delete ui->imagesThumbnails->layout(); ui->tabImagesSplitter->setSizes({128, 1}); @@ -18,13 +23,15 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); + getSupportedFormats(); } void ImagesTab::clear() { - m_image->clear(); m_files.clear(); + select(nullptr); setHasData(false); } @@ -64,6 +71,21 @@ void ImagesTab::getSupportedFormats() } } +void ImagesTab::select(const File* f) +{ + if (f) { + ui->imagesPath->setText(f->path); + ui->imagesExplore->setEnabled(true); + m_image->setImage(f->original); + } else { + ui->imagesPath->clear(); + ui->imagesExplore->setEnabled(false); + m_image->clear(); + } + + m_selection = f; +} + int ImagesTab::calcThumbSize(int availableWidth) const { return availableWidth - (m_margins * 2); @@ -227,10 +249,19 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) } if (const auto* file=fileAtPos(e->pos())) { - m_image->setImage(file->original); + select(file); } } +void ImagesTab::onExplore() +{ + if (!m_selection) { + return; + } + + MOBase::shell::ExploreFile(m_selection->path); +} + bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const { if (file.failed) { diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 9c3e5746..cb9a2d67 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -107,12 +107,15 @@ private: std::vector m_files; std::vector m_supportedFormats; int m_margins, m_padding, m_border; + const File* m_selection; void getSupportedFormats(); + void select(const File* file); void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); + void onExplore(); int calcThumbSize(int availableWidth) const; int calcWidgetHeight(int availableWidth) const; -- cgit v1.3.1 From fb93d9ff2d1c158fb546471f6e18e4dc9b965c2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 13:02:11 -0400 Subject: nexus tab: fixed to only make one request, changed css to match nexus more closely bbcode now supports [img width=x,height=x] images tab: don't reload the original image --- src/bbcode.cpp | 2 +- src/modinfodialogimages.cpp | 12 +++++------ src/modinfodialognexus.cpp | 52 +++++++++++++++++++++++++++++++++------------ src/modinfodialognexus.h | 1 + 4 files changed, 46 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 3475f1b2..9f064106 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -174,7 +174,7 @@ private: "\\1"); m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), "\\2"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), + m_TagMap["img"] = std::make_pair(QRegExp("\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"), ""); m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), "\"\\1\""); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 22c004e6..9b0e98c5 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -282,14 +282,14 @@ bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const void ImagesTab::reload(File& file, const QSize& scaledSize) { - file.original = {}; - file.thumbnail = {}; file.failed = false; - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - return; + if (file.original.isNull()) { + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + return; + } } file.thumbnail = file.original.scaled( diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index d296e000..797f7923 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -7,10 +7,16 @@ #include #include +bool isValidModID(int id) +{ + return (id > 0); +} + NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false), + m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); @@ -53,6 +59,8 @@ void NexusTab::clear() void NexusTab::update() { + QScopedValueRollback loading(m_loading, true); + clear(); ui->modID->setText(QString("%1").arg(mod()->getNexusID())); @@ -123,7 +131,7 @@ void NexusTab::updateWebpage() { const int modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); @@ -147,16 +155,19 @@ void NexusTab::onModChanged() QString descriptionAsHTML = R"( - @@ -181,6 +192,10 @@ void NexusTab::onModChanged() void NexusTab::onModIDChanged() { + if (m_loading) { + return; + } + const int oldID = mod()->getNexusID(); const int newID = ui->modID->text().toInt(); @@ -190,7 +205,7 @@ void NexusTab::onModIDChanged() ui->browser->page()->setHtml(""); - if (newID != 0) { + if (isValidModID(newID)) { refreshData(newID); } } @@ -198,6 +213,10 @@ void NexusTab::onModIDChanged() void NexusTab::onSourceGameChanged() { + if (m_loading) { + return; + } + for (auto game : plugin().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { mod()->setGameName(game->gameShortName()); @@ -210,6 +229,10 @@ void NexusTab::onSourceGameChanged() void NexusTab::onVersionChanged() { + if (m_loading) { + return; + } + MOBase::VersionInfo version(ui->version->text()); mod()->setVersion(version); updateVersionColor(); @@ -217,6 +240,10 @@ void NexusTab::onVersionChanged() void NexusTab::onUrlChanged() { + if (m_loading) { + return; + } + mod()->setURL(ui->url->text()); mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); } @@ -225,7 +252,7 @@ void NexusTab::onOpenLink() { const int modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); @@ -237,7 +264,7 @@ void NexusTab::onRefreshBrowser() { const auto modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { refreshData(modID); } else qInfo("Mod has no valid Nexus ID, info can't be updated."); @@ -259,18 +286,15 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { - if (modID <= 0) { - qDebug() << "NexusTab: can't refresh, no mod id"; + if (!isValidModID(modID)) { return false; } if (m_requestStarted) { - qDebug() << "NexusTab: a refresh request is already running"; return false; } if (!mod()->updateNXMInfo()) { - qDebug() << "NexusTab: nexus description does not need an update"; return false; } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index a09f4316..86d87c30 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -47,6 +47,7 @@ public: private: QMetaObject::Connection m_modConnection; bool m_requestStarted; + bool m_loading; void cleanup(); void updateVersionColor(); -- cgit v1.3.1 From 00d4921e47e5eef08b10dac127795ecc8ccf84ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 15:36:46 -0400 Subject: only load nexus website on activation fixed refresh not working, will now always clear the browser so the refresh is obvious --- src/modinfodialog.cpp | 34 +++++++++++++++++++++++++++++++++- src/modinfodialog.h | 3 ++- src/modinfodialognexus.cpp | 31 ++++++++++++++++--------------- src/modinfodialognexus.h | 1 + src/modinfodialogtab.cpp | 20 +++++++++++++++++++- src/modinfodialogtab.h | 5 +++++ 6 files changed, 76 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c6cdb961..f11fa6f3 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -157,6 +157,8 @@ ModInfoDialog::ModInfoDialog( update(); }); } + + connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); } ModInfoDialog::~ModInfoDialog() = default; @@ -196,6 +198,10 @@ int ModInfoDialog::exec() void ModInfoDialog::setMod(ModInfo::Ptr mod) { m_mod = mod; + + for (auto& tabInfo : m_tabs) { + tabInfo.tab->resetFirstActivation(); + } } void ModInfoDialog::setMod(const QString& name) @@ -225,8 +231,25 @@ void ModInfoDialog::setTab(ETabs id) switchToTab(id); } +ModInfoDialog::TabInfo* ModInfoDialog::currentTab() +{ + const auto index = ui->tabWidget->currentIndex(); + if (index < 0) { + return nullptr; + } + + const auto i = static_cast(index); + if (i >= m_tabs.size()) { + return nullptr; + } + + return &m_tabs[i]; +} + void ModInfoDialog::update(bool firstTime) { + const int oldTab = ui->tabWidget->currentIndex(); + setWindowTitle(m_mod->name()); setTabsVisibility(firstTime); @@ -236,6 +259,12 @@ void ModInfoDialog::update(bool firstTime) switchToTab(m_initialTab); m_initialTab = ETabs(-1); } + + if (ui->tabWidget->currentIndex() == oldTab) { + if (auto* tabInfo=currentTab()) { + tabInfo->tab->activated(); + } + } } void ModInfoDialog::setTabsVisibility(bool firstTime) @@ -533,8 +562,11 @@ void ModInfoDialog::on_closeButton_clicked() close(); } -void ModInfoDialog::on_tabWidget_currentChanged(int index) +void ModInfoDialog::onTabChanged() { + if (auto* tabInfo=currentTab()) { + tabInfo->tab->activated(); + } } void ModInfoDialog::on_nextButton_clicked() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 8ddaf86c..36363c34 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -118,7 +118,6 @@ signals: private slots: void on_closeButton_clicked(); - void on_tabWidget_currentChanged(int index); void on_nextButton_clicked(); void on_prevButton_clicked(); @@ -144,6 +143,7 @@ private: ETabs m_initialTab; std::vector createTabs(); + TabInfo* currentTab(); void restoreTabState(const QString& state); QString saveTabState() const; void update(bool firstTime=false); @@ -157,6 +157,7 @@ private: void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; void onOriginModified(std::size_t tabIndex, int originID); + void onTabChanged(); template std::unique_ptr createTab(int index) diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 797f7923..adf79060 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -26,7 +26,7 @@ NexusTab::NexusTab( connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); connect(ui->url, &QLineEdit::editingFinished, [&]{ onUrlChanged(); }); connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); - connect(ui->refresh, &QToolButton::clicked, [&]{ updateWebpage(); }); + connect(ui->refresh, &QToolButton::clicked, [&]{ onRefreshBrowser(); }); connect( ui->sourceGame, @@ -96,10 +96,14 @@ void NexusTab::update() (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); - updateWebpage(); setHasData(mod()->getNexusID() >= 0); } +void NexusTab::firstActivation() +{ + updateWebpage(); +} + void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { cleanup(); @@ -265,9 +269,11 @@ void NexusTab::onRefreshBrowser() const auto modID = mod()->getNexusID(); if (isValidModID(modID)) { - refreshData(modID); - } else + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + updateWebpage(); + } else { qInfo("Mod has no valid Nexus ID, info can't be updated."); + } } void NexusTab::onEndorse() @@ -286,17 +292,12 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { - if (!isValidModID(modID)) { - return false; - } - - if (m_requestStarted) { - return false; - } - - if (!mod()->updateNXMInfo()) { - return false; + if (isValidModID(modID) && !m_requestStarted) { + if (mod()->updateNXMInfo()) { + ui->browser->setHtml(""); + return true; + } } - return true; + return false; } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 86d87c30..92330704 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -41,6 +41,7 @@ public: void clear() override; void update() override; + void firstActivation() override; void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; bool usesOriginFiles() const override; diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 009fb804..d99e8727 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -7,10 +7,23 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabID(id), m_hasData(false) + m_origin(nullptr), m_tabID(id), m_hasData(false), m_firstActivation(true) { } +void ModInfoDialogTab::activated() +{ + if (m_firstActivation) { + m_firstActivation = false; + firstActivation(); + } +} + +void ModInfoDialogTab::resetFirstActivation() +{ + m_firstActivation = true; +} + void ModInfoDialogTab::update() { // no-op @@ -22,6 +35,11 @@ bool ModInfoDialogTab::feedFile(const QString&, const QString&) return false; } +void ModInfoDialogTab::firstActivation() +{ + // no-op +} + bool ModInfoDialogTab::canClose() { return true; diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index c85d2ded..41d913f8 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -21,9 +21,13 @@ public: ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; virtual ~ModInfoDialogTab() = default; + void activated(); + void resetFirstActivation(); + virtual void clear() = 0; virtual void update(); virtual bool feedFile(const QString& rootPath, const QString& filename); + virtual void firstActivation(); virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); @@ -70,6 +74,7 @@ private: MOShared::FilesOrigin* m_origin; int m_tabID; bool m_hasData; + bool m_firstActivation; }; -- cgit v1.3.1 From 19a19ead4059c600680d6e7ac193490e32e03245 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 16:41:32 -0400 Subject: filterwidget now support automatically installing a proxy on lists filters on txt and ini tabs --- src/filterwidget.cpp | 70 +++++++++++++++++++++++++++++++++++++++--- src/filterwidget.h | 41 ++++++++++++++++++++++--- src/modinfodialog.ui | 8 ++++- src/modinfodialogconflicts.cpp | 4 +-- src/modinfodialogtextfiles.cpp | 13 +++++--- src/modinfodialogtextfiles.h | 4 ++- 6 files changed, 123 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 82644658..1b9efd7c 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -1,12 +1,41 @@ #include "filterwidget.h" #include "eventfilter.h" -FilterWidget::FilterWidget() - : m_edit(nullptr), m_eventFilter(nullptr), m_clear(nullptr) +FilterWidgetProxyModel::FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent) + : QSortFilterProxyModel(parent), m_filter(fw) { + connect(&fw, &FilterWidget::changed, [&]{ invalidateFilter(); }); } -void FilterWidget::set(QLineEdit* edit) +bool FilterWidgetProxyModel::filterAcceptsRow( + int sourceRow, const QModelIndex& sourceParent) const +{ + const auto cols = sourceModel()->columnCount(); + + const auto m = m_filter.matches([&](auto&& what) { + for (int c=0; cindex(sourceRow, c, sourceParent); + const auto text = sourceModel()->data(index, Qt::DisplayRole).toString(); + + if (text.contains(what, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }); + + return m; +} + + +FilterWidget::FilterWidget() : + m_edit(nullptr), m_list(nullptr), m_proxy(nullptr), + m_eventFilter(nullptr), m_clear(nullptr) +{ +} + +void FilterWidget::setEdit(QLineEdit* edit) { unhook(); @@ -16,11 +45,22 @@ void FilterWidget::set(QLineEdit* edit) return; } + m_edit->setPlaceholderText(QObject::tr("Filter")); + createClear(); hookEvents(); clear(); } +void FilterWidget::setList(QAbstractItemView* list) +{ + m_list = list; + + m_proxy = new FilterWidgetProxyModel(*this); + m_proxy->setSourceModel(m_list->model()); + m_list->setModel(m_proxy); +} + void FilterWidget::clear() { if (!m_edit) { @@ -30,6 +70,16 @@ void FilterWidget::clear() m_edit->clear(); } +QModelIndex FilterWidget::map(const QModelIndex& index) +{ + if (m_proxy) { + return m_proxy->mapToSource(index); + } else { + qCritical() << "FilterWidget::map() called, but proxy isn't set up"; + return index; + } +} + void FilterWidget::compile() { m_compiled.clear(); @@ -83,6 +133,14 @@ void FilterWidget::unhook() if (m_edit) { m_edit->removeEventFilter(m_eventFilter); } + + if (m_proxy && m_list) { + auto* model = m_proxy->sourceModel(); + m_proxy->setSourceModel(nullptr); + delete m_proxy; + + m_list->setModel(model); + } } void FilterWidget::createClear() @@ -125,9 +183,11 @@ void FilterWidget::onTextChanged() m_text = text; compile(); - if (changed) { - changed(); + if (m_proxy) { + m_proxy->invalidateFilter(); } + + emit changed(); } } diff --git a/src/filterwidget.h b/src/filterwidget.h index 762d9b15..5b08c3ae 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -1,22 +1,55 @@ #ifndef FILTERWIDGET_H #define FILTERWIDGET_H +#include +#include +#include +#include +#include + class EventFilter; +class FilterWidget; -class FilterWidget +class FilterWidgetProxyModel : public QSortFilterProxyModel { + Q_OBJECT; + public: - std::function changed; + FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent=nullptr); + using QSortFilterProxyModel::invalidateFilter; + +protected: + bool filterAcceptsRow(int row, const QModelIndex& parent) const override; + +private: + FilterWidget& m_filter; +}; + + +class FilterWidget : public QObject +{ + Q_OBJECT; + +public: + using predFun = std::function; FilterWidget(); - void set(QLineEdit* edit); + void setEdit(QLineEdit* edit); + void setList(QAbstractItemView* list); void clear(); - bool matches(std::function pred) const; + QModelIndex map(const QModelIndex& index); + + bool matches(predFun pred) const; + +signals: + void changed(); private: QLineEdit* m_edit; + QAbstractItemView* m_list; + FilterWidgetProxyModel* m_proxy; EventFilter* m_eventFilter; QToolButton* m_clear; QString m_text; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 9f4ae873..69e73da4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -72,6 +72,9 @@ + + + @@ -108,7 +111,7 @@ Qt::Horizontal - + 6 @@ -132,6 +135,9 @@
    + + + diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index c053e64c..e631db30 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -999,8 +999,8 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); - m_filter.set(ui->conflictsAdvancedFilter); - m_filter.changed = [&]{ update(); }; + m_filter.setEdit(ui->conflictsAdvancedFilter); + QObject::connect(&m_filter, &FilterWidget::changed, [&]{ update(); }); } void AdvancedConflictsTab::clear() diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 675a4c79..2b5ab489 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -100,7 +100,7 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* sp, TextEditor* e) : + QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : ModInfoDialogTab(oc, plugin, parent, ui, id), m_list(list), m_editor(e), m_model(new FileListModel) { @@ -111,6 +111,9 @@ GenericFilesTab::GenericFilesTab( sp->setStretchFactor(0, 0); sp->setStretchFactor(1, 1); + m_filter.setEdit(filter); + m_filter.setList(m_list); + QObject::connect( m_list->selectionModel(), &QItemSelectionModel::currentRowChanged, [&](auto current, auto previous){ onSelection(current, previous); }); @@ -187,7 +190,7 @@ void GenericFilesTab::select(const QModelIndex& index) } m_editor->setEnabled(true); - m_editor->load(m_model->fullPath(index)); + m_editor->load(m_model->fullPath(m_filter.map(index))); } @@ -196,7 +199,8 @@ TextFilesTab::TextFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( oc, plugin, parent, ui, id, - ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) + ui->textFileList, ui->tabTextSplitter, + ui->textFileEditor, ui->textFileFilter) { } @@ -218,7 +222,8 @@ IniFilesTab::IniFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( oc, plugin, parent, ui, id, - ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) + ui->iniFileList, ui->tabIniSplitter, + ui->iniFileEditor, ui->iniFileFilter) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index d879c2bd..037c6bb3 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -2,6 +2,7 @@ #define MODINFODIALOGTEXTFILES_H #include "modinfodialogtab.h" +#include "filterwidget.h" #include #include @@ -23,11 +24,12 @@ protected: QListView* m_list; TextEditor* m_editor; FileListModel* m_model; + FilterWidget m_filter; GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* splitter, TextEditor* editor); + QListView* list, QSplitter* splitter, TextEditor* editor, QLineEdit* filter); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; -- cgit v1.3.1 From 65b3eb24fc5e3e1033d583a24cb51a19b0cfac2c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:20:19 -0400 Subject: filterable images --- src/filterwidget.cpp | 5 ++ src/filterwidget.h | 1 + src/modinfodialog.ui | 3 ++ src/modinfodialogimages.cpp | 111 +++++++++++++++++++++++++++++++++++--------- src/modinfodialogimages.h | 9 ++++ 5 files changed, 108 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 1b9efd7c..44cbb274 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -70,6 +70,11 @@ void FilterWidget::clear() m_edit->clear(); } +bool FilterWidget::empty() const +{ + return m_text.isEmpty(); +} + QModelIndex FilterWidget::map(const QModelIndex& index) { if (m_proxy) { diff --git a/src/filterwidget.h b/src/filterwidget.h index 5b08c3ae..4fb9831f 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -38,6 +38,7 @@ public: void setEdit(QLineEdit* edit); void setList(QAbstractItemView* list); void clear(); + bool empty() const; QModelIndex map(const QModelIndex& index); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 69e73da4..fd367e74 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -196,6 +196,9 @@
    + + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 9b0e98c5..deaacd14 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -23,6 +23,9 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + m_filter.setEdit(ui->imagesFilter); + connect(&m_filter, &FilterWidget::changed, [&]{ onFilterChanged(); }); + connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); getSupportedFormats(); @@ -31,6 +34,8 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { m_files.clear(); + m_filteredFiles.clear(); + select(nullptr); setHasData(false); } @@ -50,10 +55,62 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { setHasData(!m_files.empty()); + + filterImages(); resizeWidget(); ui->imagesThumbnails->update(); } +void ImagesTab::filterImages() +{ + m_filteredFiles.clear(); + + if (m_filter.empty()) { + return; + } + + for (auto& f : m_files) { + const auto m = m_filter.matches([&](auto&& what) { + return f.path.contains(what, Qt::CaseInsensitive); + }); + + if (m) { + m_filteredFiles.push_back(&f); + } + } +} + +std::size_t ImagesTab::fileCount() const +{ + if (m_filter.empty()) { + return m_files.size(); + } else { + return m_filteredFiles.size(); + } +} + +const ImagesTab::File* ImagesTab::getFile(std::size_t i) const +{ + if (m_filter.empty()) { + if (i >= m_files.size()) { + return nullptr; + } + + return &m_files[i]; + } else { + if (i >= m_filteredFiles.size()) { + return nullptr; + } + + return m_filteredFiles[i]; + } +} + +ImagesTab::File* ImagesTab::getFile(std::size_t i) +{ + return const_cast(std::as_const(*this).getFile(i)); +} + void ImagesTab::getSupportedFormats() { for (const auto& entry : QImageReader::supportedImageFormats()) { @@ -74,7 +131,7 @@ void ImagesTab::getSupportedFormats() void ImagesTab::select(const File* f) { if (f) { - ui->imagesPath->setText(f->path); + ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); } else { @@ -93,7 +150,7 @@ int ImagesTab::calcThumbSize(int availableWidth) const int ImagesTab::calcWidgetHeight(int availableWidth) const { - if (m_files.empty()) { + if (fileCount() == 0) { return 0; } @@ -106,7 +163,7 @@ int ImagesTab::calcWidgetHeight(int availableWidth) const // subsequent thumbs with padding before each const auto thumbWithPadding = m_padding + thumbSize; - h += static_cast(thumbWithPadding * (m_files.size() - 1)); + h += static_cast(thumbWithPadding * (fileCount() - 1)); // margin top and bottom h += m_margins * 2; @@ -170,7 +227,8 @@ void ImagesTab::paintThumbnails(QPaintEvent* e) const auto [begin, end] = calcVisibleRange( e->rect().top(), e->rect().bottom(), cx.thumbSize); - for (std::size_t i=begin; ifailed) { return; } const auto imageRect = calcImageRect(cx.topRect, cx.thumbSize, i); - if (needsReload(file, imageRect.size())) { - reload(file, imageRect.size()); + if (needsReload(*file, imageRect.size())) { + reload(*file, imageRect.size()); } - if (file.thumbnail.isNull()) { + if (file->thumbnail.isNull()) { return; } // center scaled image in rect const QRect scaledThumbRect( - (imageRect.left()+imageRect.width()/2) - file.thumbnail.width()/2, - (imageRect.top()+imageRect.height()/2) - file.thumbnail.height()/2, - file.thumbnail.width(), - file.thumbnail.height()); + (imageRect.left()+imageRect.width()/2) - file->thumbnail.width()/2, + (imageRect.top()+imageRect.height()/2) - file->thumbnail.height()/2, + file->thumbnail.width(), + file->thumbnail.height()); - cx.painter.drawImage(scaledThumbRect, file.thumbnail); + cx.painter.drawImage(scaledThumbRect, file->thumbnail); } const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const @@ -222,7 +285,7 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const // calculate index purely based on y position const std::size_t i = p.y() / (thumbSize + m_padding); - if (i >= m_files.size()) { + if (i >= fileCount()) { return nullptr; } @@ -234,7 +297,7 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const return nullptr; } - return &m_files[i]; + return getFile(i); } void ImagesTab::scrollAreaResized(const QSize&) @@ -248,9 +311,7 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) return; } - if (const auto* file=fileAtPos(e->pos())) { - select(file); - } + select(fileAtPos(e->pos())); } void ImagesTab::onExplore() @@ -262,6 +323,11 @@ void ImagesTab::onExplore() MOBase::shell::ExploreFile(m_selection->path); } +void ImagesTab::onFilterChanged() +{ + update(); +} + bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const { if (file.failed) { @@ -299,14 +365,17 @@ void ImagesTab::reload(File& file, const QSize& scaledSize) void ImagesTab::resizeWidget() { - if (m_files.empty()) { + if (fileCount() == 0) { ui->imagesThumbnails->setGeometry(QRect()); return; } const auto availableWidth = ui->imagesScrollArea->viewport()->width(); - const int widgetHeight = calcWidgetHeight(availableWidth); + const int widgetHeight = std::max( + calcWidgetHeight(availableWidth), + ui->imagesScrollArea->viewport()->height()); + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index cb9a2d67..d90f2bfd 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -2,6 +2,7 @@ #define MODINFODIALOGIMAGES_H #include "modinfodialogtab.h" +#include "filterwidget.h" #include class ImagesTab; @@ -105,9 +106,11 @@ private: ScalableImage* m_image; std::vector m_files; + std::vector m_filteredFiles; std::vector m_supportedFormats; int m_margins, m_padding, m_border; const File* m_selection; + FilterWidget m_filter; void getSupportedFormats(); void select(const File* file); @@ -116,6 +119,7 @@ private: void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); void onExplore(); + void onFilterChanged(); int calcThumbSize(int availableWidth) const; int calcWidgetHeight(int availableWidth) const; @@ -134,6 +138,11 @@ private: const File* fileAtPos(const QPoint& p) const; + std::size_t fileCount() const; + const File* getFile(std::size_t i) const; + File* getFile(std::size_t i); + + void filterImages(); bool needsReload(const File& file, const QSize& imageSize) const; void reload(File& file, const QSize& imageSize); void resizeWidget(); -- cgit v1.3.1 From fa20f81b46c3f8c89fcbbdff6e5da0225c707358 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:44:41 -0400 Subject: replaced reportError() calls by qCritical() because they can come from a thread, which hangs the ui categories and notes will update the tab color when changing fixed rare crash when reordering tabs while loading a mod --- src/modinfodialog.cpp | 18 ++++++++++++++++-- src/modinfodialog.h | 1 + src/modinfodialog.ui | 7 ++----- src/modinfodialogcategories.cpp | 3 ++- src/modinfodialogesps.cpp | 3 ++- src/modinfodialogtab.cpp | 36 ++++++++++++++++++++++++------------ src/modinfodialogtab.h | 2 ++ src/modinfodialogtextfiles.cpp | 2 +- src/modinforegular.cpp | 8 ++++++-- 9 files changed, 56 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f11fa6f3..7696023f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -124,7 +124,8 @@ ModInfoDialog::ModInfoDialog( MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : 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(ETabs(-1)), + m_arrangingTabs(false) { ui->setupUi(this); @@ -156,6 +157,10 @@ ModInfoDialog::ModInfoDialog( setMod(name); update(); }); + + connect( + tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, + [&]{ setTabsColors(); }); } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); @@ -269,6 +274,8 @@ void ModInfoDialog::update(bool firstTime) void ModInfoDialog::setTabsVisibility(bool firstTime) { + QScopedValueRollback arrangingTabs(m_arrangingTabs, true); + std::vector visibility(m_tabs.size()); bool changed = false; @@ -374,7 +381,7 @@ 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); + m_mainWindow->palette().color(QPalette::Disabled, QPalette::WindowText); ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, c); } @@ -564,6 +571,13 @@ void ModInfoDialog::on_closeButton_clicked() void ModInfoDialog::onTabChanged() { + if (m_arrangingTabs) { + // this can be fired while re-arranging tabs, which happens before mods + // are given to tabs, and might trigger first activation, which breaks all + // sorts of things + return; + } + if (auto* tabInfo=currentTab()) { tabInfo->tab->activated(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 36363c34..6dc66003 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -141,6 +141,7 @@ private: PluginContainer* m_plugin; std::vector m_tabs; ETabs m_initialTab; + bool m_arrangingTabs; std::vector createTabs(); TabInfo* currentTab(); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index fd367e74..30ea9217 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -810,9 +810,6 @@ text-align: left; - - true - false @@ -1038,7 +1035,7 @@ p, li { white-space: pre-wrap; } - + Enter comments about the mod here. These are displayed in the notes column of the mod list. @@ -1051,7 +1048,7 @@ p, li { white-space: pre-wrap; } - + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 0d739d1f..8ffded59 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -34,7 +34,6 @@ void CategoriesTab::update() ui->categories->invisibleRootItem(), 0); updatePrimary(); - setHasData(ui->primaryCategories->count() > 0); } bool CategoriesTab::canHandleSeparators() const @@ -92,6 +91,8 @@ void CategoriesTab::updatePrimary() break; } } + + setHasData(ui->primaryCategories->count() > 0); } void CategoriesTab::addChecked(QTreeWidgetItem* tree) diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index ecb341e8..7961096d 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -260,7 +260,6 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) m_inactiveModel->add(std::move(esp)); } - setHasData(true); return true; } } @@ -272,6 +271,8 @@ void ESPsTab::update() { m_inactiveModel->finished(); m_activeModel->finished(); + + setHasData(m_inactiveModel->rowCount() > 0 || m_activeModel->rowCount() > 0); } void ESPsTab::onActivate() diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index d99e8727..f5eeeed1 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -131,7 +131,10 @@ void ModInfoDialogTab::emitModOpen(QString name) void ModInfoDialogTab::setHasData(bool b) { - m_hasData = b; + if (m_hasData != b) { + m_hasData = b; + emit hasDataChanged(); + } } @@ -140,14 +143,14 @@ NotesTab::NotesTab( QWidget* parent, Ui::ModInfoDialog* ui, int index) : ModInfoDialogTab(oc, plugin, parent, ui, index) { - connect(ui->commentsEdit, &QLineEdit::editingFinished, [&]{ onComments(); }); - connect(ui->notesEdit, &HTMLEditor::editingFinished, [&]{ onNotes(); }); + connect(ui->comments, &QLineEdit::editingFinished, [&]{ onComments(); }); + connect(ui->notes, &HTMLEditor::editingFinished, [&]{ onNotes(); }); } void NotesTab::clear() { - ui->commentsEdit->clear(); - ui->notesEdit->clear(); + ui->comments->clear(); + ui->notes->clear(); setHasData(false); } @@ -156,10 +159,9 @@ void NotesTab::update() const auto comments = mod()->comments(); const auto notes = mod()->notes(); - ui->commentsEdit->setText(comments); - ui->notesEdit->setText(notes); - - setHasData(!comments.isEmpty() || !notes.isEmpty()); + ui->comments->setText(comments); + ui->notes->setText(notes); + checkHasData(); } bool NotesTab::canHandleSeparators() const @@ -169,20 +171,30 @@ bool NotesTab::canHandleSeparators() const void NotesTab::onComments() { - mod()->setComments(ui->commentsEdit->text()); + mod()->setComments(ui->comments->text()); + checkHasData(); } void NotesTab::onNotes() { // Avoid saving html stub if notes field is empty. - if (ui->notesEdit->toPlainText().isEmpty()) { + if (ui->notes->toPlainText().isEmpty()) { mod()->setNotes({}); } else { - mod()->setNotes(ui->notesEdit->toHtml()); + mod()->setNotes(ui->notes->toHtml()); } + + checkHasData(); } bool NotesTab::usesOriginFiles() const { return false; } + +void NotesTab::checkHasData() +{ + setHasData( + !ui->comments->text().isEmpty() || + !ui->notes->toPlainText().isEmpty()); +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 41d913f8..058035ab 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -49,6 +49,7 @@ public: signals: void originModified(int originID); void modOpen(QString name); + void hasDataChanged(); protected: Ui::ModInfoDialog* ui; @@ -93,6 +94,7 @@ public: private: void onComments(); void onNotes(); + void checkHasData(); }; #endif // MODINFODIALOGTAB_H diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 2b5ab489..e3d00049 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -158,7 +158,6 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (wantsFile(rootPath, fullPath)) { m_model->add(rootPath, fullPath); - setHasData(true); return true; } } @@ -169,6 +168,7 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) void GenericFilesTab::update() { m_model->finished(); + setHasData(m_model->rowCount() > 0); } void GenericFilesTab::onSelection( diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index babbd665..4333e351 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -194,10 +194,14 @@ void ModInfoRegular::saveMeta() if (metaFile.status() == QSettings::NoError) { m_MetaInfoChanged = false; } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + qCritical() + << QString("failed to write %1/meta.ini: error %2") + .arg(absolutePath()).arg(metaFile.status()); } } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + qCritical() + << QString("failed to write %1/meta.ini: error %2") + .arg(absolutePath()).arg(metaFile.status()); } } } -- cgit v1.3.1 From fc3ed80d0de220a1aa9d2b459785d47beea5861b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:58:34 -0400 Subject: tooltip on images --- src/modinfodialogimages.cpp | 24 ++++++++++++++++++++++++ src/modinfodialogimages.h | 2 ++ 2 files changed, 26 insertions(+) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index deaacd14..08fd5c5b 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -314,6 +314,20 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) select(fileAtPos(e->pos())); } +void ImagesTab::showTooltip(QHelpEvent* e) +{ + const auto* f = fileAtPos(e->pos()); + if (!f) { + QToolTip::hideText(); + e->ignore(); + return; + } + + QToolTip::showText( + e->globalPos(), QDir::toNativeSeparators(f->path), + ui->imagesThumbnails); +} + void ImagesTab::onExplore() { if (!m_selection) { @@ -412,6 +426,16 @@ void ImagesThumbnails::mousePressEvent(QMouseEvent* e) } } +bool ImagesThumbnails::event(QEvent* e) +{ + if (e->type() == QEvent::ToolTip) { + m_tab->showTooltip(static_cast(e)); + return true; + } + + return QWidget::event(e); +} + ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index d90f2bfd..bd404801 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -34,6 +34,7 @@ public: protected: void paintEvent(QPaintEvent* e) override; void mousePressEvent(QMouseEvent* e) override; + bool event(QEvent* e) override; private: ImagesTab* m_tab = nullptr; @@ -118,6 +119,7 @@ private: void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); + void showTooltip(QHelpEvent* e); void onExplore(); void onFilterChanged(); -- cgit v1.3.1 From cb56bf76fd8036895bda468a5ad9ef707dbefce9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 18:10:00 -0400 Subject: fixed text editors not clearing when switching mods ported typo fixes that applied to the mod info dialog --- src/modinfodialog.ui | 2 +- src/modinfodialogfiletree.cpp | 4 ++-- src/modinfodialogtextfiles.cpp | 1 + src/texteditor.cpp | 8 ++++++++ src/texteditor.h | 1 + 5 files changed, 13 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 30ea9217..7a7d82da 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -426,7 +426,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e ESPs in the data directory and thus visible to the game. - These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + <html><head/><body><p>These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window.</p></body></html> true diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index b57f6b5d..24faec5e 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -166,9 +166,9 @@ void FileTreeTab::onDelete() if (rows.count() == 1) { QString fileName = m_fs->fileName(rows[0]); - message = tr("Are sure you want to delete \"%1\"?").arg(fileName); + message = tr("Are you sure you want to delete \"%1\"?").arg(fileName); } else { - message = tr("Are sure you want to delete the selected files?"); + message = tr("Are you sure you want to delete the selected files?"); } if (QMessageBox::question(parentWidget(), tr("Confirm"), message) != QMessageBox::Yes) { diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index e3d00049..5b035c53 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -185,6 +185,7 @@ void GenericFilesTab::onSelection( void GenericFilesTab::select(const QModelIndex& index) { if (!index.isValid()) { + m_editor->clear(); m_editor->setEnabled(false); return; } diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 6c1685da..ee36c104 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -57,6 +57,14 @@ void TextEditor::setDefaultStyle() setHighlightBackgroundColor(backgroundColor); } +void TextEditor::clear() +{ + m_filename.clear(); + m_encoding.clear(); + setPlainText(""); + dirty(false); +} + bool TextEditor::load(const QString& filename) { m_filename = filename; diff --git a/src/texteditor.h b/src/texteditor.h index f3031731..775565f2 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -91,6 +91,7 @@ public: void setupToolbar(); + void clear(); bool load(const QString& filename); bool save(); -- cgit v1.3.1 From cac0fb710c48fa55c6e7303485adf9f4f3364682 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 18:54:47 -0400 Subject: added explore button to editors fixed save button on editor being enabled with empty documents added ctrl+s always show vertical scrollbar in thumbnails, fixes nonstop relayouts when the height is just right and the scrollbar flickers on and off --- src/modinfodialog.ui | 6 ++++++ src/texteditor.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++------ src/texteditor.h | 6 ++++++ 3 files changed, 66 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 7a7d82da..56c97c34 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -168,6 +168,9 @@ Qt::Horizontal + + false + @@ -184,6 +187,9 @@ + + Qt::ScrollBarAlwaysOn + diff --git a/src/texteditor.cpp b/src/texteditor.cpp index ee36c104..9bbe3ddd 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -5,7 +5,7 @@ TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), - m_dirty(false) + m_dirty(false), m_loading(false) { m_toolbar = new TextEditorToolbar(*this); m_lineNumbers = new TextEditorLineNumbers(*this); @@ -59,17 +59,25 @@ void TextEditor::setDefaultStyle() void TextEditor::clear() { + QScopedValueRollback loading(m_loading, true); + m_filename.clear(); m_encoding.clear(); setPlainText(""); dirty(false); + document()->setModified(false); + + emit loaded(""); } bool TextEditor::load(const QString& filename) { - m_filename = filename; clear(); + QScopedValueRollback loading(m_loading, true); + + m_filename = filename; + const QString s = MOBase::readFileText(filename, &m_encoding); setPlainText(s); @@ -81,6 +89,8 @@ bool TextEditor::load(const QString& filename) onModified(false); } + emit loaded(m_filename); + return true; } @@ -180,8 +190,21 @@ void TextEditor::setHighlightBackgroundColor(const QColor& c) update(); } +void TextEditor::explore() +{ + if (m_filename.isEmpty()) { + return; + } + + MOBase::shell::ExploreFile(m_filename); +} + void TextEditor::onModified(bool b) { + if (m_loading) { + return; + } + dirty(b); emit modified(b); } @@ -431,15 +454,27 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } -TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : - m_editor(editor), - m_save(new QAction(QIcon(":/MO/gui/save"), QObject::tr("&Save"))), - m_wordWrap(new QAction(QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"))) +TextEditorToolbar::TextEditorToolbar(TextEditor& editor) + : m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr) { - QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); + m_save = new QAction( + QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); + + m_save->setShortcutContext(Qt::WidgetWithChildrenShortcut); + m_save->setShortcut(Qt::CTRL + Qt::Key_S); + m_editor.addAction(m_save); + + m_wordWrap = new QAction( + QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"), &editor); + + m_explore = new QAction( + QObject::tr("&Open in Explorer"), &editor); m_wordWrap->setCheckable(true); + + QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); + QObject::connect(m_explore, &QAction::triggered, [&]{ m_editor.explore(); }); auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -453,8 +488,13 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : b->setDefaultAction(m_wordWrap); layout->addWidget(b); + b = new QToolButton; + b->setDefaultAction(m_explore); + layout->addWidget(b); + QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); + QObject::connect(&m_editor, &TextEditor::loaded, [&](QString f){ onLoaded(f); }); } void TextEditorToolbar::onTextModified(bool b) @@ -467,6 +507,13 @@ void TextEditorToolbar::onWordWrap(bool b) m_wordWrap->setChecked(b); } +void TextEditorToolbar::onLoaded(const QString& s) +{ + const auto hasDoc = !s.isEmpty(); + + m_explore->setEnabled(hasDoc); + m_wordWrap->setEnabled(hasDoc); +} void HTMLEditor::focusOutEvent(QFocusEvent* e) { diff --git a/src/texteditor.h b/src/texteditor.h index 775565f2..798222a3 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -16,9 +16,11 @@ private: TextEditor& m_editor; QAction* m_save; QAction* m_wordWrap; + QAction* m_explore; void onTextModified(bool b); void onWordWrap(bool b); + void onLoaded(const QString& s); }; @@ -112,7 +114,10 @@ public: QColor highlightBackgroundColor() const; void setHighlightBackgroundColor(const QColor& c); + void explore(); + signals: + void loaded(QString filename); void modified(bool b); void wordWrapChanged(bool b); @@ -127,6 +132,7 @@ private: QString m_filename; QString m_encoding; bool m_dirty; + bool m_loading; void setDefaultStyle(); void onModified(bool b); -- cgit v1.3.1 From 9cce32f19f435570b94c6f65c2bc6eb99bd542ac Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 01:58:13 -0400 Subject: dds toggle for images --- src/modinfodialog.ui | 10 ++++ src/modinfodialogimages.cpp | 108 ++++++++++++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 6 +++ 3 files changed, 104 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 56c97c34..3ea0a27b 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -173,6 +173,9 @@ + + 3 + 0 @@ -202,6 +205,13 @@ + + + + Show .dds files + + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 08fd5c5b..e25fd838 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,5 +1,6 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +#include "settings.h" #include "utility.h" ImagesTab::ImagesTab( @@ -7,8 +8,10 @@ ImagesTab::ImagesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), - m_selection(nullptr) + m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) { + getSupportedFormats(); + auto* ly = new QVBoxLayout(ui->imagesImage); ly->setContentsMargins({0, 0, 0, 0}); ly->addWidget(m_image); @@ -23,12 +26,15 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + ui->imagesShowDDS->setEnabled(m_ddsAvailable); + m_filter.setEdit(ui->imagesFilter); connect(&m_filter, &FilterWidget::changed, [&]{ onFilterChanged(); }); connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); + connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); }); - getSupportedFormats(); + ui->imagesShowDDS->setEnabled(m_ddsAvailable); } void ImagesTab::clear() @@ -54,55 +60,88 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { - setHasData(!m_files.empty()); - filterImages(); resizeWidget(); ui->imagesThumbnails->update(); + + setHasData(fileCount() > 0); } -void ImagesTab::filterImages() +void ImagesTab::saveState(Settings& s) { - m_filteredFiles.clear(); + s.directInterface().setValue( + "mod_info_dialog_images_show_dds", m_ddsEnabled); +} + +void ImagesTab::restoreState(const Settings& s) +{ + ui->imagesShowDDS->setChecked(s.directInterface() + .value("mod_info_dialog_images_show_dds", false).toBool()); +} - if (m_filter.empty()) { +void ImagesTab::filterImages() +{ + if (!needsFiltering()) { return; } + m_filteredFiles.clear(); + + bool hasTextFilter = !m_filter.empty(); + bool sawSelection = false; + for (auto& f : m_files) { - const auto m = m_filter.matches([&](auto&& what) { - return f.path.contains(what, Qt::CaseInsensitive); - }); + if (hasTextFilter) { + const auto m = m_filter.matches([&](auto&& what) { + return f.path.contains(what, Qt::CaseInsensitive); + }); + + if (!m) { + continue; + } + } + + if (!m_ddsEnabled) { + if (f.path.endsWith(".dds", Qt::CaseInsensitive)) { + continue; + } + } - if (m) { - m_filteredFiles.push_back(&f); + if (&f == m_selection) { + sawSelection = true; } + + m_filteredFiles.push_back(&f); + } + + if (!sawSelection) { + select(nullptr); } } std::size_t ImagesTab::fileCount() const { - if (m_filter.empty()) { - return m_files.size(); - } else { + if (needsFiltering()) { return m_filteredFiles.size(); + } else { + return m_files.size(); } } const ImagesTab::File* ImagesTab::getFile(std::size_t i) const { - if (m_filter.empty()) { - if (i >= m_files.size()) { + if (needsFiltering()) { + if (i >= m_filteredFiles.size()) { return nullptr; } - return &m_files[i]; + return m_filteredFiles[i]; } else { - if (i >= m_filteredFiles.size()) { + if (i >= m_files.size()) { return nullptr; } - return m_filteredFiles[i]; + return &m_files[i]; } } @@ -111,14 +150,34 @@ ImagesTab::File* ImagesTab::getFile(std::size_t i) return const_cast(std::as_const(*this).getFile(i)); } +bool ImagesTab::needsFiltering() const +{ + if (!m_filter.empty()) { + return true; + } + + if (!m_ddsEnabled) { + return true; + } + + return false; +} + void ImagesTab::getSupportedFormats() { + m_ddsAvailable = false; + for (const auto& entry : QImageReader::supportedImageFormats()) { QString s(entry); if (s.isNull() || s.isEmpty()) { continue; } + // used to enable the checkbox + if (s.compare("dds", Qt::CaseInsensitive) == 0) { + m_ddsAvailable = true; + } + // make sure it starts with a dot if (s[0] != ".") { s = "." + s; @@ -337,6 +396,15 @@ void ImagesTab::onExplore() MOBase::shell::ExploreFile(m_selection->path); } +void ImagesTab::onShowDDS() +{ + const auto b = ui->imagesShowDDS->isChecked(); + if (b != m_ddsEnabled) { + m_ddsEnabled = b; + update(); + } +} + void ImagesTab::onFilterChanged() { update(); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index bd404801..fa7115b0 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -79,6 +79,8 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update() override; + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; private: struct File @@ -112,15 +114,19 @@ private: int m_margins, m_padding, m_border; const File* m_selection; FilterWidget m_filter; + bool m_ddsAvailable, m_ddsEnabled; void getSupportedFormats(); + void enableDDS(bool b); void select(const File* file); + bool needsFiltering() const; void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); void showTooltip(QHelpEvent* e); void onExplore(); + void onShowDDS(); void onFilterChanged(); int calcThumbSize(int availableWidth) const; -- cgit v1.3.1 From 0fc928fd815f27eef77491224bb8922b4e070f2d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 07:03:40 -0400 Subject: rework of the images tab: - uses a custom widget with a scrollbar instead of a QScrollArea - scrolling by image instead of pixel - refactored all the geometry stuff in ImagesGeometry --- src/modinfodialog.ui | 51 +++--- src/modinfodialogimages.cpp | 393 ++++++++++++++++++++++++++------------------ src/modinfodialogimages.h | 189 ++++++++++++++++----- 3 files changed, 418 insertions(+), 215 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 3ea0a27b..a9af2593 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -172,7 +172,7 @@ false - + 3 @@ -189,20 +189,34 @@ 0 - - - Qt::ScrollBarAlwaysOn - - - - - 0 - 0 - 686 - 436 - + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 - + + + + + + + Qt::Vertical + + + + @@ -1221,16 +1235,15 @@ p, li { white-space: pre-wrap; }
    texteditor.h
    - ImagesScrollArea - QScrollArea + ImagesThumbnails + QWidget
    modinfodialogimages.h
    1
    - ImagesThumbnails - QWidget + ImagesScrollbar + QScrollBar
    modinfodialogimages.h
    - 1
    diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index e25fd838..5c93e3d4 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -3,11 +3,36 @@ #include "settings.h" #include "utility.h" +QSize resizeWithAspectRatio(const QSize& original, const QSize& available) +{ + const auto ratio = std::min({ + 1.0, + static_cast(available.width()) / original.width(), + static_cast(available.height()) / original.height()}); + + const QSize scaledSize( + static_cast(std::round(original.width() * ratio)), + static_cast(std::round(original.height() * ratio))); + + return scaledSize; +} + +QRect centeredRect(const QRect& rect, const QSize& size) +{ + return QRect( + (rect.left()+rect.width()/2) - size.width()/2, + (rect.top()+rect.height()/2) - size.height()/2, + size.width(), + size.height()); +} + + ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), + m_image(new ScalableImage), + m_margins(3), m_border(1), m_padding(0), m_spacing(5), m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -22,10 +47,12 @@ ImagesTab::ImagesTab( ui->tabImagesSplitter->setStretchFactor(0, 0); ui->tabImagesSplitter->setStretchFactor(1, 1); - ui->imagesScrollArea->setWidgetResizable(false); - ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + ui->imagesScrollerVBar->setTab(this); + ui->imagesScrollerVBar->setSingleStep(1); + connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); }); + ui->imagesShowDDS->setEnabled(m_ddsAvailable); m_filter.setEdit(ui->imagesFilter); @@ -35,12 +62,16 @@ ImagesTab::ImagesTab( connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); + + ui->imagesThumbnails->setAutoFillBackground(false); + ui->imagesThumbnails->setAttribute(Qt::WA_OpaquePaintEvent, true); } void ImagesTab::clear() { m_files.clear(); m_filteredFiles.clear(); + ui->imagesScrollerVBar->setValue(0); select(nullptr); setHasData(false); @@ -202,157 +233,98 @@ void ImagesTab::select(const File* f) m_selection = f; } -int ImagesTab::calcThumbSize(int availableWidth) const -{ - return availableWidth - (m_margins * 2); -} - -int ImagesTab::calcWidgetHeight(int availableWidth) const +ImagesGeometry ImagesTab::makeGeometry() const { - if (fileCount() == 0) { - return 0; - } - - const auto thumbSize = calcThumbSize(availableWidth); - - int h = 0; - - // first thumb - h = thumbSize; - - // subsequent thumbs with padding before each - const auto thumbWithPadding = m_padding + thumbSize; - h += static_cast(thumbWithPadding * (fileCount() - 1)); - - // margin top and bottom - h += m_margins * 2; - - return h; -} - -QRect ImagesTab::calcTopThumbRect(int thumbSize) const -{ - return {m_margins, m_margins, thumbSize, thumbSize}; + return ImagesGeometry( + ui->imagesThumbnails->size(), + m_margins, m_border, m_padding, m_spacing); } -std::pair ImagesTab::calcVisibleRange( - int top, int bottom, int thumbSize) const +void ImagesTab::paintThumbnailsArea(QPaintEvent* e) { - const std::size_t begin = top / (thumbSize + m_padding); - const std::size_t end = bottom / (thumbSize + m_padding) + 1; + const auto geo = makeGeometry(); - return {begin, end}; -} + QPainter painter(ui->imagesThumbnails); -QRect ImagesTab::calcBorderRect( - const QRect& topRect, int thumbSize, std::size_t i) const -{ - return { - topRect.left(), - static_cast(topRect.top() + (i * (thumbSize + m_padding))), - thumbSize, - thumbSize - }; -} + painter.fillRect( + ui->imagesThumbnails->rect(), + ui->imagesThumbnails->palette().color(QPalette::Window)); -QRect ImagesTab::calcImageRect( - const QRect& topRect, int thumbSize, std::size_t i) const -{ - return calcBorderRect(topRect, thumbSize, i).adjusted( - m_border, m_border, -m_border, -m_border); -} + const auto visible = geo.fullyVisibleCount() + 1; + const auto first = ui->imagesScrollerVBar->value(); -QSize ImagesTab::calcScaledImageSize( - const QSize& originalSize, const QSize& imageSize) const -{ - const auto ratio = std::min({ - 1.0, - static_cast(imageSize.width()) / originalSize.width(), - static_cast(imageSize.height()) / originalSize.height()}); - - const QSize scaledSize( - static_cast(std::round(originalSize.width() * ratio)), - static_cast(std::round(originalSize.height() * ratio))); - - return scaledSize; -} - -void ImagesTab::paintThumbnails(QPaintEvent* e) -{ - PaintContext cx(ui->imagesThumbnails); - cx.thumbSize = calcThumbSize(ui->imagesThumbnails->width()); - cx.topRect = calcTopThumbRect(cx.thumbSize); - - const auto [begin, end] = calcVisibleRange( - e->rect().top(), e->rect().bottom(), cx.thumbSize); + for (std::size_t i=0; ifailed) { + if (file.failed) { return; } - const auto imageRect = calcImageRect(cx.topRect, cx.thumbSize, i); - - if (needsReload(*file, imageRect.size())) { - reload(*file, imageRect.size()); + if (needsReload(geo, file)) { + reload(geo, file); } - if (file->thumbnail.isNull()) { + if (file.thumbnail.isNull()) { return; } - // center scaled image in rect - const QRect scaledThumbRect( - (imageRect.left()+imageRect.width()/2) - file->thumbnail.width()/2, - (imageRect.top()+imageRect.height()/2) - file->thumbnail.height()/2, - file->thumbnail.width(), - file->thumbnail.height()); + const auto imageRect = geo.imageRect(i); + const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); - cx.painter.drawImage(scaledThumbRect, file->thumbnail); + painter.drawImage(scaledThumbRect, file.thumbnail); } const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const { - const auto thumbSize = calcThumbSize(ui->imagesThumbnails->width()); + const auto geo = makeGeometry(); - // calculate index purely based on y position - const std::size_t i = p.y() / (thumbSize + m_padding); - if (i >= fileCount()) { + // this is the index relative to the top + const auto offset = geo.indexAt(p); + if (offset == ImagesGeometry::BadIndex) { return nullptr; } - // get actual rect - const auto topRect = calcTopThumbRect(thumbSize); - const auto rect = calcBorderRect(topRect, thumbSize, i); + const auto first = ui->imagesScrollerVBar->value(); + if (first < 0) { + return nullptr; + } - if (!rect.contains(p)) { + const auto i = static_cast(first) + offset; + if (i >= fileCount()) { return nullptr; } @@ -364,7 +336,7 @@ void ImagesTab::scrollAreaResized(const QSize&) resizeWidget(); } -void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) +void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) { if (e->button() != Qt::LeftButton) { return; @@ -373,6 +345,19 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) select(fileAtPos(e->pos())); } +void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) +{ + const auto d = (e->angleDelta() / 8).y(); + + ui->imagesScrollerVBar->setValue( + ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); +} + +void ImagesTab::onScrolled() +{ + ui->imagesThumbnails->update(); +} + void ImagesTab::showTooltip(QHelpEvent* e) { const auto* f = fileAtPos(e->pos()); @@ -410,7 +395,7 @@ void ImagesTab::onFilterChanged() update(); } -bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const +bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const { if (file.failed) { return false; @@ -420,15 +405,11 @@ bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const return true; } - const auto scaledSize = calcScaledImageSize(file.original.size(), imageSize); - if (file.thumbnail.size() != scaledSize) { - return true; - } - - return false; + const auto scaledSize = geo.scaledImageSize(file.original.size()); + return (file.thumbnail.size() != scaledSize); } -void ImagesTab::reload(File& file, const QSize& scaledSize) +void ImagesTab::reload(const ImagesGeometry& geo, File& file) { file.failed = false; @@ -441,56 +422,63 @@ void ImagesTab::reload(File& file, const QSize& scaledSize) } file.thumbnail = file.original.scaled( - calcScaledImageSize(file.original.size(), scaledSize), + geo.scaledImageSize(file.original.size()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } void ImagesTab::resizeWidget() { if (fileCount() == 0) { - ui->imagesThumbnails->setGeometry(QRect()); + ui->imagesScrollerVBar->setRange(0, 0); + ui->imagesScrollerVBar->setEnabled(false); return; } - const auto availableWidth = ui->imagesScrollArea->viewport()->width(); + const auto geo = makeGeometry(); + const auto availableSize = ui->imagesThumbnails->size(); + const auto fullyVisible = geo.fullyVisibleCount(); - const int widgetHeight = std::max( - calcWidgetHeight(availableWidth), - ui->imagesScrollArea->viewport()->height()); - - ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); + if (fullyVisible >= fileCount()) { + ui->imagesScrollerVBar->setRange(0, 0); + ui->imagesScrollerVBar->setEnabled(false); + } else { + const auto d = fileCount() - fullyVisible; + ui->imagesScrollerVBar->setRange(0, static_cast(d)); + ui->imagesScrollerVBar->setEnabled(true); + } } -void ImagesScrollArea::setTab(ImagesTab* tab) +void ImagesThumbnails::setTab(ImagesTab* tab) { m_tab = tab; } -void ImagesScrollArea::resizeEvent(QResizeEvent* e) +void ImagesThumbnails::paintEvent(QPaintEvent* e) { if (m_tab) { - m_tab->scrollAreaResized(e->size()); + m_tab->paintThumbnailsArea(e); } } - -void ImagesThumbnails::setTab(ImagesTab* tab) +void ImagesThumbnails::mousePressEvent(QMouseEvent* e) { - m_tab = tab; + if (m_tab) { + m_tab->thumbnailAreaMouseEvent(e); + } } -void ImagesThumbnails::paintEvent(QPaintEvent* e) +void ImagesThumbnails::wheelEvent(QWheelEvent* e) { if (m_tab) { - m_tab->paintThumbnails(e); + m_tab->thumbnailAreaWheelEvent(e); } } -void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +void ImagesThumbnails::resizeEvent(QResizeEvent* e) { if (m_tab) { - m_tab->thumbnailsMouseEvent(e); + m_tab->scrollAreaResized(e->size()); } } @@ -505,6 +493,19 @@ bool ImagesThumbnails::event(QEvent* e) } +void ImagesScrollbar::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesScrollbar::wheelEvent(QWheelEvent* e) +{ + if (m_tab) { + m_tab->thumbnailAreaWheelEvent(e); + } +} + + ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) { @@ -564,14 +565,8 @@ void ScalableImage::paintEvent(QPaintEvent* e) const QRect imageRect = widgetRect.adjusted( m_border, m_border, -m_border, -m_border); - const auto ratio = std::min({ - 1.0, - static_cast(imageRect.width()) / m_original.width(), - static_cast(imageRect.height()) / m_original.height()}); - - const QSize scaledSize( - static_cast(std::round(m_original.width() * ratio)), - static_cast(std::round(m_original.height() * ratio))); + const QSize scaledSize = resizeWithAspectRatio( + m_original.size(), imageRect.size()); if (m_scaled.isNull() || m_scaled.size() != scaledSize) { m_scaled = m_original.scaled( @@ -580,16 +575,98 @@ void ScalableImage::paintEvent(QPaintEvent* e) } const QRect drawBorderRect = widgetRect.adjusted(0, 0, -1, -1); - - const QRect drawImageRect( - (imageRect.left()+imageRect.width()/2) - m_scaled.width()/2, - (imageRect.top()+imageRect.height()/2) - m_scaled.height()/2, - m_scaled.width(), m_scaled.height()); - + const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); QPainter painter(this); - painter.setPen(QColor(Qt::black)); painter.drawRect(drawBorderRect); painter.drawImage(drawImageRect, m_scaled); } + + +ImagesGeometry::ImagesGeometry( + const QSize& widgetSize, int margins, int border, int padding, int spacing) : + m_widgetSize(widgetSize), + m_margins(margins), m_padding(padding), m_border(border), + m_spacing(spacing), m_topRect(calcTopRect()) +{ +} + +QRect ImagesGeometry::calcTopRect() const +{ + const auto thumbWidth = m_widgetSize.width() - (m_margins * 2); + const auto imageSize = thumbWidth - (m_border * 2) - (m_padding * 2); + const auto thumbHeight = m_padding + m_border + imageSize + m_border + m_padding; + + return {m_margins, m_margins, thumbWidth, thumbHeight}; +} + +std::size_t ImagesGeometry::fullyVisibleCount() const +{ + const auto r = thumbRect(0); + const auto visible = (m_widgetSize.height() / (r.height() + m_spacing)); + return static_cast(visible); +} + +QRect ImagesGeometry::thumbRect(std::size_t i) const +{ + // rect for the top thumbnail + QRect r = m_topRect; + + // move down + const auto thumbWithSpacing = m_spacing + r.height(); + r.translate(0, static_cast(i * thumbWithSpacing)); + + return r; +} + +QRect ImagesGeometry::borderRect(std::size_t i) const +{ + auto r = thumbRect(i); + + // border rect is currently the same as thumb rect, but may change if + // captions are added + + return r; +} + +QRect ImagesGeometry::imageRect(std::size_t i) const +{ + auto r = borderRect(i); + + // remove border and padding + const auto m = m_border + m_padding; + r.adjust(m, m, -m, -m); + + return r; +} + +std::size_t ImagesGeometry::indexAt(const QPoint& p) const +{ + // calculate index purely based on y position + const std::size_t offset = p.y() / (m_topRect.height() + m_spacing); + + if (!borderRect(offset).contains(p)) { + return BadIndex; + } + + return offset; +} + +QSize ImagesGeometry::scaledImageSize(const QSize& originalSize) const +{ + const auto availableSize = imageRect(0).size(); + return resizeWithAspectRatio(originalSize, availableSize); +} + +void ImagesGeometry::dump() const +{ + qDebug() + << "ImagesTab geometry:\n" + << " . widget size: " << m_widgetSize << "\n" + << " . margins: " << m_margins << "\n" + << " . border: " << m_border << "\n" + << " . padding: " << m_padding << "\n" + << " . spacing: " << m_spacing << "\n" + << " . top rect: " << m_topRect; +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index fa7115b0..60ce9def 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -3,26 +3,32 @@ #include "modinfodialogtab.h" #include "filterwidget.h" -#include +#include class ImagesTab; -class ImagesScrollArea : public QScrollArea +// vertical scrollbar, this is only to handle wheel events to scroll by one +// instead of the system's scroll setting +// +class ImagesScrollbar : public QScrollBar { - Q_OBJECT; - public: - using QScrollArea::QScrollArea; + using QScrollBar::QScrollBar; void setTab(ImagesTab* tab); protected: - void resizeEvent(QResizeEvent* e) override; + // forwards to ImagesTab::thumbnailAreaWheelEvent() + // + void wheelEvent(QWheelEvent* event) override; private: ImagesTab* m_tab = nullptr; }; +// widget inside the scroller, calls ImagesTab::paintThumbnailArea() when +// needed and also forwards mouse clicks and tooltip events +// class ImagesThumbnails : public QWidget { Q_OBJECT; @@ -32,8 +38,24 @@ public: void setTab(ImagesTab* tab); protected: + // forwards to ImagesTab::paintThumbnailArea() + // void paintEvent(QPaintEvent* e) override; + + // forwards to ImagesTab::thumbnailAreaMouseEvent() + // void mousePressEvent(QMouseEvent* e) override; + + // forwards to ImagesTab::thumbnailAreaWheelEvent() + // + void wheelEvent(QWheelEvent* e); + + // forwards to ImagesTab::scrollAreaResized() + // + void resizeEvent(QResizeEvent* e) override; + + // forwards to ImagesTab::showTooltip for tooltip events + // bool event(QEvent* e) override; private: @@ -41,6 +63,8 @@ private: }; +// a widget that draws an image scaled to fit while keeping the aspect ratio +// class ScalableImage : public QWidget { Q_OBJECT; @@ -48,12 +72,16 @@ class ScalableImage : public QWidget public: ScalableImage(QString path={}); + // sets the image to draw void setImage(const QString& path); void setImage(QImage image); + + // removes the image, won't draw the border nor the image void clear(); - bool hasHeightForWidth() const; - int heightForWidth(int w) const; + // tells the QWidget's layout manager this widget is always square + bool hasHeightForWidth() const override; + int heightForWidth(int w) const override; protected: void paintEvent(QPaintEvent* e) override; @@ -65,10 +93,105 @@ private: }; +// handles all the geometry calculations by ImagesTab for painting or handling +// mouse clicks +// +// a thumbnail looks like this: +// +// +-----------------------+ <-- thumb rect +// | margins | +// | | +// | +-border--------+ <------ border rect +// | | padding | | +// | | | | +// | | +-------+ <----------- image rect +// | | | | | | +// | | | image | | | +// | | | | | | +// | | +-------+ | | +// | | | | +// | +---------------+ | +// | | +// +-----------------------+ +// +// spacing +// +// +-----------------------+ <-- thumb rect +// | margins | +// | | +// .... +// +// +class ImagesGeometry +{ +public: + // returned by indexAt() if the point is outside any possible thumbnail + static constexpr std::size_t BadIndex = + std::numeric_limits::max(); + + ImagesGeometry( + const QSize& widgetSize, int margins, int border, int padding, int spacing); + + // returns the number of images fully visible in the widget + // + std::size_t fullyVisibleCount() const; + + // rectangle around the whole thumbnail + // + QRect thumbRect(std::size_t i) const; + + // rectangle of the border for the given thumbnail + // + QRect borderRect(std::size_t i) const; + + // rectangle of the image for the given thumbnail + // + QRect imageRect(std::size_t i) const; + + // returns the index of the image at the given point; this does not take into + // account any scrolling, the image at the top of widget is always 0 + // + std::size_t indexAt(const QPoint& p) const; + + // returns the size of the image that fits in imageRect() while keeping the + // same aspect ratio as the given one + // + QSize scaledImageSize(const QSize& originalSize) const; + + // dumps stuff to qDebug() + // + void dump() const; + +private: + // size of the widget containing all the thumbnails + const QSize m_widgetSize; + + // space outside the thumbnail border + const int m_margins; + + // size of the border + const int m_border; + + // space between the border and the image + const int m_padding; + + // spacing between thumbnails + const int m_spacing; + + // rectangle of the first thumbnail on top + const QRect m_topRect; + + + // calculates the top rectangle + // + QRect calcTopRect() const; +}; + + class ImagesTab : public ModInfoDialogTab { Q_OBJECT; - friend class ImagesScrollArea; + friend class ImagesScrollbar; friend class ImagesThumbnails; public: @@ -95,23 +218,11 @@ private: } }; - struct PaintContext - { - QPainter painter; - int thumbSize; - QRect topRect; - - PaintContext(QWidget* w) - : painter(w), thumbSize(0) - { - } - }; - ScalableImage* m_image; std::vector m_files; std::vector m_filteredFiles; std::vector m_supportedFormats; - int m_margins, m_padding, m_border; + int m_margins, m_border, m_padding, m_spacing; const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; @@ -122,27 +233,29 @@ private: bool needsFiltering() const; void scrollAreaResized(const QSize& s); - void paintThumbnails(QPaintEvent* e); - void thumbnailsMouseEvent(QMouseEvent* e); + void paintThumbnailsArea(QPaintEvent* e); + void thumbnailAreaMouseEvent(QMouseEvent* e); + void thumbnailAreaWheelEvent(QWheelEvent* e); + void onScrolled(); + void showTooltip(QHelpEvent* e); void onExplore(); void onShowDDS(); void onFilterChanged(); - int calcThumbSize(int availableWidth) const; - int calcWidgetHeight(int availableWidth) const; - QRect calcTopThumbRect(int thumbSize) const; - std::pair calcVisibleRange( - int top, int bottom, int thumbSize) const; + ImagesGeometry makeGeometry() const; + + void paintThumbnail( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); - QRect calcBorderRect(const QRect& topRect, int thumbSize, std::size_t i) const; - QRect calcImageRect(const QRect& topRect, int thumbSize, std::size_t i) const; - QSize calcScaledImageSize( - const QSize& originalSize, const QSize& imageSize) const; + void paintThumbnailBorder( + QPainter& painter, const ImagesGeometry& geo, + std::size_t i); - void paintThumbnail(PaintContext& cx, std::size_t i); - void paintThumbnailBorder(PaintContext& cx, std::size_t i); - void paintThumbnailImage(PaintContext& cx, std::size_t i); + void paintThumbnailImage( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); const File* fileAtPos(const QPoint& p) const; @@ -151,8 +264,8 @@ private: File* getFile(std::size_t i); void filterImages(); - bool needsReload(const File& file, const QSize& imageSize) const; - void reload(File& file, const QSize& imageSize); + bool needsReload(const ImagesGeometry& geo, const File& file) const; + void reload(const ImagesGeometry& geo, File& file); void resizeWidget(); }; -- cgit v1.3.1 From 48607afc65e2f06bbc8b4b05386c6de89a33b7bf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 07:27:41 -0400 Subject: changed thumbnails to use margin around border selected thumbnail now has a background color --- src/modinfodialogimages.cpp | 63 +++++++++++++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 16 +++++++++++- 2 files changed, 67 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 5c93e3d4..7e021f97 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -65,6 +65,17 @@ ImagesTab::ImagesTab( ui->imagesThumbnails->setAutoFillBackground(false); ui->imagesThumbnails->setAttribute(Qt::WA_OpaquePaintEvent, true); + + { + auto list = std::make_unique(); + parentWidget()->style()->polish(list.get()); + + m_colors.border = QColor(Qt::black); + m_colors.background = QColor(Qt::black); + m_colors.selection = list->palette().color(QPalette::Highlight); + + m_image->setColors(m_colors.border, m_colors.background); + } } void ImagesTab::clear() @@ -92,7 +103,7 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { filterImages(); - resizeWidget(); + updateScrollbar(); ui->imagesThumbnails->update(); setHasData(fileCount() > 0); @@ -231,6 +242,7 @@ void ImagesTab::select(const File* f) } m_selection = f; + ui->imagesThumbnails->update(); } ImagesGeometry ImagesTab::makeGeometry() const @@ -268,10 +280,21 @@ void ImagesTab::paintThumbnail( QPainter& painter, const ImagesGeometry& geo, File& file, std::size_t i) { + paintThumbnailBackground(painter, geo, file, i); paintThumbnailBorder(painter, geo, i); paintThumbnailImage(painter, geo, file, i); } +void ImagesTab::paintThumbnailBackground( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i) +{ + if (&file == m_selection) { + const auto rect = geo.thumbRect(i); + painter.fillRect(rect, m_colors.selection); + } +} + void ImagesTab::paintThumbnailBorder( QPainter& painter, const ImagesGeometry& geo, std::size_t i) { @@ -282,7 +305,7 @@ void ImagesTab::paintThumbnailBorder( borderRect.setRight(borderRect.right() - 1); borderRect.setBottom(borderRect.bottom() - 1); - painter.setPen(QColor(Qt::black)); + painter.setPen(m_colors.border); painter.drawRect(borderRect); } @@ -305,6 +328,7 @@ void ImagesTab::paintThumbnailImage( const auto imageRect = geo.imageRect(i); const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); + painter.fillRect(scaledThumbRect, m_colors.background); painter.drawImage(scaledThumbRect, file.thumbnail); } @@ -333,7 +357,7 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const void ImagesTab::scrollAreaResized(const QSize&) { - resizeWidget(); + updateScrollbar(); } void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) @@ -426,7 +450,7 @@ void ImagesTab::reload(const ImagesGeometry& geo, File& file) Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } -void ImagesTab::resizeWidget() +void ImagesTab::updateScrollbar() { if (fileCount() == 0) { ui->imagesScrollerVBar->setRange(0, 0); @@ -547,6 +571,12 @@ int ScalableImage::heightForWidth(int w) const return w; } +void ScalableImage::setColors(const QColor& border, const QColor& background) +{ + m_borderColor = border; + m_backgroundColor = background; +} + void ScalableImage::paintEvent(QPaintEvent* e) { if (m_original.isNull()) { @@ -578,8 +608,15 @@ void ScalableImage::paintEvent(QPaintEvent* e) const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); QPainter painter(this); - painter.setPen(QColor(Qt::black)); + + // background + painter.fillRect(drawBorderRect, m_backgroundColor); + + // border + painter.setPen(m_borderColor); painter.drawRect(drawBorderRect); + + // image painter.drawImage(drawImageRect, m_scaled); } @@ -594,11 +631,14 @@ ImagesGeometry::ImagesGeometry( QRect ImagesGeometry::calcTopRect() const { - const auto thumbWidth = m_widgetSize.width() - (m_margins * 2); - const auto imageSize = thumbWidth - (m_border * 2) - (m_padding * 2); - const auto thumbHeight = m_padding + m_border + imageSize + m_border + m_padding; + const auto thumbWidth = m_widgetSize.width(); + const auto imageSize = thumbWidth - (m_margins * 2) - (m_border * 2) - (m_padding * 2); + const auto thumbHeight = + m_margins + m_border + m_padding + + imageSize + + m_border + m_padding + m_margins; - return {m_margins, m_margins, thumbWidth, thumbHeight}; + return {0, 0, thumbWidth, thumbHeight}; } std::size_t ImagesGeometry::fullyVisibleCount() const @@ -624,8 +664,9 @@ QRect ImagesGeometry::borderRect(std::size_t i) const { auto r = thumbRect(i); - // border rect is currently the same as thumb rect, but may change if - // captions are added + // remove margins + const auto m = m_margins; + r.adjust(m, m, -m, -m); return r; } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 60ce9def..db541ed8 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -83,6 +83,9 @@ public: bool hasHeightForWidth() const override; int heightForWidth(int w) const override; + // sets the colors + void setColors(const QColor& border, const QColor& background); + protected: void paintEvent(QPaintEvent* e) override; @@ -90,6 +93,7 @@ private: QString m_path; QImage m_original, m_scaled; int m_border; + QColor m_borderColor, m_backgroundColor; }; @@ -218,6 +222,11 @@ private: } }; + struct Colors + { + QColor border, background, selection; + }; + ScalableImage* m_image; std::vector m_files; std::vector m_filteredFiles; @@ -226,6 +235,7 @@ private: const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; + Colors m_colors; void getSupportedFormats(); void enableDDS(bool b); @@ -249,6 +259,10 @@ private: QPainter& painter, const ImagesGeometry& geo, File& file, std::size_t i); + void paintThumbnailBackground( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); + void paintThumbnailBorder( QPainter& painter, const ImagesGeometry& geo, std::size_t i); @@ -266,7 +280,7 @@ private: void filterImages(); bool needsReload(const ImagesGeometry& geo, const File& file) const; void reload(const ImagesGeometry& geo, File& file); - void resizeWidget(); + void updateScrollbar(); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From c2df11298b3be4919a660206e5100ee6dbdecd1c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 08:43:18 -0400 Subject: refactored stuff into PaintContext and Files keep selection when switching back to no filtering up/down keyboard navigation --- src/modinfodialog.ui | 6 +- src/modinfodialogimages.cpp | 445 ++++++++++++++++++++++++++++++-------------- src/modinfodialogimages.h | 98 +++++++--- 3 files changed, 387 insertions(+), 162 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a9af2593..e745967d 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -207,7 +207,11 @@ 0 - + + + Qt::StrongFocus + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 7e021f97..4179c62a 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -33,7 +33,7 @@ ImagesTab::ImagesTab( ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage), m_margins(3), m_border(1), m_padding(0), m_spacing(5), - m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) + m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -80,11 +80,8 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { - m_files.clear(); - m_filteredFiles.clear(); ui->imagesScrollerVBar->setValue(0); - - select(nullptr); + select(BadIndex); setHasData(false); } @@ -92,7 +89,7 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) { for (const auto& ext : m_supportedFormats) { if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { - m_files.push_back({fullPath}); + m_files.add({fullPath}); return true; } } @@ -102,11 +99,11 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { - filterImages(); + checkFiltering(); updateScrollbar(); ui->imagesThumbnails->update(); - setHasData(fileCount() > 0); + setHasData(m_files.size() > 0); } void ImagesTab::saveState(Settings& s) @@ -121,88 +118,79 @@ void ImagesTab::restoreState(const Settings& s) .value("mod_info_dialog_images_show_dds", false).toBool()); } -void ImagesTab::filterImages() +void ImagesTab::checkFiltering() { - if (!needsFiltering()) { - return; + if (m_filter.empty() && m_ddsEnabled) { + // no filtering needed + + if (m_files.isFiltered()) { + // was filtered, needs switch + switchToAll(); + } + } else { + // filtering is needed + switchToFiltered(); } +} - m_filteredFiles.clear(); +void ImagesTab::switchToAll() +{ + // remember selection + const auto* oldSelection = m_files.selectedFile(); + + // switch + m_files.switchToAll(); + + // reselect old + if (oldSelection) { + m_files.select(m_files.indexOf(oldSelection)); + } else { + m_files.select(BadIndex); + } +} + +void ImagesTab::switchToFiltered() +{ + // remember old selection, will be checked when building the filtered list + // below + const auto* oldSelection = m_files.selectedFile(); + std::size_t newSelection = BadIndex; + + // switch, also clears list + m_files.switchToFiltered(); - bool hasTextFilter = !m_filter.empty(); - bool sawSelection = false; + const bool hasTextFilter = !m_filter.empty(); - for (auto& f : m_files) { + for (auto& f : m_files.allFiles()) { if (hasTextFilter) { + // check filter widget const auto m = m_filter.matches([&](auto&& what) { return f.path.contains(what, Qt::CaseInsensitive); - }); + }); if (!m) { + // no match, skip continue; } } if (!m_ddsEnabled) { + // skip .dds files if (f.path.endsWith(".dds", Qt::CaseInsensitive)) { continue; } } - if (&f == m_selection) { - sawSelection = true; - } - - m_filteredFiles.push_back(&f); - } - - if (!sawSelection) { - select(nullptr); - } -} - -std::size_t ImagesTab::fileCount() const -{ - if (needsFiltering()) { - return m_filteredFiles.size(); - } else { - return m_files.size(); - } -} - -const ImagesTab::File* ImagesTab::getFile(std::size_t i) const -{ - if (needsFiltering()) { - if (i >= m_filteredFiles.size()) { - return nullptr; - } - - return m_filteredFiles[i]; - } else { - if (i >= m_files.size()) { - return nullptr; + if (&f == oldSelection) { + // found the old selection, remember its index + newSelection = m_files.size(); } - return &m_files[i]; - } -} - -ImagesTab::File* ImagesTab::getFile(std::size_t i) -{ - return const_cast(std::as_const(*this).getFile(i)); -} - -bool ImagesTab::needsFiltering() const -{ - if (!m_filter.empty()) { - return true; + m_files.addFiltered(&f); } - if (!m_ddsEnabled) { - return true; - } - - return false; + // reselect old, or clear if it wasn't found + select(newSelection); } void ImagesTab::getSupportedFormats() @@ -229,22 +217,101 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(const File* f) +void ImagesTab::select(std::size_t i) { - if (f) { + m_files.select(i); + + if (const auto* f=m_files.selectedFile()) { ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); + ensureVisible(i); } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); m_image->clear(); } - m_selection = f; ui->imagesThumbnails->update(); } +void ImagesTab::moveSelection(int direction) +{ + const auto i = m_files.selectedIndex(); + + if (i == BadIndex) { + if (m_files.size() > 0) { + select(0); + } + + return; + } + + if (direction > 0) { + // moving down + if (i < (m_files.size() - 1)) { + select(i + 1); + } + } else { + // moving up + if (i > 0) { + select(i - 1); + } + } +} + +void ImagesTab::ensureVisible(std::size_t i) +{ + const auto geo = makeGeometry(); + + const auto fullyVisible = geo.fullyVisibleCount(); + const auto first = ui->imagesScrollerVBar->value(); + + if (i < first) { + // go up + ui->imagesScrollerVBar->setValue(static_cast(i)); + } else if (i >= first + fullyVisible) { + // go down + + if (i >= fullyVisible) { + ui->imagesScrollerVBar->setValue(static_cast(i - fullyVisible + 1)); + } + } +} + +std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const +{ + const auto geo = makeGeometry(); + + // this is the index relative to the top + const auto offset = geo.indexAt(p); + if (offset == ImagesGeometry::BadIndex) { + return BadIndex; + } + + const auto first = ui->imagesScrollerVBar->value(); + if (first < 0) { + return BadIndex; + } + + const auto i = static_cast(first) + offset; + if (i >= m_files.size()) { + return BadIndex; + } + + return i; +} + +const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const +{ + const auto i = fileIndexAtPos(p); + if (i >= m_files.size()) { + return nullptr; + } + + return m_files.get(i); +} + ImagesGeometry ImagesTab::makeGeometry() const { return ImagesGeometry( @@ -254,105 +321,78 @@ ImagesGeometry ImagesTab::makeGeometry() const void ImagesTab::paintThumbnailsArea(QPaintEvent* e) { - const auto geo = makeGeometry(); - - QPainter painter(ui->imagesThumbnails); + PaintContext cx(ui->imagesThumbnails, makeGeometry()); - painter.fillRect( + cx.painter.fillRect( ui->imagesThumbnails->rect(), ui->imagesThumbnails->palette().color(QPalette::Window)); - const auto visible = geo.fullyVisibleCount() + 1; + const auto visible = cx.geo.fullyVisibleCount() + 1; const auto first = ui->imagesScrollerVBar->value(); for (std::size_t i=0; ifailed) { return; } - if (needsReload(geo, file)) { - reload(geo, file); + if (needsReload(cx.geo, *cx.file)) { + reload(cx.geo, *cx.file); } - if (file.thumbnail.isNull()) { + if (cx.file->thumbnail.isNull()) { return; } - const auto imageRect = geo.imageRect(i); - const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); - - painter.fillRect(scaledThumbRect, m_colors.background); - painter.drawImage(scaledThumbRect, file.thumbnail); -} - -const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const -{ - const auto geo = makeGeometry(); - - // this is the index relative to the top - const auto offset = geo.indexAt(p); - if (offset == ImagesGeometry::BadIndex) { - return nullptr; - } - - const auto first = ui->imagesScrollerVBar->value(); - if (first < 0) { - return nullptr; - } - - const auto i = static_cast(first) + offset; - if (i >= fileCount()) { - return nullptr; - } + const auto imageRect = cx.geo.imageRect(cx.thumbIndex); + const auto scaledThumbRect = centeredRect( + imageRect, cx.file->thumbnail.size()); - return getFile(i); + cx.painter.fillRect(scaledThumbRect, m_colors.background); + cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail); } void ImagesTab::scrollAreaResized(const QSize&) @@ -366,7 +406,7 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) return; } - select(fileAtPos(e->pos())); + select(fileIndexAtPos(e->pos())); } void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) @@ -377,6 +417,19 @@ void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); } +bool ImagesTab::thumbnailAreaKeyPressEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Down) { + moveSelection(+1); + return true; + } else if (e->key() == Qt::Key_Up) { + moveSelection(-1); + return true; + } + + return false; +} + void ImagesTab::onScrolled() { ui->imagesThumbnails->update(); @@ -398,11 +451,9 @@ void ImagesTab::showTooltip(QHelpEvent* e) void ImagesTab::onExplore() { - if (!m_selection) { - return; + if (auto* f=m_files.selectedFile()) { + MOBase::shell::ExploreFile(f->path); } - - MOBase::shell::ExploreFile(m_selection->path); } void ImagesTab::onShowDDS() @@ -452,7 +503,7 @@ void ImagesTab::reload(const ImagesGeometry& geo, File& file) void ImagesTab::updateScrollbar() { - if (fileCount() == 0) { + if (m_files.size() == 0) { ui->imagesScrollerVBar->setRange(0, 0); ui->imagesScrollerVBar->setEnabled(false); return; @@ -462,11 +513,11 @@ void ImagesTab::updateScrollbar() const auto availableSize = ui->imagesThumbnails->size(); const auto fullyVisible = geo.fullyVisibleCount(); - if (fullyVisible >= fileCount()) { + if (fullyVisible >= m_files.size()) { ui->imagesScrollerVBar->setRange(0, 0); ui->imagesScrollerVBar->setEnabled(false); } else { - const auto d = fileCount() - fullyVisible; + const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); ui->imagesScrollerVBar->setEnabled(true); } @@ -506,6 +557,17 @@ void ImagesThumbnails::resizeEvent(QResizeEvent* e) } } +void ImagesThumbnails::keyPressEvent(QKeyEvent* e) +{ + if (m_tab) { + if (m_tab->thumbnailAreaKeyPressEvent(e)) { + return; + } + } + + QWidget::keyPressEvent(e); +} + bool ImagesThumbnails::event(QEvent* e) { if (e->type() == QEvent::ToolTip) { @@ -711,3 +773,116 @@ void ImagesGeometry::dump() const << " . spacing: " << m_spacing << "\n" << " . top rect: " << m_topRect; } + + +ImagesTab::Files::Files() + : m_selection(BadIndex), m_filtered(false) +{ +} + +void ImagesTab::Files::clear() +{ + m_allFiles.clear(); + m_filteredFiles.clear(); + m_filtered = false; +} + +void ImagesTab::Files::add(File f) +{ + m_allFiles.emplace_back(std::move(f)); +} + +void ImagesTab::Files::addFiltered(File* f) +{ + m_filteredFiles.push_back(f); +} + +std::size_t ImagesTab::Files::size() const +{ + if (m_filtered) { + return m_filteredFiles.size(); + } else { + return m_allFiles.size(); + } +} + +void ImagesTab::Files::switchToAll() +{ + m_filtered = false; + m_filteredFiles.clear(); +} + +void ImagesTab::Files::switchToFiltered() +{ + m_filtered = true; + m_filteredFiles.clear(); +} + +const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const +{ + if (m_filtered) { + if (i < m_filteredFiles.size()) { + return m_filteredFiles[i]; + } + } else { + if (i < m_allFiles.size()) { + return &m_allFiles[i]; + } + } + + return nullptr; +} + +ImagesTab::File* ImagesTab::Files::get(std::size_t i) +{ + return const_cast(std::as_const(*this).get(i)); +} + +std::size_t ImagesTab::Files::indexOf(const File* f) const +{ + if (m_filtered) { + for (std::size_t i=0; i& ImagesTab::Files::allFiles() +{ + return m_allFiles; +} + +bool ImagesTab::Files::isFiltered() const +{ + return m_filtered; +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index db541ed8..c7f46975 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -54,6 +54,10 @@ protected: // void resizeEvent(QResizeEvent* e) override; + // forwards to ImagesTab::thumbnailAreaKeyPressEvent() + // + void keyPressEvent(QKeyEvent* e) override; + // forwards to ImagesTab::showTooltip for tooltip events // bool event(QEvent* e) override; @@ -210,6 +214,9 @@ public: void restoreState(const Settings& s) override; private: + static constexpr std::size_t BadIndex = + std::numeric_limits::max(); + struct File { QString path; @@ -227,25 +234,72 @@ private: QColor border, background, selection; }; + struct PaintContext + { + mutable QPainter painter; + ImagesGeometry geo; + File* file; + std::size_t thumbIndex; + std::size_t fileIndex; + + PaintContext(QWidget* w, ImagesGeometry geo) + : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0) + { + } + }; + + class Files + { + public: + Files(); + + void clear(); + + void add(File f); + void addFiltered(File* f); + + std::size_t size() const; + + void switchToAll(); + void switchToFiltered(); + + const File* get(std::size_t i) const; + File* get(std::size_t i); + std::size_t indexOf(const File* f) const; + + const File* selectedFile() const; + File* selectedFile(); + std::size_t selectedIndex() const; + void select(std::size_t i); + + std::vector& allFiles(); + + bool isFiltered() const; + + private: + std::vector m_allFiles; + std::vector m_filteredFiles; + std::size_t m_selection; + bool m_filtered; + }; + + ScalableImage* m_image; - std::vector m_files; - std::vector m_filteredFiles; std::vector m_supportedFormats; + Files m_files; int m_margins, m_border, m_padding, m_spacing; - const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; Colors m_colors; void getSupportedFormats(); void enableDDS(bool b); - void select(const File* file); - bool needsFiltering() const; void scrollAreaResized(const QSize& s); void paintThumbnailsArea(QPaintEvent* e); void thumbnailAreaMouseEvent(QMouseEvent* e); void thumbnailAreaWheelEvent(QWheelEvent* e); + bool thumbnailAreaKeyPressEvent(QKeyEvent* e); void onScrolled(); void showTooltip(QHelpEvent* e); @@ -253,31 +307,23 @@ private: void onShowDDS(); void onFilterChanged(); - ImagesGeometry makeGeometry() const; - - void paintThumbnail( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); - - void paintThumbnailBackground( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); - - void paintThumbnailBorder( - QPainter& painter, const ImagesGeometry& geo, - std::size_t i); - - void paintThumbnailImage( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); + void select(std::size_t i); + void moveSelection(int direction); + void ensureVisible(std::size_t i); + std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; - std::size_t fileCount() const; - const File* getFile(std::size_t i) const; - File* getFile(std::size_t i); + ImagesGeometry makeGeometry() const; + + void paintThumbnail(const PaintContext& cx); + void paintThumbnailBackground(const PaintContext& cx); + void paintThumbnailBorder(const PaintContext& cx); + void paintThumbnailImage(const PaintContext& cx); - void filterImages(); + void checkFiltering(); + void switchToAll(); + void switchToFiltered(); bool needsReload(const ImagesGeometry& geo, const File& file) const; void reload(const ImagesGeometry& geo, File& file); void updateScrollbar(); -- cgit v1.3.1 From 933a2c7d1086ebc87ee2d214deaa3752ea8c6cf1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 09:06:41 -0400 Subject: added keyboard navigation for page down/up, end/home changed to draw the full image background only behind the image --- src/modinfodialogimages.cpp | 113 ++++++++++++++++++++++++++++++++------------ src/modinfodialogimages.h | 13 ++++- 2 files changed, 94 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4179c62a..7738d55f 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -50,7 +50,6 @@ ImagesTab::ImagesTab( ui->imagesThumbnails->setTab(this); ui->imagesScrollerVBar->setTab(this); - ui->imagesScrollerVBar->setSingleStep(1); connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); @@ -80,6 +79,7 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { + m_files.clear(); ui->imagesScrollerVBar->setValue(0); select(BadIndex); setHasData(false); @@ -221,7 +221,12 @@ void ImagesTab::select(std::size_t i) { m_files.select(i); - if (const auto* f=m_files.selectedFile()) { + if (auto* f=m_files.selectedFile()) { + // when jumping elsewhere in the list, such as with page down/up, the file + // might not be visible yet, which means it hasn't been loaded and would + // pass a null image in setImage() below + f->ensureOriginalLoaded(); + ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); @@ -235,29 +240,36 @@ void ImagesTab::select(std::size_t i) ui->imagesThumbnails->update(); } -void ImagesTab::moveSelection(int direction) +void ImagesTab::moveSelection(int by) { - const auto i = m_files.selectedIndex(); + if (m_files.empty()) { + return; + } + auto i = m_files.selectedIndex(); if (i == BadIndex) { - if (m_files.size() > 0) { - select(0); - } - - return; + i = 0; } - if (direction > 0) { + if (by > 0) { // moving down - if (i < (m_files.size() - 1)) { - select(i + 1); + i += static_cast(by); + + if (i >= m_files.size()) { + i = (m_files.size() - 1); } - } else { + } else if (by < 0) { // moving up - if (i > 0) { - select(i - 1); + const auto abs_by = static_cast(std::abs(by)); + + if (abs_by > i) { + i = 0; + } else { + i -= abs_by; } } + + select(i); } void ImagesTab::ensureVisible(std::size_t i) @@ -406,7 +418,10 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) return; } - select(fileIndexAtPos(e->pos())); + const auto i = fileIndexAtPos(e->pos()); + if (i != BadIndex) { + select(i); + } } void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) @@ -419,12 +434,39 @@ void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) bool ImagesTab::thumbnailAreaKeyPressEvent(QKeyEvent* e) { - if (e->key() == Qt::Key_Down) { - moveSelection(+1); - return true; - } else if (e->key() == Qt::Key_Up) { - moveSelection(-1); - return true; + switch (e->key()) { + case Qt::Key_Down: { + moveSelection(ui->imagesScrollerVBar->singleStep()); + return true; + } + + case Qt::Key_Up: { + moveSelection(-ui->imagesScrollerVBar->singleStep()); + return true; + } + + case Qt::Key_PageDown: { + moveSelection(ui->imagesScrollerVBar->pageStep()); + return true; + } + + case Qt::Key_PageUp: { + moveSelection(-ui->imagesScrollerVBar->pageStep()); + return true; + } + + case Qt::Key_Home: { + select(0); + return true; + } + + case Qt::Key_End: { + if (!m_files.empty()) { + select(m_files.size() - 1); + } + + return true; + } } return false; @@ -487,13 +529,10 @@ bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const void ImagesTab::reload(const ImagesGeometry& geo, File& file) { file.failed = false; + file.ensureOriginalLoaded(); - if (file.original.isNull()) { - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - return; - } + if (file.failed) { + return; } file.thumbnail = file.original.scaled( @@ -519,6 +558,8 @@ void ImagesTab::updateScrollbar() } else { const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); + ui->imagesScrollerVBar->setSingleStep(1); + ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible)); ui->imagesScrollerVBar->setEnabled(true); } } @@ -671,13 +712,13 @@ void ScalableImage::paintEvent(QPaintEvent* e) QPainter painter(this); - // background - painter.fillRect(drawBorderRect, m_backgroundColor); - // border painter.setPen(m_borderColor); painter.drawRect(drawBorderRect); + // background + painter.fillRect(drawImageRect, m_backgroundColor); + // image painter.drawImage(drawImageRect, m_scaled); } @@ -784,6 +825,7 @@ void ImagesTab::Files::clear() { m_allFiles.clear(); m_filteredFiles.clear(); + m_selection = BadIndex; m_filtered = false; } @@ -797,6 +839,15 @@ void ImagesTab::Files::addFiltered(File* f) m_filteredFiles.push_back(f); } +bool ImagesTab::Files::empty() const +{ + if (m_filtered) { + return m_filteredFiles.empty(); + } else { + return m_allFiles.empty(); + } +} + std::size_t ImagesTab::Files::size() const { if (m_filtered) { diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index c7f46975..5ce66d5d 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -227,6 +227,16 @@ private: : path(std::move(path)) { } + + void ensureOriginalLoaded() + { + if (original.isNull()) { + if (!original.load(path)) { + qCritical() << "failed to load image from " << path; + failed = true; + } + } + } }; struct Colors @@ -258,6 +268,7 @@ private: void add(File f); void addFiltered(File* f); + bool empty() const; std::size_t size() const; void switchToAll(); @@ -308,7 +319,7 @@ private: void onFilterChanged(); void select(std::size_t i); - void moveSelection(int direction); + void moveSelection(int by); void ensureVisible(std::size_t i); std::size_t fileIndexAtPos(const QPoint& p) const; -- cgit v1.3.1 From 1c0024b2feda8b5c152d2e211205601ee807e517 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:27:02 -0400 Subject: moved all images classes into a namespace with shorter names split theme and metrics into their own class added filename to thumbs --- src/modinfodialog.ui | 8 +- src/modinfodialogimages.cpp | 379 +++++++++++++++++++++++++++++--------------- src/modinfodialogimages.h | 263 +++++++++++++++++------------- 3 files changed, 405 insertions(+), 245 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e745967d..3da13261 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -207,14 +207,14 @@ 0 - + Qt::StrongFocus - + Qt::Vertical @@ -1239,13 +1239,13 @@ p, li { white-space: pre-wrap; }
    texteditor.h
    - ImagesThumbnails + ImagesTabHelpers::ThumbnailsWidget QWidget
    modinfodialogimages.h
    1
    - ImagesScrollbar + ImagesTabHelpers::Scrollbar QScrollBar
    modinfodialogimages.h
    diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 7738d55f..8c323ac6 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -3,6 +3,8 @@ #include "settings.h" #include "utility.h" +using namespace ImagesTabHelpers; + QSize resizeWithAspectRatio(const QSize& original, const QSize& available) { const auto ratio = std::min({ @@ -32,7 +34,6 @@ ImagesTab::ImagesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage), - m_margins(3), m_border(1), m_padding(0), m_spacing(5), m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -69,11 +70,19 @@ ImagesTab::ImagesTab( auto list = std::make_unique(); parentWidget()->style()->polish(list.get()); - m_colors.border = QColor(Qt::black); - m_colors.background = QColor(Qt::black); - m_colors.selection = list->palette().color(QPalette::Highlight); + m_theme.borderColor = QColor(Qt::black); + m_theme.backgroundColor = QColor(Qt::black); + m_theme.textColor = list->palette().color(QPalette::WindowText); + + m_theme.highlightBackgroundColor = list->palette().color(QPalette::Highlight); + m_theme.highlightTextColor = list->palette().color(QPalette::HighlightedText); + + m_theme.font = list->font(); - m_image->setColors(m_colors.border, m_colors.background); + const QFontMetrics fm(m_theme.font); + m_metrics.textHeight = fm.height(); + + m_image->setColors(m_theme.borderColor, m_theme.backgroundColor); } } @@ -165,8 +174,8 @@ void ImagesTab::switchToFiltered() if (hasTextFilter) { // check filter widget const auto m = m_filter.matches([&](auto&& what) { - return f.path.contains(what, Qt::CaseInsensitive); - }); + return f.path().contains(what, Qt::CaseInsensitive); + }); if (!m) { // no match, skip @@ -176,7 +185,7 @@ void ImagesTab::switchToFiltered() if (!m_ddsEnabled) { // skip .dds files - if (f.path.endsWith(".dds", Qt::CaseInsensitive)) { + if (f.path().endsWith(".dds", Qt::CaseInsensitive)) { continue; } } @@ -227,9 +236,9 @@ void ImagesTab::select(std::size_t i) // pass a null image in setImage() below f->ensureOriginalLoaded(); - ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); + ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); - m_image->setImage(f->original); + m_image->setImage(f->original()); ensureVisible(i); } else { ui->imagesPath->clear(); @@ -297,7 +306,7 @@ std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const // this is the index relative to the top const auto offset = geo.indexAt(p); - if (offset == ImagesGeometry::BadIndex) { + if (offset == BadIndex) { return BadIndex; } @@ -314,7 +323,7 @@ std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const return i; } -const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const +const File* ImagesTab::fileAtPos(const QPoint& p) const { const auto i = fileIndexAtPos(p); if (i >= m_files.size()) { @@ -324,11 +333,9 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const return m_files.get(i); } -ImagesGeometry ImagesTab::makeGeometry() const +Geometry ImagesTab::makeGeometry() const { - return ImagesGeometry( - ui->imagesThumbnails->size(), - m_margins, m_border, m_padding, m_spacing); + return Geometry(ui->imagesThumbnails->size(), m_metrics); } void ImagesTab::paintThumbnailsArea(QPaintEvent* e) @@ -362,13 +369,14 @@ void ImagesTab::paintThumbnail(const PaintContext& cx) paintThumbnailBackground(cx); paintThumbnailBorder(cx); paintThumbnailImage(cx); + paintThumbnailText(cx); } void ImagesTab::paintThumbnailBackground(const PaintContext& cx) { if (m_files.selectedIndex() == cx.fileIndex) { const auto rect = cx.geo.thumbRect(cx.thumbIndex); - cx.painter.fillRect(rect, m_colors.selection); + cx.painter.fillRect(rect, m_theme.highlightBackgroundColor); } } @@ -381,30 +389,46 @@ void ImagesTab::paintThumbnailBorder(const PaintContext& cx) borderRect.setRight(borderRect.right() - 1); borderRect.setBottom(borderRect.bottom() - 1); - cx.painter.setPen(m_colors.border); + cx.painter.setPen(m_theme.borderColor); cx.painter.drawRect(borderRect); } void ImagesTab::paintThumbnailImage(const PaintContext& cx) { - if (cx.file->failed) { + if (cx.file->failed()) { return; } - if (needsReload(cx.geo, *cx.file)) { - reload(cx.geo, *cx.file); - } - - if (cx.file->thumbnail.isNull()) { - return; - } + cx.file->loadIfNeeded(cx.geo); const auto imageRect = cx.geo.imageRect(cx.thumbIndex); const auto scaledThumbRect = centeredRect( - imageRect, cx.file->thumbnail.size()); + imageRect, cx.file->thumbnail().size()); - cx.painter.fillRect(scaledThumbRect, m_colors.background); - cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail); + cx.painter.fillRect(scaledThumbRect, m_theme.backgroundColor); + cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail()); +} + +void ImagesTab::paintThumbnailText(const PaintContext& cx) +{ + const auto tr = cx.geo.textRect(cx.thumbIndex); + + if (cx.fileIndex == m_files.selectedIndex()) { + cx.painter.setPen(m_theme.highlightTextColor); + } else { + cx.painter.setPen(m_theme.textColor); + } + + cx.painter.setFont(m_theme.font); + + QFontMetrics fm(m_theme.font); + + const auto text = fm.elidedText( + cx.file->filename(), Qt::ElideRight, tr.width()); + + const auto flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine; + + cx.painter.drawText(tr, flags, text); } void ImagesTab::scrollAreaResized(const QSize&) @@ -487,14 +511,14 @@ void ImagesTab::showTooltip(QHelpEvent* e) } QToolTip::showText( - e->globalPos(), QDir::toNativeSeparators(f->path), + e->globalPos(), QDir::toNativeSeparators(f->path()), ui->imagesThumbnails); } void ImagesTab::onExplore() { if (auto* f=m_files.selectedFile()) { - MOBase::shell::ExploreFile(f->path); + MOBase::shell::ExploreFile(f->path()); } } @@ -512,34 +536,6 @@ void ImagesTab::onFilterChanged() update(); } -bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const -{ - if (file.failed) { - return false; - } - - if (file.original.isNull() || file.thumbnail.isNull()) { - return true; - } - - const auto scaledSize = geo.scaledImageSize(file.original.size()); - return (file.thumbnail.size() != scaledSize); -} - -void ImagesTab::reload(const ImagesGeometry& geo, File& file) -{ - file.failed = false; - file.ensureOriginalLoaded(); - - if (file.failed) { - return; - } - - file.thumbnail = file.original.scaled( - geo.scaledImageSize(file.original.size()), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); -} - void ImagesTab::updateScrollbar() { if (m_files.size() == 0) { @@ -565,40 +561,56 @@ void ImagesTab::updateScrollbar() } -void ImagesThumbnails::setTab(ImagesTab* tab) +namespace ImagesTabHelpers +{ + +void Scrollbar::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void Scrollbar::wheelEvent(QWheelEvent* e) +{ + if (m_tab) { + m_tab->thumbnailAreaWheelEvent(e); + } +} + + +void ThumbnailsWidget::setTab(ImagesTab* tab) { m_tab = tab; } -void ImagesThumbnails::paintEvent(QPaintEvent* e) +void ThumbnailsWidget::paintEvent(QPaintEvent* e) { if (m_tab) { m_tab->paintThumbnailsArea(e); } } -void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +void ThumbnailsWidget::mousePressEvent(QMouseEvent* e) { if (m_tab) { m_tab->thumbnailAreaMouseEvent(e); } } -void ImagesThumbnails::wheelEvent(QWheelEvent* e) +void ThumbnailsWidget::wheelEvent(QWheelEvent* e) { if (m_tab) { m_tab->thumbnailAreaWheelEvent(e); } } -void ImagesThumbnails::resizeEvent(QResizeEvent* e) +void ThumbnailsWidget::resizeEvent(QResizeEvent* e) { if (m_tab) { m_tab->scrollAreaResized(e->size()); } } -void ImagesThumbnails::keyPressEvent(QKeyEvent* e) +void ThumbnailsWidget::keyPressEvent(QKeyEvent* e) { if (m_tab) { if (m_tab->thumbnailAreaKeyPressEvent(e)) { @@ -609,7 +621,7 @@ void ImagesThumbnails::keyPressEvent(QKeyEvent* e) QWidget::keyPressEvent(e); } -bool ImagesThumbnails::event(QEvent* e) +bool ThumbnailsWidget::event(QEvent* e) { if (e->type() == QEvent::ToolTip) { m_tab->showTooltip(static_cast(e)); @@ -620,19 +632,6 @@ bool ImagesThumbnails::event(QEvent* e) } -void ImagesScrollbar::setTab(ImagesTab* tab) -{ - m_tab = tab; -} - -void ImagesScrollbar::wheelEvent(QWheelEvent* e) -{ - if (m_tab) { - m_tab->thumbnailAreaWheelEvent(e); - } -} - - ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) { @@ -724,104 +723,220 @@ void ScalableImage::paintEvent(QPaintEvent* e) } -ImagesGeometry::ImagesGeometry( - const QSize& widgetSize, int margins, int border, int padding, int spacing) : - m_widgetSize(widgetSize), - m_margins(margins), m_padding(padding), m_border(border), - m_spacing(spacing), m_topRect(calcTopRect()) +Metrics::Metrics() : + margins(3), + border(1), + padding(0), + spacing(5), + textSpacing(2), + textHeight(0) +{ +} + + +Geometry::Geometry(QSize widgetSize, Metrics metrics) + : m_widgetSize(widgetSize), m_metrics(metrics), m_topRect(calcTopRect()) { } -QRect ImagesGeometry::calcTopRect() const +QRect Geometry::calcTopRect() const { const auto thumbWidth = m_widgetSize.width(); - const auto imageSize = thumbWidth - (m_margins * 2) - (m_border * 2) - (m_padding * 2); + const auto& m = m_metrics; + + const auto imageSize = + thumbWidth - + (m.margins * 2) - + (m.border * 2) - + (m.padding * 2); + const auto thumbHeight = - m_margins + m_border + m_padding + + m.margins + + m.border + m.padding + imageSize + - m_border + m_padding + m_margins; + m.padding + m.border + + m.textSpacing + m.textHeight + + m.margins; return {0, 0, thumbWidth, thumbHeight}; } -std::size_t ImagesGeometry::fullyVisibleCount() const +std::size_t Geometry::fullyVisibleCount() const { const auto r = thumbRect(0); - const auto visible = (m_widgetSize.height() / (r.height() + m_spacing)); + + const auto thumbWithSpacing = r.height() + m_metrics.spacing; + const auto visible = (m_widgetSize.height() / thumbWithSpacing); + return static_cast(visible); } -QRect ImagesGeometry::thumbRect(std::size_t i) const +QRect Geometry::thumbRect(std::size_t i) const { // rect for the top thumbnail QRect r = m_topRect; // move down - const auto thumbWithSpacing = m_spacing + r.height(); + const auto thumbWithSpacing = m_metrics.spacing + r.height(); r.translate(0, static_cast(i * thumbWithSpacing)); return r; } -QRect ImagesGeometry::borderRect(std::size_t i) const +QRect Geometry::borderRect(std::size_t i) const { auto r = thumbRect(i); + const auto& m = m_metrics; - // remove margins - const auto m = m_margins; - r.adjust(m, m, -m, -m); + // remove margins and text + r.adjust(m.margins, m.margins, -m.margins, -m.margins); + + // remove text + r.adjust(0, 0, 0, -(m.textSpacing + m.textHeight)); return r; } -QRect ImagesGeometry::imageRect(std::size_t i) const +QRect Geometry::imageRect(std::size_t i) const { auto r = borderRect(i); // remove border and padding - const auto m = m_border + m_padding; + const auto m = m_metrics.border + m_metrics.padding; r.adjust(m, m, -m, -m); return r; } -std::size_t ImagesGeometry::indexAt(const QPoint& p) const +QRect Geometry::textRect(std::size_t i) const +{ + const auto r = borderRect(i); + + return QRect( + r.left(), + r.bottom() + m_metrics.textSpacing, + r.width(), + m_metrics.textHeight); +} + +QRect Geometry::selectionRect(std::size_t i) const +{ + const auto br = borderRect(i); + const auto tr = textRect(i); + + return QRect( + br.left(), + br.top(), + br.width(), + tr.bottom() - br.top()); +} + +std::size_t Geometry::indexAt(const QPoint& p) const { // calculate index purely based on y position - const std::size_t offset = p.y() / (m_topRect.height() + m_spacing); + const std::size_t offset = p.y() / (m_topRect.height() + m_metrics.spacing); - if (!borderRect(offset).contains(p)) { + if (!selectionRect(offset).contains(p)) { return BadIndex; } return offset; } -QSize ImagesGeometry::scaledImageSize(const QSize& originalSize) const +QSize Geometry::scaledImageSize(const QSize& originalSize) const { const auto availableSize = imageRect(0).size(); return resizeWithAspectRatio(originalSize, availableSize); } -void ImagesGeometry::dump() const + +File::File(QString path) + : m_path(std::move(path)), m_failed(false) +{ +} + +void File::ensureOriginalLoaded() +{ + if (m_original.isNull()) { + if (!m_original.load(m_path)) { + qCritical() << "failed to load image from " << m_path; + m_failed = true; + } + } +} + +const QString& File::path() const +{ + return m_path; +} + +const QString& File::filename() const +{ + if (m_filename.isEmpty()) { + m_filename = QFileInfo(m_path).fileName(); + } + + return m_filename; +} + +const QImage& File::original() const { - qDebug() - << "ImagesTab geometry:\n" - << " . widget size: " << m_widgetSize << "\n" - << " . margins: " << m_margins << "\n" - << " . border: " << m_border << "\n" - << " . padding: " << m_padding << "\n" - << " . spacing: " << m_spacing << "\n" - << " . top rect: " << m_topRect; + return m_original; } +const QImage& File::thumbnail() const +{ + return m_thumbnail; +} + +bool File::failed() const +{ + return m_failed; +} + +void File::loadIfNeeded(const Geometry& geo) +{ + if (needsLoad(geo)) { + load(geo); + } +} + +bool File::needsLoad(const Geometry& geo) const +{ + if (m_failed) { + return false; + } + + if (m_original.isNull() || m_thumbnail.isNull()) { + return true; + } -ImagesTab::Files::Files() + const auto scaledSize = geo.scaledImageSize(m_original.size()); + return (m_thumbnail.size() != scaledSize); +} + +void File::load(const Geometry& geo) +{ + m_failed = false; + ensureOriginalLoaded(); + + if (m_failed) { + return; + } + + const auto scaledSize = geo.scaledImageSize(m_original.size()); + + m_thumbnail = m_original.scaled( + scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + + +Files::Files() : m_selection(BadIndex), m_filtered(false) { } -void ImagesTab::Files::clear() +void Files::clear() { m_allFiles.clear(); m_filteredFiles.clear(); @@ -829,17 +944,17 @@ void ImagesTab::Files::clear() m_filtered = false; } -void ImagesTab::Files::add(File f) +void Files::add(File f) { m_allFiles.emplace_back(std::move(f)); } -void ImagesTab::Files::addFiltered(File* f) +void Files::addFiltered(File* f) { m_filteredFiles.push_back(f); } -bool ImagesTab::Files::empty() const +bool Files::empty() const { if (m_filtered) { return m_filteredFiles.empty(); @@ -848,7 +963,7 @@ bool ImagesTab::Files::empty() const } } -std::size_t ImagesTab::Files::size() const +std::size_t Files::size() const { if (m_filtered) { return m_filteredFiles.size(); @@ -857,19 +972,19 @@ std::size_t ImagesTab::Files::size() const } } -void ImagesTab::Files::switchToAll() +void Files::switchToAll() { m_filtered = false; m_filteredFiles.clear(); } -void ImagesTab::Files::switchToFiltered() +void Files::switchToFiltered() { m_filtered = true; m_filteredFiles.clear(); } -const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const +const File* Files::get(std::size_t i) const { if (m_filtered) { if (i < m_filteredFiles.size()) { @@ -884,12 +999,12 @@ const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const return nullptr; } -ImagesTab::File* ImagesTab::Files::get(std::size_t i) +File* Files::get(std::size_t i) { return const_cast(std::as_const(*this).get(i)); } -std::size_t ImagesTab::Files::indexOf(const File* f) const +std::size_t Files::indexOf(const File* f) const { if (m_filtered) { for (std::size_t i=0; i& ImagesTab::Files::allFiles() +std::vector& Files::allFiles() { return m_allFiles; } -bool ImagesTab::Files::isFiltered() const +bool Files::isFiltered() const { return m_filtered; } + + +PaintContext::PaintContext(QWidget* w, Geometry geo) + : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0) +{ +} + +} // namespace diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 5ce66d5d..0d34b46a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -7,10 +7,15 @@ class ImagesTab; +namespace ImagesTabHelpers +{ + +static constexpr std::size_t BadIndex = std::numeric_limits::max(); + // vertical scrollbar, this is only to handle wheel events to scroll by one // instead of the system's scroll setting // -class ImagesScrollbar : public QScrollBar +class Scrollbar : public QScrollBar { public: using QScrollBar::QScrollBar; @@ -29,7 +34,7 @@ private: // widget inside the scroller, calls ImagesTab::paintThumbnailArea() when // needed and also forwards mouse clicks and tooltip events // -class ImagesThumbnails : public QWidget +class ThumbnailsWidget : public QWidget { Q_OBJECT; @@ -101,15 +106,47 @@ private: }; +struct Theme +{ + QColor borderColor, backgroundColor, textColor; + QColor highlightBackgroundColor, highlightTextColor; + QFont font; +}; + + +struct Metrics +{ + // space outside the thumbnail border + int margins; + + // size of the border + int border; + + // space between the border and the image + int padding; + + // spacing between the thumbnail and the text + int textSpacing; + + // height of the text + int textHeight; + + // spacing between thumbnails + int spacing; + + Metrics(); +}; + + // handles all the geometry calculations by ImagesTab for painting or handling // mouse clicks // // a thumbnail looks like this: // -// +-----------------------+ <-- thumb rect +// +-----------------------+ <--- thumb rect // | margins | // | | -// | +-border--------+ <------ border rect +// | +-border--------+ <------- border rect // | | padding | | // | | | | // | | +-------+ <----------- image rect @@ -119,6 +156,10 @@ private: // | | +-------+ | | // | | | | // | +---------------+ | +// | text spacing | +// | +---------------+ <------- text rect +// | | text | | +// | +---------------+ | // | | // +-----------------------+ // @@ -130,15 +171,10 @@ private: // .... // // -class ImagesGeometry +class Geometry { public: - // returned by indexAt() if the point is outside any possible thumbnail - static constexpr std::size_t BadIndex = - std::numeric_limits::max(); - - ImagesGeometry( - const QSize& widgetSize, int margins, int border, int padding, int spacing); + Geometry(QSize widgetSize, Metrics metrics); // returns the number of images fully visible in the widget // @@ -156,9 +192,20 @@ public: // QRect imageRect(std::size_t i) const; + // rectangle of the text for the given thumbnail + // + QRect textRect(std::size_t i) const; + + // rectangle that responds to selection: includes the border and extends down + // to the text + // + QRect selectionRect(std::size_t i) const; + // returns the index of the image at the given point; this does not take into // account any scrolling, the image at the top of widget is always 0 // + // returns BadIndex if there's no thumbnail at this point + // std::size_t indexAt(const QPoint& p) const; // returns the size of the image that fits in imageRect() while keeping the @@ -166,25 +213,12 @@ public: // QSize scaledImageSize(const QSize& originalSize) const; - // dumps stuff to qDebug() - // - void dump() const; - private: // size of the widget containing all the thumbnails const QSize m_widgetSize; - // space outside the thumbnail border - const int m_margins; - - // size of the border - const int m_border; - - // space between the border and the image - const int m_padding; - - // spacing between thumbnails - const int m_spacing; + // metrics + const Metrics m_metrics; // rectangle of the first thumbnail on top const QRect m_topRect; @@ -196,11 +230,88 @@ private: }; +class File +{ +public: + File(QString path); + + void ensureOriginalLoaded(); + + const QString& path() const; + const QString& filename() const; + const QImage& original() const; + const QImage& thumbnail() const; + bool failed() const; + + void loadIfNeeded(const Geometry& geo); + +private: + QString m_path; + mutable QString m_filename; + QImage m_original, m_thumbnail; + bool m_failed; + + bool needsLoad(const Geometry& geo) const; + void load(const Geometry& geo); +}; + + +class Files +{ +public: + Files(); + + void clear(); + + void add(File f); + void addFiltered(File* f); + + bool empty() const; + std::size_t size() const; + + void switchToAll(); + void switchToFiltered(); + + const File* get(std::size_t i) const; + File* get(std::size_t i); + std::size_t indexOf(const File* f) const; + + const File* selectedFile() const; + File* selectedFile(); + std::size_t selectedIndex() const; + void select(std::size_t i); + + std::vector& allFiles(); + + bool isFiltered() const; + +private: + std::vector m_allFiles; + std::vector m_filteredFiles; + std::size_t m_selection; + bool m_filtered; +}; + + +struct PaintContext +{ + mutable QPainter painter; + Geometry geo; + File* file; + std::size_t thumbIndex; + std::size_t fileIndex; + + PaintContext(QWidget* w, Geometry geo); +}; + +} // namespace + + class ImagesTab : public ModInfoDialogTab { Q_OBJECT; - friend class ImagesScrollbar; - friend class ImagesThumbnails; + friend class ImagesTabHelpers::Scrollbar; + friend class ImagesTabHelpers::ThumbnailsWidget; public: ImagesTab( @@ -214,94 +325,21 @@ public: void restoreState(const Settings& s) override; private: - static constexpr std::size_t BadIndex = - std::numeric_limits::max(); - - struct File - { - QString path; - QImage original, thumbnail; - bool failed = false; - - File(QString path) - : path(std::move(path)) - { - } - - void ensureOriginalLoaded() - { - if (original.isNull()) { - if (!original.load(path)) { - qCritical() << "failed to load image from " << path; - failed = true; - } - } - } - }; - - struct Colors - { - QColor border, background, selection; - }; - - struct PaintContext - { - mutable QPainter painter; - ImagesGeometry geo; - File* file; - std::size_t thumbIndex; - std::size_t fileIndex; - - PaintContext(QWidget* w, ImagesGeometry geo) - : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0) - { - } - }; - - class Files - { - public: - Files(); - - void clear(); - - void add(File f); - void addFiltered(File* f); - - bool empty() const; - std::size_t size() const; - - void switchToAll(); - void switchToFiltered(); - - const File* get(std::size_t i) const; - File* get(std::size_t i); - std::size_t indexOf(const File* f) const; - - const File* selectedFile() const; - File* selectedFile(); - std::size_t selectedIndex() const; - void select(std::size_t i); - - std::vector& allFiles(); - - bool isFiltered() const; - - private: - std::vector m_allFiles; - std::vector m_filteredFiles; - std::size_t m_selection; - bool m_filtered; - }; - + using ScalableImage = ImagesTabHelpers::ScalableImage; + using Files = ImagesTabHelpers::Files; + using File = ImagesTabHelpers::File; + using Theme = ImagesTabHelpers::Theme; + using Metrics = ImagesTabHelpers::Metrics; + using PaintContext = ImagesTabHelpers::PaintContext; + using Geometry = ImagesTabHelpers::Geometry; ScalableImage* m_image; std::vector m_supportedFormats; Files m_files; - int m_margins, m_border, m_padding, m_spacing; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; - Colors m_colors; + Theme m_theme; + Metrics m_metrics; void getSupportedFormats(); void enableDDS(bool b); @@ -325,18 +363,17 @@ private: std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; - ImagesGeometry makeGeometry() const; + Geometry makeGeometry() const; void paintThumbnail(const PaintContext& cx); void paintThumbnailBackground(const PaintContext& cx); void paintThumbnailBorder(const PaintContext& cx); void paintThumbnailImage(const PaintContext& cx); + void paintThumbnailText(const PaintContext& cx); void checkFiltering(); void switchToAll(); void switchToFiltered(); - bool needsReload(const ImagesGeometry& geo, const File& file) const; - void reload(const ImagesGeometry& geo, File& file); void updateScrollbar(); }; -- cgit v1.3.1 From 162fa30c3eb10db25f6ecc7bea84d38a2ab106a1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:43:46 -0400 Subject: add image dimension to toolbar and tooltip don't scroll when selecting an image with the mouse --- src/modinfodialog.ui | 9 ++++++++- src/modinfodialogimages.cpp | 35 +++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 2 +- 3 files changed, 36 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 3da13261..01d70d73 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -174,7 +174,7 @@ - 3 + 6 0 @@ -281,6 +281,13 @@
    + + + + 0x0 + + +
    diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 8c323ac6..3d50876b 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -28,6 +28,12 @@ QRect centeredRect(const QRect& rect, const QSize& size) size.height()); } +QString dimensionString(const QSize& s) +{ + return QString::fromUtf8("%1 \xc3\x97 %2") + .arg(s.width()).arg(s.height()); +} + ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, @@ -152,9 +158,9 @@ void ImagesTab::switchToAll() // reselect old if (oldSelection) { - m_files.select(m_files.indexOf(oldSelection)); + select(m_files.indexOf(oldSelection)); } else { - m_files.select(BadIndex); + select(BadIndex); } } @@ -226,7 +232,7 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(std::size_t i) +void ImagesTab::select(std::size_t i, bool ev) { m_files.select(i); @@ -238,11 +244,17 @@ void ImagesTab::select(std::size_t i) ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); + ui->imagesSize->setText(dimensionString(f->original().size())); + m_image->setImage(f->original()); - ensureVisible(i); + + if (ev) { + ensureVisible(i); + } } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); + ui->imagesSize->clear(); m_image->clear(); } @@ -444,7 +456,12 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) const auto i = fileIndexAtPos(e->pos()); if (i != BadIndex) { - select(i); + // 'false' so the selection is not made visible + // + // the only way to click on a thumbnail is if it's already visible, so the + // only thing that can happen is a click on a partially visible thumbnail, + // which would scroll so it is fully visible, and that's just annoying + select(i, false); } } @@ -510,9 +527,11 @@ void ImagesTab::showTooltip(QHelpEvent* e) return; } - QToolTip::showText( - e->globalPos(), QDir::toNativeSeparators(f->path()), - ui->imagesThumbnails); + const auto s = QString("%1 (%2)") + .arg(QDir::toNativeSeparators(f->path())) + .arg(dimensionString(f->original().size())); + + QToolTip::showText(e->globalPos(), s, ui->imagesThumbnails); } void ImagesTab::onExplore() diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 0d34b46a..566113bf 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -356,7 +356,7 @@ private: void onShowDDS(); void onFilterChanged(); - void select(std::size_t i); + void select(std::size_t i, bool ensureVisible=true); void moveSelection(int by); void ensureVisible(std::size_t i); -- cgit v1.3.1 From c90a822444da5842a12786dc923b2a481850608f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:57:27 -0400 Subject: added path to editor toolbar moved url to top in nexus tab and made it readonly --- src/modinfodialog.ui | 67 +++++++++++++++++++++++++++++++--------------- src/modinfodialognexus.cpp | 11 -------- src/modinfodialognexus.h | 1 - src/texteditor.cpp | 18 +++++++++---- src/texteditor.h | 1 + 5 files changed, 60 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 01d70d73..4de65e95 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -887,7 +887,7 @@ text-align: left; - + @@ -996,19 +996,58 @@ p, li { white-space: pre-wrap; } - - - 150 - 16777215 - - 32 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + URL + + + + + + + true + + + + + + @@ -1054,20 +1093,6 @@ p, li { white-space: pre-wrap; }
    - - - - - - URL - - - - - - - - diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index adf79060..e1fbe352 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -24,7 +24,6 @@ NexusTab::NexusTab( connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); - connect(ui->url, &QLineEdit::editingFinished, [&]{ onUrlChanged(); }); connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); connect(ui->refresh, &QToolButton::clicked, [&]{ onRefreshBrowser(); }); @@ -242,16 +241,6 @@ void NexusTab::onVersionChanged() updateVersionColor(); } -void NexusTab::onUrlChanged() -{ - if (m_loading) { - return; - } - - mod()->setURL(ui->url->text()); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); -} - void NexusTab::onOpenLink() { const int modID = mod()->getNexusID(); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 92330704..8528f0af 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -64,7 +64,6 @@ private: void onVersionChanged(); void onRefreshBrowser(); void onEndorse(); - void onUrlChanged(); }; #endif // MODINFODIALOGNEXUS_H diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 9bbe3ddd..130cd76f 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -454,8 +454,9 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } -TextEditorToolbar::TextEditorToolbar(TextEditor& editor) - : m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr) +TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : + m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr), + m_path(nullptr) { m_save = new QAction( QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); @@ -467,10 +468,13 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) m_wordWrap = new QAction( QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"), &editor); + m_wordWrap->setCheckable(true); + m_explore = new QAction( QObject::tr("&Open in Explorer"), &editor); - m_wordWrap->setCheckable(true); + m_path = new QLineEdit; + m_path->setReadOnly(true); QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); @@ -492,6 +496,8 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) b->setDefaultAction(m_explore); layout->addWidget(b); + layout->addWidget(m_path); + QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); QObject::connect(&m_editor, &TextEditor::loaded, [&](QString f){ onLoaded(f); }); @@ -507,12 +513,14 @@ void TextEditorToolbar::onWordWrap(bool b) m_wordWrap->setChecked(b); } -void TextEditorToolbar::onLoaded(const QString& s) +void TextEditorToolbar::onLoaded(const QString& path) { - const auto hasDoc = !s.isEmpty(); + const auto hasDoc = !path.isEmpty(); m_explore->setEnabled(hasDoc); m_wordWrap->setEnabled(hasDoc); + m_path->setEnabled(hasDoc); + m_path->setText(path); } void HTMLEditor::focusOutEvent(QFocusEvent* e) diff --git a/src/texteditor.h b/src/texteditor.h index 798222a3..dc53f1c4 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -17,6 +17,7 @@ private: QAction* m_save; QAction* m_wordWrap; QAction* m_explore; + QLineEdit* m_path; void onTextModified(bool b); void onWordWrap(bool b); -- cgit v1.3.1 From 4b958d9efed3834ceaa57f6c50a1e8288eff01b0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 11:12:22 -0400 Subject: added visibility flags reduced paging size by one, feels much more natural --- src/modinfodialogimages.cpp | 33 ++++++++++++++++++++++----------- src/modinfodialogimages.h | 11 +++++++++-- 2 files changed, 31 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 3d50876b..02fc7c20 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -116,6 +116,14 @@ void ImagesTab::update() { checkFiltering(); updateScrollbar(); + + // visibility needs to be rechecked here because the scrollbar configuration + // may have changed in updateScrollbar(), in which case any ensureVisible() + // calls in checkFiltering() might have been incorrect + if (m_files.selectedIndex() != BadIndex) { + ensureVisible(m_files.selectedIndex(), Visibility::Partial); + } + ui->imagesThumbnails->update(); setHasData(m_files.size() > 0); @@ -232,7 +240,7 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(std::size_t i, bool ev) +void ImagesTab::select(std::size_t i, Visibility v) { m_files.select(i); @@ -247,10 +255,7 @@ void ImagesTab::select(std::size_t i, bool ev) ui->imagesSize->setText(dimensionString(f->original().size())); m_image->setImage(f->original()); - - if (ev) { - ensureVisible(i); - } + ensureVisible(i, v); } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); @@ -293,17 +298,25 @@ void ImagesTab::moveSelection(int by) select(i); } -void ImagesTab::ensureVisible(std::size_t i) +void ImagesTab::ensureVisible(std::size_t i, Visibility v) { + if (v == Visibility::Ignore) { + return; + } + const auto geo = makeGeometry(); const auto fullyVisible = geo.fullyVisibleCount(); + const auto partiallyVisible = fullyVisible + 1; + const auto first = ui->imagesScrollerVBar->value(); + const auto last = (v == Visibility::Full ? + first + fullyVisible : first + partiallyVisible); if (i < first) { // go up ui->imagesScrollerVBar->setValue(static_cast(i)); - } else if (i >= first + fullyVisible) { + } else if (i >= last) { // go down if (i >= fullyVisible) { @@ -456,12 +469,10 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) const auto i = fileIndexAtPos(e->pos()); if (i != BadIndex) { - // 'false' so the selection is not made visible - // // the only way to click on a thumbnail is if it's already visible, so the // only thing that can happen is a click on a partially visible thumbnail, // which would scroll so it is fully visible, and that's just annoying - select(i, false); + select(i, Visibility::Ignore); } } @@ -574,7 +585,7 @@ void ImagesTab::updateScrollbar() const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); ui->imagesScrollerVBar->setSingleStep(1); - ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible)); + ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible - 1)); ui->imagesScrollerVBar->setEnabled(true); } } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 566113bf..d22a6ab2 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -325,6 +325,13 @@ public: void restoreState(const Settings& s) override; private: + enum class Visibility + { + Ignore = 0, + Full, + Partial + }; + using ScalableImage = ImagesTabHelpers::ScalableImage; using Files = ImagesTabHelpers::Files; using File = ImagesTabHelpers::File; @@ -356,9 +363,9 @@ private: void onShowDDS(); void onFilterChanged(); - void select(std::size_t i, bool ensureVisible=true); + void select(std::size_t i, Visibility v=Visibility::Full); void moveSelection(int by); - void ensureVisible(std::size_t i); + void ensureVisible(std::size_t i, Visibility v); std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; -- cgit v1.3.1 From a818954ee5d02b8723f5b48f458a0c76f9ba1935 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 11:30:46 -0400 Subject: added explore menu item to conflict lists and filetree changed filetree context menu to always show all items added mnemonics to conflicts context menu --- src/modinfodialog.cpp | 6 ++ src/modinfodialog.h | 1 + src/modinfodialogconflicts.cpp | 43 ++++++++-- src/modinfodialogconflicts.h | 3 + src/modinfodialogfiletree.cpp | 180 ++++++++++++++++++++++++----------------- src/modinfodialogfiletree.h | 2 + 6 files changed, 154 insertions(+), 81 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 7696023f..2772994c 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -66,6 +66,12 @@ bool canOpenFile(bool isArchive, const QString&) return !isArchive; } +bool canExploreFile(bool isArchive, const QString&) +{ + // can explore anything as long as it's not in an archive + return !isArchive; +} + bool canHideFile(bool isArchive, const QString& filename) { if (isArchive) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6dc66003..273af8c7 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -36,6 +36,7 @@ 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); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index e631db30..a3383a50 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -7,6 +7,7 @@ using namespace MOShared; using namespace MOBase; +namespace shell = MOBase::shell; // if there are more than 50 selected items in the conflict tree, don't bother // checking whether menu items apply to them, just show all of them @@ -91,6 +92,11 @@ public: return canPreviewFile(pluginContainer, isArchive(), fileName()); } + bool canExplore() const + { + return canExploreFile(isArchive(), fileName()); + } + private: QString m_before; QString m_relativeName; @@ -533,6 +539,16 @@ void ConflictsTab::previewItems(QTreeView* tree) }); } +void ConflictsTab::exploreItems(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + shell::ExploreFile(item->fileName()); + return true; + }); +} + void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) { auto actions = createMenuActions(tree); @@ -557,6 +573,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.preview); } + // explore + if (actions.explore) { + connect(actions.explore, &QAction::triggered, [&]{ + exploreItems(tree); + }); + + menu.addAction(actions.explore); + } + // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ @@ -603,6 +628,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) bool enableUnhide = true; bool enableOpen = true; bool enablePreview = true; + bool enableExplore = true; bool enableGoto = true; const auto n = smallSelectionSize(tree); @@ -626,6 +652,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableUnhide = item->canUnhide(); enableOpen = item->canOpen(); enablePreview = item->canPreview(plugin()); + enableExplore = item->canExplore(); enableGoto = item->hasAlts(); } else { @@ -634,6 +661,9 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableOpen = false; enablePreview = false; + // can't explore multiple files + enableExplore = false; + // don't bother with this on multiple selection, at least for now enableGoto = false; @@ -664,21 +694,24 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) Actions actions; - actions.hide = new QAction(tr("Hide"), parentWidget()); + actions.hide = new QAction(tr("&Hide"), parentWidget()); actions.hide->setEnabled(enableHide); // note that it is possible for hidden files to appear if they override other // hidden files from another mod - actions.unhide = new QAction(tr("Unhide"), parentWidget()); + actions.unhide = new QAction(tr("&Unhide"), parentWidget()); actions.unhide->setEnabled(enableUnhide); - actions.open = new QAction(tr("Open/Execute"), parentWidget()); + actions.open = new QAction(tr("&Open/Execute"), parentWidget()); actions.open->setEnabled(enableOpen); - actions.preview = new QAction(tr("Preview"), parentWidget()); + actions.preview = new QAction(tr("&Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); - actions.gotoMenu = new QMenu(tr("Go to..."), parentWidget()); + actions.explore = new QAction(tr("Open in &Explorer"), parentWidget()); + actions.explore->setEnabled(enableExplore); + + actions.gotoMenu = new QMenu(tr("&Go to..."), parentWidget()); actions.gotoMenu->setEnabled(enableGoto); if (enableGoto && n == 1) { diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index fc7dd32a..3fa12231 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -115,6 +115,7 @@ public: void openItems(QTreeView* tree); void previewItems(QTreeView* tree); + void exploreItems(QTreeView* tree); void changeItemsVisibility(QTreeView* tree, bool visible); @@ -127,7 +128,9 @@ private: QAction* unhide = nullptr; QAction* open = nullptr; QAction* preview = nullptr; + QAction* explore = nullptr; QMenu* gotoMenu = nullptr; + std::vector gotoActions; }; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 24faec5e..6690dd2f 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -24,8 +24,9 @@ FileTreeTab::FileTreeTab( ui->filetree->setColumnWidth(0, 300); m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); - m_actions.open = new QAction(tr("&Open"), ui->filetree); + m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); m_actions.preview = new QAction(tr("&Preview"), ui->filetree); + m_actions.explore = new QAction(tr("Open in &Explorer"), ui->filetree); m_actions.rename = new QAction(tr("&Rename"), ui->filetree); m_actions.del = new QAction(tr("&Delete"), ui->filetree); m_actions.hide = new QAction(tr("&Hide"), ui->filetree); @@ -34,6 +35,7 @@ FileTreeTab::FileTreeTab( connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); connect(m_actions.preview, &QAction::triggered, [&]{ onPreview(); }); + connect(m_actions.explore, &QAction::triggered, [&]{ onExplore(); }); connect(m_actions.rename, &QAction::triggered, [&]{ onRename(); }); connect(m_actions.del, &QAction::triggered, [&]{ onDelete(); }); connect(m_actions.hide, &QAction::triggered, [&]{ onHide(); }); @@ -140,6 +142,17 @@ void FileTreeTab::onPreview() core().previewFile(parentWidget(), mod()->name(), m_fs->filePath(selection)); } +void FileTreeTab::onExplore() +{ + auto selection = singleSelection(); + + if (selection.isValid()) { + shell::ExploreFile(m_fs->filePath(selection)); + } else { + shell::ExploreFile(mod()->absolutePath()); + } +} + void FileTreeTab::onRename() { auto selection = singleSelection(); @@ -316,103 +329,118 @@ void FileTreeTab::onContextMenu(const QPoint &pos) QMenu menu(ui->filetree); - menu.addAction(m_actions.newFolder); - - if (selection.size() > 0) { - bool enableOpen = true; - bool enablePreview = true; - bool enableRename = true; - bool enableDelete = true; - bool enableHide = true; - bool enableUnhide = true; + bool enableNewFolder = true; + bool enableOpen = true; + bool enablePreview = true; + bool enableExplore = true; + bool enableRename = true; + bool enableDelete = true; + bool enableHide = true; + bool enableUnhide = true; + + if (selection.size() == 0) { + // no selection, only new folder and explore + enableOpen = false; + enablePreview = false; + enableRename = false; + enableDelete = false; + enableHide = false; + enableUnhide = false; + } else if (selection.size() == 1) { + // single selection + + // only enable open action if a file is selected + bool hasFiles = false; + + for (auto index : selection) { + if (m_fs->fileInfo(index).isFile()) { + hasFiles = true; + break; + } + } - if (selection.size() == 1) { - // single selection + if (!hasFiles) { + enableOpen = false; + enablePreview = false; + } - // only enable open action if a file is selected - bool hasFiles = false; + const QString fileName = m_fs->fileName(selection[0]); - for (auto index : selection) { - if (m_fs->fileInfo(index).isFile()) { - hasFiles = true; - break; - } - } + if (!canPreviewFile(plugin(), false, fileName)) { + enablePreview = false; + } - if (!hasFiles) { - enableOpen = false; - enablePreview = false; - } + if (!canExploreFile(false, fileName)) { + enableExplore = false; + } - const QString fileName = m_fs->fileName(selection[0]); + if (!canHideFile(false, fileName)) { + enableHide = false; + } - if (!canPreviewFile(plugin(), false, fileName)) { - enablePreview = false; - } + if (!canUnhideFile(false, fileName)) { + enableUnhide = false; + } + } else { + // this is a multiple selection, don't show open action so users don't open + // a thousand files + enableOpen = false; + enablePreview = false; - if (!canHideFile(false, fileName)) { - enableHide = false; - } + // can't explore multiple files + enableExplore = false; - if (!canUnhideFile(false, fileName)) { - enableUnhide = false; - } - } else { - // this is a multiple selection, don't show open action so users don't open - // a thousand files - enableOpen = false; - enablePreview = false; - enableRename = false; + // can't rename multiple files + enableRename = false; - if (selection.size() < max_scan_for_context_menu) { - // if the number of selected items is low, checking them to accurately - // show the menu items is worth it - enableHide = false; - enableUnhide = false; + if (selection.size() < max_scan_for_context_menu) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; - for (const auto& index : selection) { - const QString fileName = m_fs->fileName(index); + for (const auto& index : selection) { + const QString fileName = m_fs->fileName(index); - if (canHideFile(false, fileName)) { - enableHide = true; - } + if (canHideFile(false, fileName)) { + enableHide = true; + } - if (canUnhideFile(false, fileName)) { - enableUnhide = true; - } + if (canUnhideFile(false, fileName)) { + enableUnhide = true; + } - if (enableHide && enableUnhide) { - // found both, no need to check more - break; - } + if (enableHide && enableUnhide) { + // found both, no need to check more + break; } } } + } - if (enableOpen) { - menu.addAction(m_actions.open); - } + menu.addAction(m_actions.newFolder); + m_actions.newFolder->setEnabled(enableNewFolder); - if (enablePreview) { - menu.addAction(m_actions.preview); - } + menu.addAction(m_actions.open); + m_actions.open->setEnabled(enableOpen); - if (enableRename) { - menu.addAction(m_actions.rename); - } + menu.addAction(m_actions.preview); + m_actions.preview->setEnabled(enablePreview); - if (enableDelete) { - menu.addAction(m_actions.del); - } + menu.addAction(m_actions.explore); + m_actions.explore->setEnabled(enableExplore); - if (enableHide) { - menu.addAction(m_actions.hide); - } + menu.addAction(m_actions.rename); + m_actions.rename->setEnabled(enableRename); - if (enableUnhide) { - menu.addAction(m_actions.unhide); - } - } + menu.addAction(m_actions.del); + m_actions.del->setEnabled(enableDelete); + + menu.addAction(m_actions.hide); + m_actions.hide->setEnabled(enableHide); + + menu.addAction(m_actions.unhide); + m_actions.unhide->setEnabled(enableUnhide); menu.exec(ui->filetree->viewport()->mapToGlobal(pos)); } diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 2145f298..9f5206b9 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -20,6 +20,7 @@ private: QAction *newFolder = nullptr; QAction *open = nullptr; QAction *preview = nullptr; + QAction *explore = nullptr; QAction *rename = nullptr; QAction *del = nullptr; QAction *hide = nullptr; @@ -32,6 +33,7 @@ private: void onCreateDirectory(); void onOpen(); void onPreview(); + void onExplore(); void onRename(); void onDelete(); void onHide(); -- cgit v1.3.1 From e1990df0a2f25b4ac9227655fd5d597cf8ab6cf4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 11:36:40 -0400 Subject: fixed tab re-ordering not being used if the old setting was present --- src/modinfodialog.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2772994c..ca20b318 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -427,6 +427,11 @@ void ModInfoDialog::saveState(Settings& s) const s.directInterface().setValue("mod_info_tab_order", tabState); } + // remove old settings + if (s.directInterface().contains("mod_info_tabs")) { + s.directInterface().remove("mod_info_tabs"); + } + for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); } -- cgit v1.3.1 From 6547197e5f685439371446b499427848ed687871 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 12:01:38 -0400 Subject: remember state of various widgets remove obsolete settings --- src/modinfodialog.cpp | 12 ++++++++---- src/modinfodialogesps.cpp | 11 +++++++++++ src/modinfodialogesps.h | 2 ++ src/modinfodialogimages.cpp | 4 ++++ src/modinfodialogtab.h | 23 +++++++++++++++++++++++ src/modinfodialogtextfiles.cpp | 19 +++++++++++++++---- src/modinfodialogtextfiles.h | 3 +++ 7 files changed, 66 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ca20b318..bcb979d2 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -427,10 +427,14 @@ void ModInfoDialog::saveState(Settings& s) const s.directInterface().setValue("mod_info_tab_order", tabState); } - // remove old settings - if (s.directInterface().contains("mod_info_tabs")) { - s.directInterface().remove("mod_info_tabs"); - } + // remove 2.2.0 settings + s.directInterface().remove("mod_info_tabs"); + s.directInterface().remove("mod_info_conflict_expanders"); + s.directInterface().remove("mod_info_conflicts"); + s.directInterface().remove("mod_info_advanced_conflicts"); + s.directInterface().remove("mod_info_conflicts_overwrite"); + s.directInterface().remove("mod_info_conflicts_noconflict"); + s.directInterface().remove("mod_info_conflicts_overwritten"); for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 7961096d..d00eccf3 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -1,6 +1,7 @@ #include "modinfodialogesps.h" #include "ui_modinfodialog.h" #include "modinfodialog.h" +#include "settings.h" #include using MOBase::reportError; @@ -275,6 +276,16 @@ void ESPsTab::update() setHasData(m_inactiveModel->rowCount() > 0 || m_activeModel->rowCount() > 0); } +void ESPsTab::saveState(Settings& s) +{ + saveWidgetState(s.directInterface(), ui->ESPsSplitter); +} + +void ESPsTab::restoreState(const Settings& s) +{ + restoreWidgetState(s.directInterface(), ui->ESPsSplitter); +} + void ESPsTab::onActivate() { const auto index = ui->inactiveESPList->currentIndex(); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index c972074a..217863c6 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -18,6 +18,8 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update(); + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; private: ESPListModel* m_inactiveModel; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 02fc7c20..307fa8e8 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -133,12 +133,16 @@ void ImagesTab::saveState(Settings& s) { s.directInterface().setValue( "mod_info_dialog_images_show_dds", m_ddsEnabled); + + saveWidgetState(s.directInterface(), ui->tabImagesSplitter); } void ImagesTab::restoreState(const Settings& s) { ui->imagesShowDDS->setChecked(s.directInterface() .value("mod_info_dialog_images_show_dds", false).toBool()); + + restoreWidgetState(s.directInterface(), ui->tabImagesSplitter); } void ImagesTab::checkFiltering() diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 058035ab..ce29c868 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -67,6 +67,23 @@ protected: void emitModOpen(QString name); void setHasData(bool b); + // this needs to be a template because saveState() and restoreState() are + // not in QWidget, but they're in various widgets + // + template + void saveWidgetState(QSettings& s, Widget* w) + { + s.setValue(settingName(w), w->saveState()); + } + + template + void restoreWidgetState(const QSettings& s, Widget* w) + { + if (s.contains(settingName(w))) { + w->restoreState(s.value(settingName(w)).toByteArray()); + } + } + private: OrganizerCore& m_core; PluginContainer& m_plugin; @@ -76,6 +93,12 @@ private: int m_tabID; bool m_hasData; bool m_firstActivation; + + + QString settingName(QWidget* w) + { + return "geometry/modinfodialog_" + w->objectName(); + } }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 5b035c53..52891bf5 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -1,6 +1,7 @@ #include "modinfodialogtextfiles.h" #include "ui_modinfodialog.h" #include "modinfodialog.h" +#include "settings.h" #include class FileListModel : public QAbstractItemModel @@ -102,14 +103,14 @@ GenericFilesTab::GenericFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id, QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_list(list), m_editor(e), m_model(new FileListModel) + m_list(list), m_editor(e), m_splitter(sp), m_model(new FileListModel) { m_list->setModel(m_model); m_editor->setupToolbar(); - sp->setSizes({200, 1}); - sp->setStretchFactor(0, 0); - sp->setStretchFactor(1, 1); + m_splitter->setSizes({200, 1}); + m_splitter->setStretchFactor(0, 0); + m_splitter->setStretchFactor(1, 1); m_filter.setEdit(filter); m_filter.setList(m_list); @@ -171,6 +172,16 @@ void GenericFilesTab::update() setHasData(m_model->rowCount() > 0); } +void GenericFilesTab::saveState(Settings& s) +{ + saveWidgetState(s.directInterface(), m_splitter); +} + +void GenericFilesTab::restoreState(const Settings& s) +{ + restoreWidgetState(s.directInterface(), m_splitter); +} + void GenericFilesTab::onSelection( const QModelIndex& current, const QModelIndex& previous) { diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 037c6bb3..ffe49904 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -19,10 +19,13 @@ public: bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update() override; + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; protected: QListView* m_list; TextEditor* m_editor; + QSplitter* m_splitter; FileListModel* m_model; FilterWidget m_filter; -- cgit v1.3.1 From 9ec0feba4395d492d814c03cf8be67a196bab36e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 12:57:21 -0400 Subject: fixed crash when opening separators --- src/modinfodialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index bcb979d2..676f3480 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -345,7 +345,9 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) feedFiles(becauseOriginChanged); for (auto& tabInfo : m_tabs) { - tabInfo.tab->update(); + if (tabInfo.isVisible()) { + tabInfo.tab->update(); + } } setTabsColors(); -- cgit v1.3.1 From 093eb5650bdd010d9c779e64d3ca63682ba6ca19 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 15:17:05 -0400 Subject: fixed problems with re-ordering tabs, realPos was never updated properly and it wasn't actually used everywhere --- src/modinfodialog.cpp | 40 ++++++++++++++++++++++++++++++++++++---- src/modinfodialog.h | 2 ++ 2 files changed, 38 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 676f3480..a5e8d8cd 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -170,6 +170,7 @@ ModInfoDialog::ModInfoDialog( } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); + connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); } ModInfoDialog::~ModInfoDialog() = default; @@ -249,12 +250,14 @@ ModInfoDialog::TabInfo* ModInfoDialog::currentTab() return nullptr; } - const auto i = static_cast(index); - if (i >= m_tabs.size()) { - return nullptr; + for (auto& tabInfo : m_tabs) { + if (tabInfo.realPos == index) { + return &tabInfo; + } } - return &m_tabs[i]; + qCritical() << "tab index " << index << " not found"; + return nullptr; } void ModInfoDialog::update(bool firstTime) @@ -272,6 +275,7 @@ void ModInfoDialog::update(bool firstTime) } if (ui->tabWidget->currentIndex() == oldTab) { + // manually fire activated() because the tab index hasn't been changed if (auto* tabInfo=currentTab()) { tabInfo->tab->activated(); } @@ -600,6 +604,34 @@ void ModInfoDialog::onTabChanged() } } +void ModInfoDialog::onTabMoved() +{ + // reset + for (auto& tabInfo : m_tabs) { + tabInfo.realPos = -1; + } + + // for each tab in the widget + for (int i=0; itabWidget->count(); ++i) { + const auto* w = ui->tabWidget->widget(i); + + bool found = false; + + // find the corresponding tab info + for (auto& tabInfo : m_tabs) { + if (tabInfo.widget == w) { + tabInfo.realPos = i; + found = true; + break; + } + } + + if (!found) { + qCritical() << "unknown tab at index " << i; + } + } +} + void ModInfoDialog::on_nextButton_clicked() { auto mod = m_mainWindow->nextModInList(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 273af8c7..030b3f93 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -158,8 +158,10 @@ private: void switchToTab(ETabs id); void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; + void onOriginModified(std::size_t tabIndex, int originID); void onTabChanged(); + void onTabMoved(); template std::unique_ptr createTab(int index) -- cgit v1.3.1 From b4c26c516412b2f7270be02494b85be9cad0cadb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 17:28:15 -0400 Subject: fix inactive esps being marked as active because path separators changed from / to \ since the change to std::filesystem --- src/modinfodialogesps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index d00eccf3..8dceaa31 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -12,7 +12,7 @@ public: ESPItem(QString rootPath, QString relativePath) : m_rootPath(std::move(rootPath)), m_active(false) { - if (relativePath.contains('/')) { + if (relativePath.contains('/') || relativePath.contains('\\')) { m_inactivePath = relativePath; } else { m_activePath = relativePath; -- cgit v1.3.1 From 41292e02bdebea8b09c2c11ee87221672e970cdf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 28 Jun 2019 14:21:10 -0400 Subject: changed how the mod url works: - it is now independent from the nexus mod url - it can be set in addition to having a valid mod id - both urls can be displayed in the context menu re-arranged some of the widgets on the nexus tab added a track button added a custom url checkbox and open in browser button added a max-width to the browser --- src/mainwindow.cpp | 44 +++--- src/modinfo.cpp | 19 +++ src/modinfo.h | 29 +++- src/modinfodialog.ui | 341 ++++++++++++++++++++++++++------------------- src/modinfodialognexus.cpp | 147 +++++++++++++------ src/modinfodialognexus.h | 11 +- src/modinforegular.cpp | 85 ++++++++++- src/modinforegular.h | 16 +-- 8 files changed, 462 insertions(+), 230 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d75e8d9d..d48fae4f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3429,18 +3429,16 @@ void MainWindow::visitOnNexus_clicked() int row_idx; ModInfo::Ptr info; QString gameName; - QString webUrl; + for (QModelIndex idx : selection->selectedRows()) { row_idx = idx.data(Qt::UserRole + 1).toInt(); info = ModInfo::getByIndex(row_idx); int modID = info->getNexusID(); - webUrl = info->getURL(); gameName = info->getGameName(); if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); - } - else if (webUrl != "") { - linkClicked(webUrl); + } else { + qCritical() << "mod '" << info->name() << "' has no nexus id"; } } } @@ -3450,14 +3448,13 @@ void MainWindow::visitOnNexus_clicked() if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); } else { - MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); + MessageDialog::showMessage(tr("Nexus ID for this mod is unknown"), this); } } } void MainWindow::visitWebPage_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { int count = selection->selectedRows().count(); @@ -3471,28 +3468,22 @@ void MainWindow::visitWebPage_clicked() int row_idx; ModInfo::Ptr info; QString gameName; - QString webUrl; for (QModelIndex idx : selection->selectedRows()) { row_idx = idx.data(Qt::UserRole + 1).toInt(); info = ModInfo::getByIndex(row_idx); - int modID = info->getNexusID(); - webUrl = info->getURL(); - gameName = info->getGameName(); - if (modID > 0) { - linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); - } - else if (webUrl != "") { - linkClicked(webUrl); + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + linkClicked(url.toString()); } } } else { ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - if (info->getURL() != "") { - linkClicked(info->getURL()); - } - else { - MessageDialog::showMessage(tr("Web page for this mod is unknown"), this); + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + linkClicked(url.toString()); } } } @@ -4711,7 +4702,7 @@ void MainWindow::exportModListCSV() builder.writeHeader(); auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); - for (auto& iter : indexesByPriority) { + for (auto& iter : indexesByPriority) { ModInfo::Ptr info = ModInfo::getByIndex(iter.second); bool enabled = m_OrganizerCore.currentProfile()->modEnabled(iter.second); if ((selectedRowID == 1) && !enabled) { @@ -4991,8 +4982,13 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (info->getNexusID() > 0) { menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); - } else if ((info->getURL() != "")) { - menu.addAction(tr("Visit web page"), this, SLOT(visitWebPage_clicked())); + } + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + menu.addAction( + tr("Visit on %1").arg(url.host()), + this, SLOT(visitWebPage_clicked())); } menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked())); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index bc2979ef..585d4963 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -520,3 +520,22 @@ void ModInfo::testValid() dirIter.next(); } } + +QUrl ModInfo::parseCustomURL() const +{ + if (!hasCustomURL() || getCustomURL().isEmpty()) { + return {}; + } + + const auto url = QUrl::fromUserInput(getCustomURL()); + + if (!url.isValid()) { + qCritical() + << "mod '" << name() << "' has an invalid custom url " + << "'" << getCustomURL() << "'"; + + return {}; + } + + return url; +} diff --git a/src/modinfo.h b/src/modinfo.h index f1d816fe..e395f45b 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -733,14 +733,31 @@ public: virtual void doConflictCheck() const {} /** - * @brief set the URL for a mod - */ - virtual void setURL(QString const &) {} + * @brief sets whether this mod uses a custom url + **/ + virtual void setHasCustomURL(bool) {} /** - * @returns the URL for a mod - */ - virtual QString getURL() const { return ""; } + * @brief returns whether this mod uses a custom url + **/ + virtual bool hasCustomURL() const { return false; } + + /** + * @brief sets the custom url + **/ + virtual void setCustomURL(QString const &) {} + + /** + * @brief returns the custom url + **/ + virtual QString getCustomURL() const { return ""; } + + /** + * If hasCustomURL() is true and getCustomURL() is not empty, tries to parse + * the url using QUrl::fromUserInput() and returns it. Otherwise, returns an + * empty QUrl. + **/ + QUrl parseCustomURL() const; signals: diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 4de65e95..78f19a36 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -491,7 +491,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 1 + 0 @@ -887,138 +887,8 @@ text-align: left; - - - - - - 0 - 0 - - - - Refresh - - - Refresh all information from Nexus. - - - Refresh - - - - :/MO/gui/refresh:/MO/gui/refresh - - - Qt::ToolButtonTextBesideIcon - - - - - - - Open in Browser - - - - :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png - - - Qt::ToolButtonTextBesideIcon - - - - - - - Endorse - - - - :/MO/gui/icon_favorite:/MO/gui/icon_favorite - - - Qt::ToolButtonTextBesideIcon - - - - - - - Mod ID - - - - - - - Mod ID for this mod on Nexus. - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> - - - - - - - Source Game - - - - - - - Source game for this mod. - - - <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - - - Version - - - - - - - 32 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + + 0 @@ -1032,17 +902,169 @@ p, li { white-space: pre-wrap; } 0 - - - URL - - + + + + + Mod ID + + + + + + + Mod ID for this mod on Nexus. + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> + + + + + + + Source Game + + + + + + + Source game for this mod. + + + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + + + Version + + + + + + + 32 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - true - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Refresh + + + Refresh all information from Nexus. + + + Refresh + + + + :/MO/gui/refresh:/MO/gui/refresh + + + + + + + Open in Browser + + + + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png + + + + + + + Endorse + + + + :/MO/gui/icon_favorite:/MO/gui/icon_favorite + + + + + + + Track + + + + :/MO/gui/tracked:/MO/gui/tracked + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -1093,6 +1115,41 @@ p, li { white-space: pre-wrap; } + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Use Custom URL + + + + + + + + + + Open in Browser + + + + + + diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index e1fbe352..8c8ce55a 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -7,6 +7,8 @@ #include #include +namespace shell = MOBase::shell; + bool isValidModID(int id) { return (id > 0); @@ -22,15 +24,20 @@ NexusTab::NexusTab( ui->endorse->setVisible(core().settings().endorsementIntegration()); connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); - connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); - connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); - connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); - connect(ui->refresh, &QToolButton::clicked, [&]{ onRefreshBrowser(); }); - connect( ui->sourceGame, static_cast(&QComboBox::currentIndexChanged), [&]{ onSourceGameChanged(); }); + connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); + + connect(ui->refresh, &QPushButton::clicked, [&]{ onRefreshBrowser(); }); + connect(ui->visitNexus, &QPushButton::clicked, [&]{ onVisitNexus(); }); + connect(ui->endorse, &QPushButton::clicked, [&]{ onEndorse(); }); + connect(ui->track, &QPushButton::clicked, [&]{ onTrack(); }); + + connect(ui->hasCustomURL, &QCheckBox::toggled, [&]{ onCustomURLToggled(); }); + connect(ui->customURL, &QLineEdit::editingFinished, [&]{ onCustomURLChanged(); }); + connect(ui->visitCustomURL, &QPushButton::clicked, [&]{ onVisitCustomURL(); }); } NexusTab::~NexusTab() @@ -52,7 +59,8 @@ void NexusTab::clear() ui->sourceGame->clear(); ui->version->clear(); ui->browser->setPage(new NexusTabWebpage(ui->browser)); - ui->url->clear(); + ui->hasCustomURL->setChecked(false); + ui->customURL->clear(); setHasData(false); } @@ -89,7 +97,7 @@ void NexusTab::update() connect( page, &NexusTabWebpage::linkClicked, - [&](const QUrl& url){ MOBase::shell::OpenLink(url); }); + [&](const QUrl& url){ shell::OpenLink(url); }); ui->endorse->setEnabled( (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || @@ -138,15 +146,52 @@ void NexusTab::updateWebpage() const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); - ui->openInBrowser->setToolTip(nexusLink); - mod()->setURL(nexusLink); + ui->visitNexus->setToolTip(nexusLink); refreshData(modID); } else { onModChanged(); } ui->version->setText(mod()->getVersion().displayString()); - ui->url->setText(mod()->getURL()); + ui->hasCustomURL->setChecked(mod()->hasCustomURL()); + ui->customURL->setText(mod()->getCustomURL()); + ui->customURL->setEnabled(mod()->hasCustomURL()); + ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); + ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + + updateTracking(); +} + +void NexusTab::updateTracking() +{ + if (mod()->trackedState() == ModInfo::TRACKED_TRUE) { + ui->track->setChecked(true); + ui->track->setText(tr("Tracked")); + } else { + ui->track->setChecked(false); + ui->track->setText(tr("Untracked")); + } +} + +void NexusTab::refreshData(int modID) +{ + if (tryRefreshData(modID)) { + m_requestStarted = true; + } else { + onModChanged(); + } +} + +bool NexusTab::tryRefreshData(int modID) +{ + if (isValidModID(modID) && !m_requestStarted) { + if (mod()->updateNXMInfo()) { + ui->browser->setHtml(""); + return true; + } + } + + return false; } void NexusTab::onModChanged() @@ -165,6 +210,9 @@ void NexusTab::onModChanged() font-size: 14px; background: #404040; color: #f1f1f1; + max-width: 1060px; + margin-left: auto; + margin-right: auto; } a @@ -178,12 +226,11 @@ void NexusTab::onModChanged() )"; if (nexusDescription.isEmpty()) { - descriptionAsHTML = descriptionAsHTML.arg(tr( - "
    " - "

    Uh oh!

    " - "

    Sorry, there is no description available for this mod. :(

    " - "
    ")); - + descriptionAsHTML = descriptionAsHTML.arg(tr(R"( +
    +

    This mod does not have a valid Nexus ID. You can add a custom web + page for it in the "Custom URL" box below.

    +
    )")); } else { descriptionAsHTML = descriptionAsHTML.arg( BBCode::convertToHTML(nexusDescription)); @@ -191,6 +238,7 @@ void NexusTab::onModChanged() ui->browser->page()->setHtml(descriptionAsHTML); updateVersionColor(); + updateTracking(); } void NexusTab::onModIDChanged() @@ -241,18 +289,6 @@ void NexusTab::onVersionChanged() updateVersionColor(); } -void NexusTab::onOpenLink() -{ - const int modID = mod()->getNexusID(); - - if (isValidModID(modID)) { - const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); - - MOBase::shell::OpenLink(QUrl(nexusLink)); - } -} - void NexusTab::onRefreshBrowser() { const auto modID = mod()->getNexusID(); @@ -265,28 +301,59 @@ void NexusTab::onRefreshBrowser() } } +void NexusTab::onVisitNexus() +{ + const int modID = mod()->getNexusID(); + + if (isValidModID(modID)) { + const QString nexusLink = NexusInterface::instance(&plugin()) + ->getModURL(modID, mod()->getGameName()); + + shell::OpenLink(QUrl(nexusLink)); + } +} + void NexusTab::onEndorse() { core().loggedInAction(parentWidget(), [m=mod()]{ m->endorse(true); }); } -void NexusTab::refreshData(int modID) +void NexusTab::onTrack() { - if (tryRefreshData(modID)) { - m_requestStarted = true; - } else { - onModChanged(); + core().loggedInAction(parentWidget(), [m=mod()] { + if (m->trackedState() == ModInfo::TRACKED_TRUE) { + m->track(false); + } else { + m->track(true); + } + }); +} + +void NexusTab::onCustomURLToggled() +{ + if (m_loading) { + return; } + + mod()->setHasCustomURL(ui->hasCustomURL->isChecked()); + ui->customURL->setEnabled(mod()->hasCustomURL()); + ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); } -bool NexusTab::tryRefreshData(int modID) +void NexusTab::onCustomURLChanged() { - if (isValidModID(modID) && !m_requestStarted) { - if (mod()->updateNXMInfo()) { - ui->browser->setHtml(""); - return true; - } + if (m_loading) { + return; } - return false; + mod()->setCustomURL(ui->customURL->text()); + ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); +} + +void NexusTab::onVisitCustomURL() +{ + const auto url = mod()->parseCustomURL(); + if (url.isValid()) { + shell::OpenLink(url); + } } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 8528f0af..930c1ffc 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -53,17 +53,24 @@ private: void cleanup(); void updateVersionColor(); void updateWebpage(); + void updateTracking(); void refreshData(int modID); bool tryRefreshData(int modID); - void onModChanged(); - void onOpenLink(); + void onModIDChanged(); void onSourceGameChanged(); void onVersionChanged(); + void onRefreshBrowser(); + void onVisitNexus(); void onEndorse(); + void onTrack(); + + void onCustomURLToggled(); + void onCustomURLChanged(); + void onVisitCustomURL(); }; #endif // MODINFODIALOGNEXUS_H diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 4333e351..448447e1 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -100,7 +100,70 @@ void ModInfoRegular::readMeta() m_Repository = metaFile.value("repository", "Nexus").toString(); m_Converted = metaFile.value("converted", false).toBool(); m_Validated = metaFile.value("validated", false).toBool(); - m_URL = metaFile.value("url", "").toString(); + + // this handles changes to how the URL works after 2.2.0 + // + // in 2.2.0, "hasCustomUrl" does not exist and "url" is only used when the mod + // id is invalid, although it can be set at any time in the mod info dialog + // + // post 2.2.0, a custom url can be set on any mod, whether the mod id is + // valid or not, so an additional flag "hasCustomURL" is required, with a + // corresponding checkbox in the mod info dialog + // + // there are several cases to handle to make sure no data is lost and to + // determine whether the user has set a custom url before: + // + // 1) some mods have an incorrect url set along with a valid mod id; + // there is apparently a bug with the fomod installer that can set the + // url of a mod to a value used by a _previous_ installation + // + // 2) it is possible to set the url even if the mod id is valid, in which + // case it is saved, but never used in 2.2.0 + // + // 3) opening the mod info dialog on the nexus tab for a mod that has a + // valid id will force the url to be the same as what the plugin gives + // back + // + // the algorithm is as follows: + // always read the url from the meta file and store it so this piece of data + // is never lost; the problem then only becomes about whether to enable + // hasCustomURL + // + // if hasCustomURL is present in the meta file, just read that and be + // done with it + // + // if not, then the flag depends on the mod id and the url + // if the mod id is valid, the custom url is disabled; although the url + // could be _set_ by the user when a mod id was valid, it was never + // _used_, so the behaviour won't change + // + // if the mod id is invalid, the url should normally be empty, unless the + // user specified one, in which case hasCustomURL should be true + // (the only case where this fails is if a mod id was valid before and + // the user visited the nexus tab, in which case the url was set + // automatically, but then the id was manually changed to 0 + // + // in that case, the mod id is invalid and the url is not empty, but it + // was never set by the user; this case is impossible to distinguish + // from a user manually entering a url, and so is handled as such) + + // always read the url + m_CustomURL = metaFile.value("url").toString(); + + if (metaFile.contains("hasCustomURL")) { + m_HasCustomURL = metaFile.value("hasCustomURL").toBool(); + } else { + if (m_NexusID > 0) { + // the mod id is valid, disable the custom url + m_HasCustomURL = false; + } else { + if (!m_CustomURL.isEmpty()) { + // the mod id is invalid and the url is not empty, enable it + m_HasCustomURL = true; + } + } + } + m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); m_NexusLastModified = QDateTime::fromString(metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); @@ -166,7 +229,8 @@ void ModInfoRegular::saveMeta() metaFile.setValue("comments", m_Comments); metaFile.setValue("notes", m_Notes); metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("url", m_URL); + metaFile.setValue("url", m_CustomURL); + metaFile.setValue("hasCustomURL", m_HasCustomURL); metaFile.setValue("nexusFileStatus", m_NexusFileStatus); metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate)); @@ -769,18 +833,27 @@ void ModInfoRegular::setNexusLastModified(QDateTime time) emit modDetailsUpdated(true); } -void ModInfoRegular::setURL(QString const &url) +void ModInfoRegular::setCustomURL(QString const &url) { - m_URL = url; + m_CustomURL = url; m_MetaInfoChanged = true; } -QString ModInfoRegular::getURL() const +QString ModInfoRegular::getCustomURL() const { - return m_URL; + return m_CustomURL; } +void ModInfoRegular::setHasCustomURL(bool b) +{ + m_HasCustomURL = b; + m_MetaInfoChanged = true; +} +bool ModInfoRegular::hasCustomURL() const +{ + return m_HasCustomURL; +} QStringList ModInfoRegular::archives(bool checkOnDisk) { diff --git a/src/modinforegular.h b/src/modinforegular.h index cfe713ca..705e66a8 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -397,15 +397,10 @@ public: void readMeta(); - /** - * @brief set the URL for a mod - */ - virtual void setURL(QString const &); - - /** - * @returns the URL for a mod - */ - virtual QString getURL() const; + virtual void setHasCustomURL(bool b) override; + virtual bool hasCustomURL() const override; + virtual void setCustomURL(QString const &) override; + virtual QString getCustomURL() const override; private: @@ -432,7 +427,8 @@ private: QString m_Notes; QString m_NexusDescription; QString m_Repository; - QString m_URL; + QString m_CustomURL; + bool m_HasCustomURL; QString m_GameName; mutable QStringList m_Archives; -- cgit v1.3.1 From ee71f40ce9e4edf985ee13437afc6be94c45e925 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 03:59:24 -0400 Subject: convert fs::path to utf-16 instead of ansi --- src/modinfodialog.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a5e8d8cd..b3055b5a 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -360,16 +360,18 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) void ModInfoDialog::feedFiles(bool becauseOriginChanged) { namespace fs = std::filesystem; + const auto rootPath = m_mod->absolutePath(); if (rootPath.length() > 0) { - - for (const auto& entry : fs::recursive_directory_iterator(rootPath.toStdString())) { + fs::path path(rootPath.toStdWString()); + for (const auto& entry : fs::recursive_directory_iterator(path)) { if (!entry.is_regular_file()) { continue; } - const auto fileName = QString::fromWCharArray(entry.path().c_str()); + const auto fileName = QString::fromWCharArray( + entry.path().native().c_str()); for (auto& tabInfo : m_tabs) { if (!tabInfo.isVisible()) { -- cgit v1.3.1 From 16b6e772a7ae1561ba87c6d76d001b19635761cd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 19:56:12 -0400 Subject: fixed conflict lists not expanding vertically properly --- src/modinfodialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 78f19a36..e54b94b3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -506,7 +506,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e 100 - + 0 -- cgit v1.3.1 From c6e80f1a5eed4a57663e7fc55c2727258eaad552 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 11:05:31 -0400 Subject: focus tabs when they need a confirmation to close dialog did not handle closeEvent() at all, it only checked the close button bunch of comments --- src/modinfodialog.cpp | 34 +++++++++++++++-- src/modinfodialog.h | 6 ++- src/modinfodialogtab.cpp | 5 +++ src/modinfodialogtab.h | 87 ++++++++++++++++++++++++++++++++++++++++-- src/modinfodialogtextfiles.cpp | 2 + 5 files changed, 126 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b3055b5a..4af479c4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -154,7 +154,7 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, [this, i](int originID) { - onOriginModified(static_cast(i), originID); + onOriginModified(originID); }); connect( @@ -167,6 +167,15 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, [&]{ setTabsColors(); }); + + connect( + tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, + [&, i=static_cast(i)] + { + if (i < m_tabs.size()) { + switchToTab(ETabs(m_tabs[i].tab->tabID())); + } + }); } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); @@ -546,6 +555,7 @@ void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) } + // removing all tabs ui->tabWidget->clear(); // reset real positions @@ -566,7 +576,7 @@ void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) } } -void ModInfoDialog::onOriginModified(std::size_t tabIndex, int originID) +void ModInfoDialog::onOriginModified(int originID) { emit originModified(originID); updateTabs(true); @@ -581,15 +591,31 @@ void ModInfoDialog::onDeleteShortcut() } } +void ModInfoDialog::closeEvent(QCloseEvent* e) +{ + if (tryClose()) { + e->accept(); + } else { + e->ignore(); + } +} + void ModInfoDialog::on_closeButton_clicked() +{ + if (tryClose()) { + close(); + } +} + +bool ModInfoDialog::tryClose() { for (auto& tabInfo : m_tabs) { if (!tabInfo.tab->canClose()) { - return; + return false; } } - close(); + return true; } void ModInfoDialog::onTabChanged() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 030b3f93..899a3eab 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -117,6 +117,9 @@ public: signals: void originModified(int originID); +protected: + void closeEvent(QCloseEvent* e); + private slots: void on_closeButton_clicked(); void on_nextButton_clicked(); @@ -158,8 +161,9 @@ private: void switchToTab(ETabs id); void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; + bool tryClose(); - void onOriginModified(std::size_t tabIndex, int originID); + void onOriginModified(int originID); void onTabChanged(); void onTabMoved(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index f5eeeed1..0468b405 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -137,6 +137,11 @@ void ModInfoDialogTab::setHasData(bool b) } } +void ModInfoDialogTab::setFocus() +{ + emit wantsFocus(); +} + NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index ce29c868..3f98314a 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -10,6 +10,36 @@ namespace Ui { class ModInfoDialog; } class Settings; class OrganizerCore; +// base class for all tabs in the mod info dialog +// +// when the dialog is opened or when next/previous is clicked, the sequence is: +// setMod(), clear(), feedFile() an update() +// +// when the dialog is closed, canClose() is called on all tabs +// +// when a tab is selected for the first time for the current mod, +// firstActivation() is called; this is used by NexusTab to refresh stuff +// +// when the dialog is first shown, restoreState() is called on all tabs and +// saveState() is called when the dialog is closed +// +// there isn't a good framework for keyboard shortcuts because only the delete +// key is used for now, which calls deletedRequested() on all tabs until one +// returns true +// +// each tab override canHandleSeparators() and canHandleUnmanaged() to return +// true if they can handle separators or unmanaged mods; if these return false +// (which they do by default), the tabs will be removed from the widget entirely +// +// when tabs modify the origin and call emitOriginModified() (such as the +// conflicts tabs), all tabs that return true in usesOriginFiles() will go +// through the full update sequence as above +// +// tabs can call emitModOpen() to request showing a different mod +// +// hasDataChanged() should be called when a tab goes from having data to being +// empty or vice versa; this will update the tab text color +// class ModInfoDialogTab : public QObject { Q_OBJECT; @@ -21,13 +51,63 @@ public: ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; virtual ~ModInfoDialogTab() = default; + // called by ModInfoDialog every time this tab is selected; this will call + // firstActivation() the first time it's called, until resetFirstActivation() + // is called + // void activated(); + + // called by ModInfoDialog when the selected mod has changed, to make sure + // activated() will call firstActivation() next time + // void resetFirstActivation(); + + // called when the selected mod changed, `mod` can never be empty, but + // `origin` can (if the mod is not active, for example) + // + // 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); + + // this tab should clear its user interface; clear() will always be called + // before feedFile() and update() + // virtual void clear() = 0; - virtual void update(); + + // the dialog will go through each file in the mod and call feedFile() + // with it on all tabs; if a tab handles the file, it should return true to + // prevent other tabs from displaying it + // + // this prevents individual tabs from having to go through the filesystem + // independently, which would kill performance, but it cannot be the only way + // to update tabs because some of them don't actually use files (like + // NotesTab) or they use the internal structures such as `FilesOrigin` (like + // ConflictsTab) + // + // once all the files have been fed to tabs, update() will be called; tabs + // that need to do additional work after being fed files, or tabs that are + // unable to use feedFile() at all can use update() to do work + // virtual bool feedFile(const QString& rootPath, const QString& filename); + + // called after all the files on the filesystem have been sent through + // feedFile() + // + // this can be used to do a final processing of the files handled by + // feedFile() (ImagesTab will set up the scrollbars, for example) or something + // completely different (CategoriesTab will fill up the lists) + // + virtual void update(); + + // called when a tab is visible for the first time for the current mod, can + // be used to do expensive work that's not worth doing until the tab is + // actually selected by the user + // virtual void firstActivation(); + + // virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); @@ -38,8 +118,6 @@ public: virtual bool canHandleUnmanaged() const; virtual bool usesOriginFiles() const; - virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); - ModInfo::Ptr mod() const; MOShared::FilesOrigin* origin() const; @@ -50,6 +128,7 @@ signals: void originModified(int originID); void modOpen(QString name); void hasDataChanged(); + void wantsFocus(); protected: Ui::ModInfoDialog* ui; @@ -67,6 +146,8 @@ protected: void emitModOpen(QString name); void setHasData(bool b); + void setFocus(); + // this needs to be a template because saveState() and restoreState() are // not in QWidget, but they're in various widgets // diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 52891bf5..bc44ee3e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -133,6 +133,8 @@ bool GenericFilesTab::canClose() return true; } + setFocus(); + const int res = QMessageBox::question( parentWidget(), QObject::tr("Save changes?"), -- cgit v1.3.1 From 82d985064e5105ded4b20d357eaf7cd1b97fe9da Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 12:02:07 -0400 Subject: added a ModInfoDialogTabContext to avoid passing too many things to tab constructors mod is passed to ctors to make sure they can never be empty only call deleteRequest() to selected mod comments --- src/mainwindow.cpp | 4 +- src/modinfodialog.cpp | 40 +++++++++++------- src/modinfodialog.h | 36 ++++------------ src/modinfodialogcategories.cpp | 14 +++--- src/modinfodialogcategories.h | 4 +- src/modinfodialogconflicts.cpp | 12 +++--- src/modinfodialogconflicts.h | 4 +- src/modinfodialogesps.cpp | 8 ++-- src/modinfodialogesps.h | 4 +- src/modinfodialogfiletree.cpp | 14 +++--- src/modinfodialogfiletree.h | 4 +- src/modinfodialogimages.cpp | 9 ++-- src/modinfodialogimages.h | 4 +- src/modinfodialognexus.cpp | 85 +++++++++++++++++++------------------ src/modinfodialognexus.h | 4 +- src/modinfodialogtab.cpp | 33 ++++++++------- src/modinfodialogtab.h | 94 +++++++++++++++++++++++++++++++++++++---- src/modinfodialogtextfiles.cpp | 30 ++++++------- src/modinfodialogtextfiles.h | 14 +++--- 19 files changed, 226 insertions(+), 191 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d48fae4f..7ab555fa 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3223,11 +3223,9 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); - ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer); + ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer, modInfo); 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.setTab(ModInfoDialog::ETabs(tab)); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4af479c4..4ef010e4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -127,7 +127,8 @@ bool ModInfoDialog::TabInfo::isVisible() const ModInfoDialog::ModInfoDialog( - MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, + ModInfo::Ptr mod) : TutorableDialog("ModInfoDialog", mw), ui(new Ui::ModInfoDialog), m_mainWindow(mw), m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)), @@ -138,6 +139,7 @@ ModInfoDialog::ModInfoDialog( auto* sc = new QShortcut(QKeySequence::Delete, this); connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); + setMod(mod); m_tabs = createTabs(); for (int i=0; itabWidget->count(); ++i) { @@ -184,19 +186,26 @@ ModInfoDialog::ModInfoDialog( ModInfoDialog::~ModInfoDialog() = default; +template +std::unique_ptr createTab(ModInfoDialog& d, int index) +{ + return std::make_unique(ModInfoDialogTabContext( + *d.m_core, *d.m_plugin, &d, d.ui.get(), index, d.m_mod, d.getOrigin())); +} + std::vector ModInfoDialog::createTabs() { std::vector v; - v.push_back(createTab(TAB_TEXTFILES)); - v.push_back(createTab(TAB_INIFILES)); - v.push_back(createTab(TAB_IMAGES)); - v.push_back(createTab(TAB_ESPS)); - v.push_back(createTab(TAB_CONFLICTS)); - v.push_back(createTab(TAB_CATEGORIES)); - v.push_back(createTab(TAB_NEXUS)); - v.push_back(createTab(TAB_NOTES)); - v.push_back(createTab(TAB_FILETREE)); + 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)); return v; } @@ -218,6 +227,7 @@ int ModInfoDialog::exec() void ModInfoDialog::setMod(ModInfo::Ptr mod) { + Q_ASSERT(mod); m_mod = mod; for (auto& tabInfo : m_tabs) { @@ -584,10 +594,8 @@ void ModInfoDialog::onOriginModified(int originID) void ModInfoDialog::onDeleteShortcut() { - for (auto& tabInfo : m_tabs) { - if (tabInfo.tab->deleteRequested()) { - break; - } + if (auto* tabInfo=currentTab()) { + tabInfo->tab->deleteRequested(); } } @@ -663,7 +671,7 @@ void ModInfoDialog::onTabMoved() void ModInfoDialog::on_nextButton_clicked() { auto mod = m_mainWindow->nextModInList(); - if (mod == m_mod) { + if (!mod || mod == m_mod) { return; } @@ -674,7 +682,7 @@ void ModInfoDialog::on_nextButton_clicked() void ModInfoDialog::on_prevButton_clicked() { auto mod = m_mainWindow->previousModInList(); - if (mod == m_mod) { + if (!mod || mod == m_mod) { return; } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 899a3eab..9eb00a3b 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -66,7 +66,11 @@ protected: **/ class ModInfoDialog : public MOBase::TutorableDialog { - Q_OBJECT + Q_OBJECT; + + template + friend std::unique_ptr createTab( + ModInfoDialog& d, int index); public: enum ETabs { @@ -81,30 +85,12 @@ public: TAB_FILETREE }; - /** - * @brief constructor - * - * @param modInfo info structure about the mod to display - * @param parent parend widget - **/ - ModInfoDialog(MainWindow* mw, OrganizerCore* core, PluginContainer* plugin); + ModInfoDialog( + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, + ModInfo::Ptr mod); ~ModInfoDialog(); - /** - * @brief retrieve the (user-modified) version of the mod - * - * @return the (user-modified) version of the mod - **/ - QString getModVersion() const; - - /** - * @brief retrieve the (user-modified) mod id - * - * @return the (user-modified) id of the mod - **/ - const int getModID() const; - void setMod(ModInfo::Ptr mod); void setMod(const QString& name); void setTab(ETabs id); @@ -166,12 +152,6 @@ private: void onOriginModified(int originID); void onTabChanged(); void onTabMoved(); - - template - std::unique_ptr createTab(int index) - { - return std::make_unique(*m_core, *m_plugin, this, ui.get(), index); - } }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 8ffded59..c61e248e 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -3,10 +3,8 @@ #include "categories.h" #include "modinfo.h" -CategoriesTab::CategoriesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id) +CategoriesTab::CategoriesTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)) { connect( ui->categories, &QTreeWidget::itemChanged, @@ -30,7 +28,7 @@ void CategoriesTab::update() clear(); add( - CategoryFactory::instance(), mod()->getCategories(), + CategoryFactory::instance(), mod().getCategories(), ui->categories->invisibleRootItem(), 0); updatePrimary(); @@ -81,7 +79,7 @@ void CategoriesTab::updatePrimary() { ui->primaryCategories->clear(); - int primaryCategory = mod()->getPrimaryCategory(); + int primaryCategory = mod().getPrimaryCategory(); addChecked(ui->categories->invisibleRootItem()); @@ -111,7 +109,7 @@ void CategoriesTab::save(QTreeWidgetItem* currentNode) for (int i = 0; i < currentNode->childCount(); ++i) { QTreeWidgetItem *childNode = currentNode->child(i); - mod()->setCategory( + mod().setCategory( childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); save(childNode); @@ -134,6 +132,6 @@ void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) void CategoriesTab::onPrimaryChanged(int index) { if (index != -1) { - mod()->setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); + mod().setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); } } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 392023e7..c8b52fec 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -5,9 +5,7 @@ class CategoryFactory; class CategoriesTab : public ModInfoDialogTab { public: - CategoriesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + CategoriesTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index a3383a50..511d48ad 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -382,11 +382,9 @@ void for_each_in_selection(QTreeView* tree, F&& f) } -ConflictsTab::ConflictsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_general(this, ui, oc), m_advanced(this, ui, oc) +ConflictsTab::ConflictsTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(cx), // don't move, cx is used again + m_general(this, cx.ui, cx.core), m_advanced(this, cx.ui, cx.core) { connect( &m_general, &GeneralConflictsTab::modOpen, @@ -872,7 +870,7 @@ bool GeneralConflictsTab::update() int numOverwritten = 0; if (m_tab->origin() != nullptr) { - const auto rootPath = m_tab->mod()->absolutePath(); + const auto rootPath = m_tab->mod().absolutePath(); for (const auto& file : m_tab->origin()->getFiles()) { // careful: these two strings are moved into createXItem() below @@ -1085,7 +1083,7 @@ void AdvancedConflictsTab::update() clear(); if (m_tab->origin() != nullptr) { - const auto rootPath = m_tab->mod()->absolutePath(); + const auto rootPath = m_tab->mod().absolutePath(); const auto& files = m_tab->origin()->getFiles(); m_model->reserve(files.size()); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 3fa12231..a77c2ac9 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -103,9 +103,7 @@ class ConflictsTab : public ModInfoDialogTab Q_OBJECT; public: - ConflictsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ConflictsTab(ModInfoDialogTabContext cx); void update() override; void clear() override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 8dceaa31..fba5d39a 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -224,11 +224,9 @@ private: -ESPsTab::ESPsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) +ESPsTab::ESPsTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), + m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) { ui->inactiveESPList->setModel(m_inactiveModel); ui->activeESPList->setModel(m_activeModel); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index 217863c6..b128f279 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -11,9 +11,7 @@ class ESPsTab : public ModInfoDialogTab Q_OBJECT; public: - ESPsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ESPsTab(ModInfoDialogTabContext cx); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 6690dd2f..35480e2c 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -13,10 +13,8 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; -FileTreeTab::FileTreeTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_fs(nullptr) +FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); @@ -58,7 +56,7 @@ void FileTreeTab::clear() void FileTreeTab::update() { - const auto rootPath = mod()->absolutePath(); + const auto rootPath = mod().absolutePath(); m_fs->setRootPath(rootPath); ui->filetree->setRootIndex(m_fs->index(rootPath)); @@ -139,7 +137,7 @@ void FileTreeTab::onPreview() return; } - core().previewFile(parentWidget(), mod()->name(), m_fs->filePath(selection)); + core().previewFile(parentWidget(), mod().name(), m_fs->filePath(selection)); } void FileTreeTab::onExplore() @@ -149,7 +147,7 @@ void FileTreeTab::onExplore() if (selection.isValid()) { shell::ExploreFile(m_fs->filePath(selection)); } else { - shell::ExploreFile(mod()->absolutePath()); + shell::ExploreFile(mod().absolutePath()); } } @@ -205,7 +203,7 @@ void FileTreeTab::onUnhide() void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(mod()->absolutePath()); + shell::ExploreFile(mod().absolutePath()); } bool FileTreeTab::deleteFile(const QModelIndex& index) diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 9f5206b9..f9fa62d4 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -6,9 +6,7 @@ class FileTreeTab : public ModInfoDialogTab { public: - FileTreeTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + FileTreeTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 307fa8e8..cf33f9f5 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -35,12 +35,9 @@ QString dimensionString(const QSize& s) } -ImagesTab::ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), - m_ddsAvailable(false), m_ddsEnabled(false) +ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), m_image(new ScalableImage), + m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index d22a6ab2..8d9b965b 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -314,9 +314,7 @@ class ImagesTab : public ModInfoDialogTab friend class ImagesTabHelpers::ThumbnailsWidget; public: - ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ImagesTab(ModInfoDialogTabContext cx); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 8c8ce55a..81546f58 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -14,11 +14,8 @@ bool isValidModID(int id) return (id > 0); } -NexusTab::NexusTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false), - m_loading(false) +NexusTab::NexusTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); @@ -70,9 +67,9 @@ void NexusTab::update() clear(); - ui->modID->setText(QString("%1").arg(mod()->getNexusID())); + ui->modID->setText(QString("%1").arg(mod().getNexusID())); - QString gameName = mod()->getGameName(); + QString gameName = mod().getGameName(); ui->sourceGame->addItem( core().managedGame()->gameName(), core().managedGame()->gameShortName()); @@ -100,10 +97,10 @@ void NexusTab::update() [&](const QUrl& url){ shell::OpenLink(url); }); ui->endorse->setEnabled( - (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || - (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); + (mod().endorsedState() == ModInfo::ENDORSED_FALSE) || + (mod().endorsedState() == ModInfo::ENDORSED_NEVER)); - setHasData(mod()->getNexusID() >= 0); + setHasData(mod().getNexusID() >= 0); } void NexusTab::firstActivation() @@ -128,10 +125,10 @@ bool NexusTab::usesOriginFiles() const void NexusTab::updateVersionColor() { - if (mod()->getVersion() != mod()->getNewestVersion()) { + if (mod().getVersion() != mod().getNewestVersion()) { ui->version->setStyleSheet("color: red"); ui->version->setToolTip(tr("Current Version: %1").arg( - mod()->getNewestVersion().canonicalString())); + mod().getNewestVersion().canonicalString())); } else { ui->version->setStyleSheet("color: green"); ui->version->setToolTip(tr("No update available")); @@ -140,11 +137,11 @@ void NexusTab::updateVersionColor() void NexusTab::updateWebpage() { - const int modID = mod()->getNexusID(); + const int modID = mod().getNexusID(); if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); + ->getModURL(modID, mod().getGameName()); ui->visitNexus->setToolTip(nexusLink); refreshData(modID); @@ -152,19 +149,19 @@ void NexusTab::updateWebpage() onModChanged(); } - ui->version->setText(mod()->getVersion().displayString()); - ui->hasCustomURL->setChecked(mod()->hasCustomURL()); - ui->customURL->setText(mod()->getCustomURL()); - ui->customURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + ui->version->setText(mod().getVersion().displayString()); + ui->hasCustomURL->setChecked(mod().hasCustomURL()); + ui->customURL->setText(mod().getCustomURL()); + ui->customURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setToolTip(mod().parseCustomURL().toString()); updateTracking(); } void NexusTab::updateTracking() { - if (mod()->trackedState() == ModInfo::TRACKED_TRUE) { + if (mod().trackedState() == ModInfo::TRACKED_TRUE) { ui->track->setChecked(true); ui->track->setText(tr("Tracked")); } else { @@ -185,7 +182,7 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { if (isValidModID(modID) && !m_requestStarted) { - if (mod()->updateNXMInfo()) { + if (mod().updateNXMInfo()) { ui->browser->setHtml(""); return true; } @@ -198,7 +195,7 @@ void NexusTab::onModChanged() { m_requestStarted = false; - const QString nexusDescription = mod()->getNexusDescription(); + const QString nexusDescription = mod().getNexusDescription(); QString descriptionAsHTML = R"( @@ -247,12 +244,12 @@ void NexusTab::onModIDChanged() return; } - const int oldID = mod()->getNexusID(); + const int oldID = mod().getNexusID(); const int newID = ui->modID->text().toInt(); if (oldID != newID){ - mod()->setNexusID(newID); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + mod().setNexusID(newID); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); ui->browser->page()->setHtml(""); @@ -270,9 +267,9 @@ void NexusTab::onSourceGameChanged() for (auto game : plugin().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { - mod()->setGameName(game->gameShortName()); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); - refreshData(mod()->getNexusID()); + mod().setGameName(game->gameShortName()); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + refreshData(mod().getNexusID()); return; } } @@ -285,16 +282,16 @@ void NexusTab::onVersionChanged() } MOBase::VersionInfo version(ui->version->text()); - mod()->setVersion(version); + mod().setVersion(version); updateVersionColor(); } void NexusTab::onRefreshBrowser() { - const auto modID = mod()->getNexusID(); + const auto modID = mod().getNexusID(); if (isValidModID(modID)) { - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); updateWebpage(); } else { qInfo("Mod has no valid Nexus ID, info can't be updated."); @@ -303,11 +300,11 @@ void NexusTab::onRefreshBrowser() void NexusTab::onVisitNexus() { - const int modID = mod()->getNexusID(); + const int modID = mod().getNexusID(); if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); + ->getModURL(modID, mod().getGameName()); shell::OpenLink(QUrl(nexusLink)); } @@ -315,12 +312,16 @@ void NexusTab::onVisitNexus() void NexusTab::onEndorse() { - core().loggedInAction(parentWidget(), [m=mod()]{ m->endorse(true); }); + // use modPtr() instead of mod() or this because the callback may be + // executed after the dialog is closed + core().loggedInAction(parentWidget(), [m=modPtr()]{ m->endorse(true); }); } void NexusTab::onTrack() { - core().loggedInAction(parentWidget(), [m=mod()] { + // use modPtr() instead of mod() or this because the callback may be + // executed after the dialog is closed + core().loggedInAction(parentWidget(), [m=modPtr()] { if (m->trackedState() == ModInfo::TRACKED_TRUE) { m->track(false); } else { @@ -335,9 +336,9 @@ void NexusTab::onCustomURLToggled() return; } - mod()->setHasCustomURL(ui->hasCustomURL->isChecked()); - ui->customURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); + mod().setHasCustomURL(ui->hasCustomURL->isChecked()); + ui->customURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setEnabled(mod().hasCustomURL()); } void NexusTab::onCustomURLChanged() @@ -346,13 +347,13 @@ void NexusTab::onCustomURLChanged() return; } - mod()->setCustomURL(ui->customURL->text()); - ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + mod().setCustomURL(ui->customURL->text()); + ui->visitCustomURL->setToolTip(mod().parseCustomURL().toString()); } void NexusTab::onVisitCustomURL() { - const auto url = mod()->parseCustomURL(); + const auto url = mod().parseCustomURL(); if (url.isValid()) { shell::OpenLink(url); } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 930c1ffc..6478375b 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -33,9 +33,7 @@ signals: class NexusTab : public ModInfoDialogTab { public: - NexusTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + NexusTab(ModInfoDialogTabContext cx); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 0468b405..554df6df 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -3,11 +3,9 @@ #include "texteditor.h" #include "directoryentry.h" -ModInfoDialogTab::ModInfoDialogTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabID(id), m_hasData(false), m_firstActivation(true) +ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) : + ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), + m_origin(cx.origin), m_tabID(cx.id), m_hasData(false), m_firstActivation(true) { } @@ -82,8 +80,15 @@ void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) m_origin = origin; } -ModInfo::Ptr ModInfoDialogTab::mod() const +ModInfo& ModInfoDialogTab::mod() const { + Q_ASSERT(m_mod); + return *m_mod; +} + +ModInfo::Ptr ModInfoDialogTab::modPtr() const +{ + Q_ASSERT(m_mod); return m_mod; } @@ -143,10 +148,8 @@ void ModInfoDialogTab::setFocus() } -NotesTab::NotesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index) +NotesTab::NotesTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)) { connect(ui->comments, &QLineEdit::editingFinished, [&]{ onComments(); }); connect(ui->notes, &HTMLEditor::editingFinished, [&]{ onNotes(); }); @@ -161,8 +164,8 @@ void NotesTab::clear() void NotesTab::update() { - const auto comments = mod()->comments(); - const auto notes = mod()->notes(); + const auto comments = mod().comments(); + const auto notes = mod().notes(); ui->comments->setText(comments); ui->notes->setText(notes); @@ -176,7 +179,7 @@ bool NotesTab::canHandleSeparators() const void NotesTab::onComments() { - mod()->setComments(ui->comments->text()); + mod().setComments(ui->comments->text()); checkHasData(); } @@ -184,9 +187,9 @@ void NotesTab::onNotes() { // Avoid saving html stub if notes field is empty. if (ui->notes->toPlainText().isEmpty()) { - mod()->setNotes({}); + mod().setNotes({}); } else { - mod()->setNotes(ui->notes->toHtml()); + mod().setNotes(ui->notes->toHtml()); } checkHasData(); diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 3f98314a..eb574de0 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -10,6 +10,33 @@ namespace Ui { class ModInfoDialog; } class Settings; class OrganizerCore; +// helper struct to avoid passing too much stuff to tab constructors +// +struct ModInfoDialogTabContext +{ + OrganizerCore& core; + PluginContainer& plugin; + QWidget* parent; + Ui::ModInfoDialog* ui; + int id; + ModInfo::Ptr mod; + MOShared::FilesOrigin* origin; + + ModInfoDialogTabContext( + OrganizerCore& core, + PluginContainer& plugin, + QWidget* parent, + Ui::ModInfoDialog* ui, + int id, + ModInfo::Ptr mod, + MOShared::FilesOrigin* origin) : + core(core), plugin(plugin), parent(parent), ui(ui), id(id), + mod(mod), origin(origin) + { + } +}; + + // base class for all tabs in the mod info dialog // // when the dialog is opened or when next/previous is clicked, the sequence is: @@ -38,7 +65,7 @@ class OrganizerCore; // tabs can call emitModOpen() to request showing a different mod // // hasDataChanged() should be called when a tab goes from having data to being -// empty or vice versa; this will update the tab text color +// empty or vice versa; this will update the tab text colour // class ModInfoDialogTab : public QObject { @@ -107,20 +134,75 @@ public: // virtual void firstActivation(); + // called when closing the dialog, can return false to stop the dialog from + // closing + // + // this is typically used by tabs that require manual saving, like text files; + // tabs that refuse to close should focus themselves before showing whatever + // confirmation they have // virtual bool canClose(); + + + // called after the dialog is closed, tabs should save whatever UI state they + // want + // virtual void saveState(Settings& s); + + // called before the is shown, tabs should restore whatever UI state they + // saved in saveState() + // virtual void restoreState(const Settings& s); + + // called on the selected tab when the Delete key is pressed on the keyboard; + // tabs _must_ check which widget currently has focus to decide whether this + // should be handled or not; do not blindly delete stuff when this is called + // + // if the delete request was handled, this should return true + // virtual bool deleteRequested(); + + // return true if this tab can handle a separator mod, defaults to false; + // when this returns false, the tab is removed from the widget entirely + // + // if a tab can show meaningful information about a separator (like + // categories or notes), it should return true + // virtual bool canHandleSeparators() const; + + // return true if this tab can handle unmanaged mods, defaults to false; + // when this returns false, the tab is removed from the widget entirely + // virtual bool canHandleUnmanaged() const; + + // return true if this tab uses the files from the mod's origin, defaults to + // false + // + // tabs that do not care about the files inside a mod should return false, + // such as the notes or categories tab + // + // mods that return true will be updated anytime a tab calls + // emitOriginModifed() + // virtual bool usesOriginFiles() const; - ModInfo::Ptr mod() const; + + // returns the currently selected mod + // + ModInfo& mod() const; + + // returns the currently selected mod, can never be empty + // + ModInfo::Ptr modPtr() const; + + // returns the origin of the selected mod; this can be null for mods that + // don't have an origin, like deactivated mods + // MOShared::FilesOrigin* origin() const; + int tabID() const; bool hasData() const; @@ -133,9 +215,7 @@ signals: protected: Ui::ModInfoDialog* ui; - ModInfoDialogTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ModInfoDialogTab(ModInfoDialogTabContext cx); OrganizerCore& core(); PluginContainer& plugin(); @@ -186,9 +266,7 @@ private: class NotesTab : public ModInfoDialogTab { public: - NotesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + NotesTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index bc44ee3e..7a09fa4e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -99,10 +99,10 @@ private: GenericFilesTab::GenericFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : - ModInfoDialogTab(oc, plugin, parent, ui, id), + ModInfoDialogTabContext cx, + QListView* list, QSplitter* sp, + TextEditor* e, QLineEdit* filter) : + ModInfoDialogTab(std::move(cx)), m_list(list), m_editor(e), m_splitter(sp), m_model(new FileListModel) { m_list->setModel(m_model); @@ -208,13 +208,10 @@ void GenericFilesTab::select(const QModelIndex& index) } -TextFilesTab::TextFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : GenericFilesTab( - oc, plugin, parent, ui, id, - ui->textFileList, ui->tabTextSplitter, - ui->textFileEditor, ui->textFileFilter) +TextFilesTab::TextFilesTab(ModInfoDialogTabContext cx) + : GenericFilesTab(cx, + cx.ui->textFileList, cx.ui->tabTextSplitter, + cx.ui->textFileEditor, cx.ui->textFileFilter) { } @@ -231,13 +228,10 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : GenericFilesTab( - oc, plugin, parent, ui, id, - ui->iniFileList, ui->tabIniSplitter, - ui->iniFileEditor, ui->iniFileFilter) +IniFilesTab::IniFilesTab(ModInfoDialogTabContext cx) + : GenericFilesTab(cx, + cx.ui->iniFileList, cx.ui->tabIniSplitter, + cx.ui->iniFileEditor, cx.ui->iniFileFilter) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index ffe49904..725ac999 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -30,9 +30,9 @@ protected: FilterWidget m_filter; GenericFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* splitter, TextEditor* editor, QLineEdit* filter); + ModInfoDialogTabContext cx, + QListView* list, QSplitter* splitter, + TextEditor* editor, QLineEdit* filter); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -45,9 +45,7 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + TextFilesTab(ModInfoDialogTabContext cx); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -57,9 +55,7 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + IniFilesTab(ModInfoDialogTabContext cx); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- 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') 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 d9a7ef636bc111f71de697caed89171335a5f3e3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 13:21:48 -0400 Subject: reordering tabs, then switching to a mod with a different tab set would discard the changes comments, moving some stuff around --- src/mainwindow.cpp | 2 +- src/modinfodialog.cpp | 163 ++++++++++++++++++++++++++----------------------- src/modinfodialog.h | 134 +++++++++++++++++++++++++++++++++++----- src/modinfodialog.ui | 6 +- src/modinfodialogtab.h | 61 +++++++++++++++++- 5 files changed, 268 insertions(+), 98 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a596c542..7681b482 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3229,7 +3229,7 @@ void MainWindow::displayModInformation( //Open the tab first if we want to use the standard indexes of the tabs. if (tabID != ModInfoTabIDs::None) { - dialog.setTab(tabID); + dialog.selectTab(tabID); } dialog.restoreState(m_OrganizerCore.settings()); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c8ffa35b..adbd22df 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -170,8 +170,11 @@ ModInfoDialog::ModInfoDialog( [&, id=tabInfo.tab->tabID()]{ switchToTab(id); }); } - connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); + connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabSelectionChanged(); }); connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); + connect(ui->close, &QPushButton::clicked, [&]{ onCloseButton(); }); + connect(ui->previousMod, &QPushButton::clicked, [&]{ onPreviousMod(); }); + connect(ui->nextMod, &QPushButton::clicked, [&]{ onNextMod(); }); } ModInfoDialog::~ModInfoDialog() = default; @@ -242,7 +245,7 @@ void ModInfoDialog::setMod(const QString& name) setMod(mod); } -void ModInfoDialog::setTab(ModInfoTabIDs id) +void ModInfoDialog::selectTab(ModInfoTabIDs id) { if (!isVisible()) { m_initialTab = id; @@ -323,6 +326,10 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) return; } + // something changed; save the current order (if necessary) because some tabs + // will be removed and others added + saveTabOrder(Settings::instance()); + // remember selection const int selIndex = ui->tabWidget->currentIndex(); auto sel = ModInfoTabIDs::None; @@ -337,6 +344,68 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) reAddTabs(visibility, sel); } +void ModInfoDialog::reAddTabs( + const std::vector& visibility, ModInfoTabIDs sel) +{ + Q_ASSERT(visibility.size() == m_tabs.size()); + + // ordered tab names from settings + const auto orderedNames = getOrderedTabNames(); + + bool canSort = true; + + // gathering visible tabs + std::vector visibleTabs; + for (std::size_t i=0; iobjectName(); + auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); + if (itor == orderedNames.end()) { + qCritical() << "can't sort tabs, '" << objectName << "' not found"; + canSort = false; + } + } + } + } + + // sorting tabs + if (canSort) { + std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ + auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); + auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); + + // this was checked above + Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); + + return (aItor < bItor); + }); + } + + + // removing all tabs + ui->tabWidget->clear(); + + // reset real positions + for (auto& tabInfo : m_tabs) { + tabInfo.realPos = -1; + } + + // add visible tabs + for (std::size_t i=0; i(i); + ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); + + if (tabInfo.tab->tabID() == sel) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + } + } +} + void ModInfoDialog::updateTabs(bool becauseOriginChanged) { auto* origin = getOrigin(); @@ -440,10 +509,7 @@ MOShared::FilesOrigin* ModInfoDialog::getOrigin() void ModInfoDialog::saveState(Settings& s) const { - const auto tabState = saveTabState(); - if (!tabState.isEmpty()) { - s.directInterface().setValue("mod_info_tab_order", tabState); - } + saveTabOrder(s); // remove 2.2.0 settings s.directInterface().remove("mod_info_tabs"); @@ -466,21 +532,24 @@ void ModInfoDialog::restoreState(const Settings& s) } } -QString ModInfoDialog::saveTabState() const +void ModInfoDialog::saveTabOrder(Settings& s) const { if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { // only save tab state when all tabs are visible - return {}; + return; } - QString result; - QTextStream stream(&result); + QString names; for (int i=0; itabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName() << " "; + if (!names.isEmpty()) { + names += " "; + } + + names += ui->tabWidget->widget(i)->objectName(); } - return result.trimmed(); + s.directInterface().setValue("mod_info_tab_order", names); } std::vector ModInfoDialog::getOrderedTabNames() const @@ -516,68 +585,6 @@ std::vector ModInfoDialog::getOrderedTabNames() const return v; } -void ModInfoDialog::reAddTabs( - const std::vector& visibility, ModInfoTabIDs sel) -{ - Q_ASSERT(visibility.size() == m_tabs.size()); - - // ordered tab names from settings - const auto orderedNames = getOrderedTabNames(); - - bool canSort = true; - - // gathering visible tabs - std::vector visibleTabs; - for (std::size_t i=0; iobjectName(); - auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); - if (itor == orderedNames.end()) { - qCritical() << "can't sort tabs, '" << objectName << "' not found"; - canSort = false; - } - } - } - } - - // sorting tabs - if (canSort) { - std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ - auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); - auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); - - // this was checked above - Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); - - return (aItor < bItor); - }); - } - - - // removing all tabs - ui->tabWidget->clear(); - - // reset real positions - for (auto& tabInfo : m_tabs) { - tabInfo.realPos = -1; - } - - // add visible tabs - for (std::size_t i=0; i(i); - ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); - - if (tabInfo.tab->tabID() == sel) { - ui->tabWidget->setCurrentIndex(static_cast(i)); - } - } -} - void ModInfoDialog::onOriginModified(int originID) { emit originModified(originID); @@ -600,7 +607,7 @@ void ModInfoDialog::closeEvent(QCloseEvent* e) } } -void ModInfoDialog::on_closeButton_clicked() +void ModInfoDialog::onCloseButton() { if (tryClose()) { close(); @@ -618,7 +625,7 @@ bool ModInfoDialog::tryClose() return true; } -void ModInfoDialog::onTabChanged() +void ModInfoDialog::onTabSelectionChanged() { if (m_arrangingTabs) { // this can be fired while re-arranging tabs, which happens before mods @@ -660,7 +667,7 @@ void ModInfoDialog::onTabMoved() } } -void ModInfoDialog::on_nextButton_clicked() +void ModInfoDialog::onNextMod() { auto mod = m_mainWindow->nextModInList(); if (!mod || mod == m_mod) { @@ -671,7 +678,7 @@ void ModInfoDialog::on_nextButton_clicked() update(); } -void ModInfoDialog::on_prevButton_clicked() +void ModInfoDialog::onPreviousMod() { auto mod = m_mainWindow->previousModInList(); if (!mod || mod == m_mod) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index effb5d98..035eb6d6 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -43,6 +43,9 @@ class ModInfoDialog : public MOBase::TutorableDialog { Q_OBJECT; + // creates a tab, it's a friend because it uses a bunch of member variables + // to create ModInfoDialogTabContext + // template friend std::unique_ptr createTab( ModInfoDialog& d, ModInfoTabIDs index); @@ -54,37 +57,67 @@ public: ~ModInfoDialog(); - void setMod(ModInfo::Ptr mod); - void setMod(const QString& name); - - void setTab(ModInfoTabIDs id); + // switches to the tab with the given id + // + void selectTab(ModInfoTabIDs id); + // updates all tabs, selects the initial tab and opens the dialog + // int exec() override; + // saves the dialog state and calls saveState() on all tabs + // void saveState(Settings& s) const; + + // restores the dialog state and calls restoreState() on all tabs + // void restoreState(const Settings& s); signals: + // emitted when a tab changes the origin + // void originModified(int originID); protected: + // forwards to tryClose() + // void closeEvent(QCloseEvent* e); -private slots: - void on_closeButton_clicked(); - void on_nextButton_clicked(); - void on_prevButton_clicked(); - private: + // represents a single tab + // struct TabInfo { + // tab implementation std::unique_ptr tab; + + // actual position in the tab bar, updated every time a tab is moved int realPos; + + // widget used by the QTabWidget for this tab + // + // because QTabWidget doesn't support simply hiding tabs, they have to be + // completely removed from the widget when they don't support the current + // mod + // + // therefore, `widget, `caption` and `icon` are remembered so tabs can be + // removed and re-added when navigating between mods + // + // `widget` is also used figure out which tab is where when they're + // re-ordered QWidget* widget; + + // caption for this tab, see `widget` QString caption; + + // icon for this tab, see `widget` QIcon icon; + TabInfo(std::unique_ptr tab); + + // returns whether this tab is part of the tab widget + // bool isVisible() const; }; @@ -94,28 +127,99 @@ private: OrganizerCore* m_core; PluginContainer* m_plugin; std::vector m_tabs; + + // initial tab requested by the main window when the dialog is opened; whether + // the request can be honoured depends on what tabs are present ModInfoTabIDs m_initialTab; + + // set to true when tabs are being removed and re-added while navigating + // between mods; since the current index changes while this is happening, + // onTabSelectionChanged() will be called repeatedly + // + // however, it will check this flag and ignore the event so first activations + // are not fired incorrectly bool m_arrangingTabs; + + // creates all the tabs + // std::vector createTabs(); + + + // sets the currently selected mod; resets first activation, but doesn't + // update anything + // + void setMod(ModInfo::Ptr mod); + + // sets the currently selected mod, if found; forwards to setMod() above + // + void setMod(const QString& name); + + + // returns the currently selected tab, taking re-ordering in to account; + // shouldn't be null, but could be + // TabInfo* currentTab(); - void restoreTabState(const QString& state); - QString saveTabState() const; + + // returns a list of tab object names in their visual order, used by + // saveState() + // + void saveTabOrder(Settings& s) const; + + + // fully updates the dialog; sets the title, the tab visibility and updates + // all the tabs; used when the current mod changes + // + // see setTabsVisibility() for firstTime + // void update(bool firstTime=false); + + // builds the list of visible tabs; if the list is different from what's + // currently displayed, or firstTime is true, forwards to reAddTabs() + void setTabsVisibility(bool firstTime); + + // clears the tab widgets and re-adds the tabs having the visible flag in + // the given vector, following the + void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); + void onDeleteShortcut(); MOShared::FilesOrigin* getOrigin(); - void setTabsVisibility(bool firstTime); void updateTabs(bool becauseOriginChanged=false); void feedFiles(bool becauseOriginChanged); void setTabsColors(); void switchToTab(ModInfoTabIDs id); - void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); std::vector getOrderedTabNames() const; bool tryClose(); - void onOriginModified(int originID); - void onTabChanged(); + + // called when the user clicks the close button; closing the dialog by other + // means ends up in closeEvent(); forwards to tryClose() + // + void onCloseButton(); + + // called when the user clicks the previous button; asks the main window for + // the previous mod in the list and loads it + // + void onPreviousMod(); + + // called when the user clicks the next button; asks the main window for the + // next mod in the list and loads it + // + void onNextMod(); + + // called when the selects a tab; handles first activation + // + void onTabSelectionChanged(); + + // called when the user re-orders tabs; sets the correct TabInfo::realPos for + // all tabs + // void onTabMoved(); + + // called when a tab has modified the origin; emits originModified() and + // updates all the tabs that use origin files + // + void onOriginModified(int originID); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e54b94b3..5b559992 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -1260,7 +1260,7 @@ p, li { white-space: pre-wrap; } - + Previous @@ -1270,7 +1270,7 @@ p, li { white-space: pre-wrap; } - + Next @@ -1293,7 +1293,7 @@ p, li { white-space: pre-wrap; } - + Close diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 283d9e73..c43fa076 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -203,13 +203,29 @@ public: MOShared::FilesOrigin* origin() const; + // return this tab's ID + // ModInfoTabIDs tabID() const; + + // returns whether this tab has data; derived classes should call setHasData() + // bool hasData() const; signals: + // emitted when a tab modified the files in a mod + // void originModified(int originID); + + // emitted when a tab wants to open a mod by name + // void modOpen(QString name); + + // emitted when a tab used to have data and is now empty, or vice versa + // void hasDataChanged(); + + // emitted when a tab wants focus + // void wantsFocus(); protected: @@ -219,15 +235,27 @@ protected: OrganizerCore& core(); PluginContainer& plugin(); - QWidget* parentWidget(); + // emits originModified + // void emitOriginModified(); + + // emits modOpen + // void emitModOpen(QString name); + + // emits hasDataChanged + // void setHasData(bool b); + // emits wantsFocus + // void setFocus(); + + // saves the sate of the given widget in geometry/modinfodialog_[objectname] + // // this needs to be a template because saveState() and restoreState() are // not in QWidget, but they're in various widgets // @@ -237,6 +265,12 @@ protected: s.setValue(settingName(w), w->saveState()); } + // restores the sate of the given widget from + // geometry/modinfodialog_[objectname] + // + // this needs to be a template because saveState() and restoreState() are + // not in QWidget, but they're in various widgets + // template void restoreWidgetState(const QSettings& s, Widget* w) { @@ -246,16 +280,33 @@ protected: } private: + // core OrganizerCore& m_core; + + // plugin PluginContainer& m_plugin; + + // parent widget, used to display modal dialogs QWidget* m_parent; + + // current mod, never null ModInfoPtr m_mod; + + // current mod origin, may be null MOShared::FilesOrigin* m_origin; + + // tab ID ModInfoTabIDs m_tabID; + + // whether the tab has data bool m_hasData; + + // true if the tab has never been selected for the current mod bool m_firstActivation; + // used by saveWidgetState() and restoreWidgetState() + // QString settingName(QWidget* w) { return "geometry/modinfodialog_" + w->objectName(); @@ -263,6 +314,8 @@ private: }; +// the Notes tab +// class NotesTab : public ModInfoDialogTab { public: @@ -270,7 +323,13 @@ public: void clear() override; void update() override; + + // returns true, separators can have notes + // bool canHandleSeparators() const override; + + // returns false, notes don't use files + // bool usesOriginFiles() const override; private: -- cgit v1.3.1 From 971f890826412994a56fc1b37b10d3bf21e1275d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 14:36:59 -0400 Subject: some refactoring updateTabs() creates a list of tabs to update instead of checking for each file comments --- src/modinfodialog.cpp | 307 +++++++++++++++++++++++++++--------------- src/modinfodialog.h | 55 ++++++-- src/modinfodialogfiletree.cpp | 1 - 3 files changed, 241 insertions(+), 122 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index adbd22df..16690019 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -33,6 +33,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +namespace fs = std::filesystem; const int max_scan_for_context_menu = 50; @@ -140,15 +141,51 @@ ModInfoDialog::ModInfoDialog( connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); setMod(mod); - m_tabs = createTabs(); + createTabs(); - for (int i=0; itabWidget->count(); ++i) { - if (static_cast(i) >= m_tabs.size()) { - qCritical() << "mod info dialog has more tabs than expected"; - break; - } + connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabSelectionChanged(); }); + connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); + connect(ui->close, &QPushButton::clicked, [&]{ onCloseButton(); }); + connect(ui->previousMod, &QPushButton::clicked, [&]{ onPreviousMod(); }); + connect(ui->nextMod, &QPushButton::clicked, [&]{ onNextMod(); }); +} + +ModInfoDialog::~ModInfoDialog() = default; + +template +std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) +{ + return std::make_unique(ModInfoDialogTabContext( + *d.m_core, *d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); +} + +void ModInfoDialog::createTabs() +{ + m_tabs.clear(); + + m_tabs.push_back(createTab(*this, ModInfoTabIDs::TextFiles)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::IniFiles)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Images)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Esps)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Conflicts)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Categories)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Nexus)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Notes)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Filetree)); + + // check for tabs in the ui not having a corresponding tab in the list + int count = ui->tabWidget->count(); + if (count < 0 || count > static_cast(m_tabs.size())) { + qCritical() << "mod info dialog has more tabs than expected"; + count = static_cast(m_tabs.size()); + } + // for each tab in the widget; connects the widgets with the tab objects + // + for (int i=0; i(i)]; + + // remembering tab information so tabs can be removed and re-added tabInfo.widget = ui->tabWidget->widget(i); tabInfo.caption = ui->tabWidget->tabText(i); tabInfo.icon = ui->tabWidget->tabIcon(i); @@ -169,50 +206,18 @@ ModInfoDialog::ModInfoDialog( tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, [&, id=tabInfo.tab->tabID()]{ switchToTab(id); }); } - - connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabSelectionChanged(); }); - connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); - connect(ui->close, &QPushButton::clicked, [&]{ onCloseButton(); }); - connect(ui->previousMod, &QPushButton::clicked, [&]{ onPreviousMod(); }); - connect(ui->nextMod, &QPushButton::clicked, [&]{ onNextMod(); }); -} - -ModInfoDialog::~ModInfoDialog() = default; - -template -std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) -{ - return std::make_unique(ModInfoDialogTabContext( - *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, 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() { + // whether to select the first tab; if the main window requested a specific + // tab, it is selected when encountered in update() const auto selectFirst = (m_initialTab == ModInfoTabIDs::None); update(true); - if (selectFirst) { - if (ui->tabWidget->count() > 0) { - ui->tabWidget->setCurrentIndex(0); - } + if (selectFirst && ui->tabWidget->count() > 0) { + ui->tabWidget->setCurrentIndex(0); } return TutorableDialog::exec(); @@ -223,6 +228,8 @@ void ModInfoDialog::setMod(ModInfo::Ptr mod) Q_ASSERT(mod); m_mod = mod; + // resetting the first activation flag so selecting tabs will trigger it + // again for (auto& tabInfo : m_tabs) { tabInfo.tab->resetFirstActivation(); } @@ -248,6 +255,7 @@ void ModInfoDialog::setMod(const QString& name) void ModInfoDialog::selectTab(ModInfoTabIDs id) { if (!isVisible()) { + // can't select a tab if the dialog hasn't been properly updated yet m_initialTab = id; return; } @@ -262,42 +270,55 @@ ModInfoDialog::TabInfo* ModInfoDialog::currentTab() return nullptr; } + // looking for the actual tab at that position for (auto& tabInfo : m_tabs) { if (tabInfo.realPos == index) { return &tabInfo; } } - qCritical() << "tab index " << index << " not found"; return nullptr; } void ModInfoDialog::update(bool firstTime) { + // remembering the current selection, will be restored if the tab still + // exists const int oldTab = ui->tabWidget->currentIndex(); setWindowTitle(m_mod->name()); + + // rebuilding the tab widget if needed depending on what tabs are valid for + // the current mod setTabsVisibility(firstTime); + // updating the data in all tabs updateTabs(); + // switching to the initial tab, if any if (m_initialTab != ModInfoTabIDs::None) { switchToTab(m_initialTab); m_initialTab = ModInfoTabIDs::None; } if (ui->tabWidget->currentIndex() == oldTab) { - // manually fire activated() because the tab index hasn't been changed if (auto* tabInfo=currentTab()) { + // activated() has to be fired manually because the tab index hasn't been + // changed tabInfo->tab->activated(); + } else { + qCritical() << "tab index " << oldTab << " not found"; } } } void ModInfoDialog::setTabsVisibility(bool firstTime) { + // this flag is picked up by onTabSelectionChanged() to avoid triggering + // activation events while moving tabs around QScopedValueRollback arrangingTabs(m_arrangingTabs, true); + // one bool per tab to indicate whether the tab should be visible std::vector visibility(m_tabs.size()); bool changed = false; @@ -307,14 +328,16 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) bool visible = true; + // a tab is visible if it can handle the current mod if (m_mod->hasFlag(ModInfo::FLAG_FOREIGN)) { visible = tabInfo.tab->canHandleUnmanaged(); } else if (m_mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { visible = tabInfo.tab->canHandleSeparators(); } + // if the visibility of this tab is changing, set changed to true because + // the tabs have to be rebuilt const auto currentlyVisible = (ui->tabWidget->indexOf(tabInfo.widget) != -1); - if (visible != currentlyVisible) { changed = true; } @@ -322,25 +345,23 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) visibility[i] = visible; } + // the tabs have to be rebuilt the first time the dialog is shown, or when + // the visibility of any tab has changed if (!firstTime && !changed) { return; } - // something changed; save the current order (if necessary) because some tabs - // will be removed and others added + // save the current order (if necessary) because some tabs will be removed and + // others added saveTabOrder(Settings::instance()); - // remember selection - const int selIndex = ui->tabWidget->currentIndex(); + // remember selection, if any auto sel = ModInfoTabIDs::None; - - for (const auto& tabInfo : m_tabs) { - if (tabInfo.realPos == selIndex) { - sel = tabInfo.tab->tabID(); - break; - } + if (const auto* tabInfo=currentTab()) { + sel = tabInfo->tab->tabID(); } + // removes all tabs and re-adds the visible ones reAddTabs(visibility, sel); } @@ -352,21 +373,31 @@ void ModInfoDialog::reAddTabs( // ordered tab names from settings const auto orderedNames = getOrderedTabNames(); + // whether the tabs can be sorted; if the object name of a tab widget is not + // found in orderedNames, the list cannot be sorted safely bool canSort = true; // gathering visible tabs std::vector visibleTabs; for (std::size_t i=0; iobjectName(); - auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); - if (itor == orderedNames.end()) { - qCritical() << "can't sort tabs, '" << objectName << "' not found"; - canSort = false; - } + if (!visibility[i]) { + // this tab is not visible, skip it + continue; + } + + // this tab is visible + visibleTabs.push_back(&m_tabs[i]); + + if (canSort) { + // make sure the widget object name is found in the list + const auto objectName = m_tabs[i].widget->objectName(); + auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); + + if (itor == orderedNames.end()) { + // this shouldn't happen, it means there's a tab in the UI that's no + // in the list + qCritical() << "can't sort tabs, '" << objectName << "' not found"; + canSort = false; } } } @@ -374,10 +405,16 @@ void ModInfoDialog::reAddTabs( // sorting tabs if (canSort) { std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ - auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); - auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); + // looking the names in the ordered list + auto aItor = std::find( + orderedNames.begin(), orderedNames.end(), + a->widget->objectName()); - // this was checked above + auto bItor = std::find( + orderedNames.begin(), orderedNames.end(), + b->widget->objectName()); + + // this shouldn't happen, it was checked above Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); return (aItor < bItor); @@ -397,9 +434,13 @@ void ModInfoDialog::reAddTabs( for (std::size_t i=0; i(i); + + // adding tab ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); + // selecting if (tabInfo.tab->tabID() == sel) { ui->tabWidget->setCurrentIndex(static_cast(i)); } @@ -410,59 +451,72 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) { auto* origin = getOrigin(); + // list of tabs that should be updated + std::vector interestedTabs; + for (auto& tabInfo : m_tabs) { + // don't touch invisible tabs if (!tabInfo.isVisible()) { continue; } + // updateTabs() is also called from onOriginModified() to update all the + // tabs that depend on the origin; if updateTabs() is called because the + // origin changed, but the tab doesn't use origin files, it can be safely + // skipped + // + // this happens for tabs like notes and categories, which don't need to + // be updated when files change if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { continue; } - tabInfo.tab->setMod(m_mod, origin); - tabInfo.tab->clear(); + // this tab should be updated + interestedTabs.push_back(&tabInfo); } - feedFiles(becauseOriginChanged); + for (auto* tabInfo : interestedTabs) { + // set the current mod + tabInfo->tab->setMod(m_mod, origin); - for (auto& tabInfo : m_tabs) { - if (tabInfo.isVisible()) { - tabInfo.tab->update(); - } + // clear + tabInfo->tab->clear(); } + // feed all the files from the filesystem + feedFiles(interestedTabs); + + // call update() on all tabs + for (auto* tabInfo : interestedTabs) { + tabInfo->tab->update(); + } + + // update the text colours setTabsColors(); } -void ModInfoDialog::feedFiles(bool becauseOriginChanged) +void ModInfoDialog::feedFiles(std::vector& interestedTabs) { - namespace fs = std::filesystem; - const auto rootPath = m_mod->absolutePath(); + if (rootPath.isEmpty()) { + return; + } - if (rootPath.length() > 0) { - fs::path path(rootPath.toStdWString()); - for (const auto& entry : fs::recursive_directory_iterator(path)) { - if (!entry.is_regular_file()) { - continue; - } - - const auto fileName = QString::fromWCharArray( - entry.path().native().c_str()); + const fs::path fsPath(rootPath.toStdWString()); - for (auto& tabInfo : m_tabs) { - if (!tabInfo.isVisible()) { - continue; - } + for (const auto& entry : fs::recursive_directory_iterator(fsPath)) { + if (!entry.is_regular_file()) { + // skip directories + continue; + } - if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { - continue; - } + const auto filePath = QString::fromStdWString(entry.path().native()); - if (tabInfo.tab->feedFile(rootPath, fileName)) { - break; - } + // for each tab + for (auto* tabInfo : interestedTabs) { + if (tabInfo->tab->feedFile(rootPath, filePath)) { + break; } } } @@ -470,41 +524,53 @@ void ModInfoDialog::feedFiles(bool becauseOriginChanged) void ModInfoDialog::setTabsColors() { + const auto p = m_mainWindow->palette(); + for (const auto& tabInfo : m_tabs) { - const auto c = tabInfo.tab->hasData() ? + if (!tabInfo.isVisible()) { + // don't bother with invisible tabs + continue; + } + + const QColor color = tabInfo.tab->hasData() ? QColor::Invalid : - m_mainWindow->palette().color(QPalette::Disabled, QPalette::WindowText); + p.color(QPalette::Disabled, QPalette::WindowText); - ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, c); + ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, color); } } void ModInfoDialog::switchToTab(ModInfoTabIDs id) { + // look a tab with the given id for (const auto& tabInfo : m_tabs) { if (tabInfo.tab->tabID() == id) { + // use realPos to select the proper tab in the widget ui->tabWidget->setCurrentIndex(tabInfo.realPos); return; } } + // this could happen if the tab is not visible right now qDebug() - << "can't switch to tab ID " << static_cast(id) << ", not available"; + << "can't switch to tab ID " << static_cast(id) + << ", not available"; } 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; - } + + if (!ds->originExists(m_mod->name().toStdWString())) { + return nullptr; } - return nullptr; + auto* origin = &ds->getOriginByName(m_mod->name().toStdWString()); + if (origin->isDisabled()) { + return nullptr; + } + + return origin; } void ModInfoDialog::saveState(Settings& s) const @@ -520,6 +586,7 @@ void ModInfoDialog::saveState(Settings& s) const s.directInterface().remove("mod_info_conflicts_noconflict"); s.directInterface().remove("mod_info_conflicts_overwritten"); + // save state for each tab for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); } @@ -527,6 +594,10 @@ void ModInfoDialog::saveState(Settings& s) const void ModInfoDialog::restoreState(const Settings& s) { + // tab order is not restored here, it will be picked up if tabs have to be + // removed and re-added + + // restore state for each tab for (const auto& tabInfo : m_tabs) { tabInfo.tab->restoreState(s); } @@ -536,6 +607,13 @@ void ModInfoDialog::saveTabOrder(Settings& s) const { if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { // only save tab state when all tabs are visible + // + // if not all tabs are visible, it becomes very difficult to figure out in + // what order the user wants these tabs to be, so just avoid saving it + // completely + // + // this means that reordering tabs when not all tabs are visible is not + // saved, but it's better than breaking everything return; } @@ -559,7 +637,7 @@ std::vector ModInfoDialog::getOrderedTabNames() const std::vector v; if (settings.contains("mod_info_tabs")) { - // old byte array + // old byte array from 2.2.0 QDataStream stream(settings.value("mod_info_tabs").toByteArray()); int count = 0; @@ -587,12 +665,16 @@ std::vector ModInfoDialog::getOrderedTabNames() const void ModInfoDialog::onOriginModified(int originID) { + // tell the main window the origin changed emit originModified(originID); + + // update tabs that depend on the origin updateTabs(true); } void ModInfoDialog::onDeleteShortcut() { + // forward the request to the current tab if (auto* tabInfo=currentTab()) { tabInfo->tab->deleteRequested(); } @@ -616,6 +698,10 @@ void ModInfoDialog::onCloseButton() bool ModInfoDialog::tryClose() { + // cancel the close if any tab returns false; for example. this can happen if + // a tab has unsaved content, pops a confirmation dialog, and the user clicks + // cancel + for (auto& tabInfo : m_tabs) { if (!tabInfo.tab->canClose()) { return false; @@ -634,6 +720,7 @@ void ModInfoDialog::onTabSelectionChanged() return; } + // this will call firstActivation() on the tab if needed if (auto* tabInfo=currentTab()) { tabInfo->tab->activated(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 035eb6d6..34555b0c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -141,9 +141,9 @@ private: bool m_arrangingTabs; - // creates all the tabs + // creates all the tabs and connects events // - std::vector createTabs(); + void createTabs(); // sets the currently selected mod; resets first activation, but doesn't @@ -155,17 +155,16 @@ private: // void setMod(const QString& name); + // returns the origin of the current mod, may be null + // + MOShared::FilesOrigin* getOrigin(); + // returns the currently selected tab, taking re-ordering in to account; // shouldn't be null, but could be // TabInfo* currentTab(); - // returns a list of tab object names in their visual order, used by - // saveState() - // - void saveTabOrder(Settings& s) const; - // fully updates the dialog; sets the title, the tab visibility and updates // all the tabs; used when the current mod changes @@ -179,16 +178,50 @@ private: void setTabsVisibility(bool firstTime); // clears the tab widgets and re-adds the tabs having the visible flag in - // the given vector, following the + // the given vector, following the tab order from the settings + // void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); - void onDeleteShortcut(); - MOShared::FilesOrigin* getOrigin(); + // called by update(); clears tabs, feeds files and calls update() on all + // tabs, then setTabsColors() + // void updateTabs(bool becauseOriginChanged=false); - void feedFiles(bool becauseOriginChanged); + + // goes through all files on the filesystem for the current mod and calls + // feedFile() on every tab until one accepts it + // + void feedFiles(std::vector& interestedTabs); + + // goes through all tabs and sets the tab text colour depending on whether + // they have data or not + // void setTabsColors(); + + + // called when the delete key is pressed anywhere in the dialog; forwards to + // ModInfoDialogTab::deleteRequest() for the currently selected tab + // + void onDeleteShortcut(); + + + // finds the tab with the given id and selects it + // void switchToTab(ModInfoTabIDs id); + + + // saves the current tab order; used by saveState(), but also by + // setTabsVisibility() to make sure any changes to order are saved before + // re-adding tabs + // + void saveTabOrder(Settings& s) const; + + // returns a list of tab names in the order they should appear on the widget + // std::vector getOrderedTabNames() const; + + // asks all the tabs if they accept closing the dialog, returns false if one + // objected + // bool tryClose(); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 35480e2c..0b519932 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -314,7 +314,6 @@ void FileTreeTab::changeVisibility(bool visible) qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; if (changed) { - qDebug().nospace() << "triggering refresh"; if (origin()) { emitOriginModified(); } -- cgit v1.3.1 From 7fe637ce4421e0c6d6ee6b103db5fcc4ef676c25 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 14:57:33 -0400 Subject: display error when loading an image fails --- src/modinfodialogimages.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index cf33f9f5..69866902 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -888,11 +888,20 @@ File::File(QString path) void File::ensureOriginalLoaded() { - if (m_original.isNull()) { - if (!m_original.load(m_path)) { - qCritical() << "failed to load image from " << m_path; - m_failed = true; - } + if (!m_original.isNull()) { + // already loaded + return; + } + + QImageReader reader(m_path); + + if (!reader.read(&m_original)) { + qCritical().noquote().nospace() + << "failed to load '" << m_path << "'\n" + << reader.errorString() << " " + << "(error " << static_cast(reader.error()) << ")"; + + m_failed = true; } } -- cgit v1.3.1 From aeca2a7782a29f3d893f318d91d00109b6451f7e Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 14:14:10 -0500 Subject: Restore UI files to VS project and remove unused files --- src/CMakeLists.txt | 9 +-- src/finddialog.ui | 76 --------------------- src/gameinfoimpl.cpp | 56 --------------- src/installdialog.ui | 165 --------------------------------------------- src/simpleinstalldialog.ui | 82 ---------------------- 5 files changed, 3 insertions(+), 385 deletions(-) delete mode 100644 src/finddialog.ui delete mode 100644 src/gameinfoimpl.cpp delete mode 100644 src/installdialog.ui delete mode 100644 src/simpleinstalldialog.ui (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 929ee296..f197211a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -253,7 +253,6 @@ SET(organizer_HDRS SET(organizer_UIS transfersavesdialog.ui syncoverwritedialog.ui - simpleinstalldialog.ui settingsdialog.ui selectiondialog.ui queryoverwritedialog.ui @@ -265,8 +264,6 @@ SET(organizer_UIS mainwindow.ui lockeddialog.ui waitingonclosedialog.ui - installdialog.ui - finddialog.ui editexecutablesdialog.ui credentialsdialog.ui categoriesdialog.ui @@ -293,7 +290,7 @@ SET(organizer_RCS ) -source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp)") +source_group(src REGULAR_EXPRESSION ".*\\.(h|cpp|ui)") set(application iuserinterface @@ -457,7 +454,7 @@ set(src_filters foreach(filter in list ${src_filters}) set(files) foreach(d in lists ${${filter}}) - set(files ${files} ${d}.cpp ${d}.h ${d}.inc) + set(files ${files} ${d}.cpp ${d}.h ${d}.inc ${d}.ui) endforeach() source_group(src\\${filter} FILES ${files}) @@ -553,7 +550,7 @@ ELSE() SET(usvfs_name usvfs_x86) ENDIF() -ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm}) +ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIS} ${organizer_RCS} ${organizer_QRCS} ${organizer_translations_qm}) TARGET_LINK_LIBRARIES(ModOrganizer Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick Qt5::Qml Qt5::QuickWidgets Qt5::Network Qt5::WebSockets diff --git a/src/finddialog.ui b/src/finddialog.ui deleted file mode 100644 index 2c4c80b8..00000000 --- a/src/finddialog.ui +++ /dev/null @@ -1,76 +0,0 @@ - - - FindDialog - - - - 0 - 0 - 400 - 72 - - - - Find - - - - - - - - - - Find what: - - - - - - - Search term - - - Search term - - - - - - - - - - - - - Find next occurence from current file position. - - - Find next occurence from current file position. - - - &Find Next - - - - - - - Close - - - Close - - - Close - - - - - - - - - - diff --git a/src/gameinfoimpl.cpp b/src/gameinfoimpl.cpp deleted file mode 100644 index 61a4c523..00000000 --- a/src/gameinfoimpl.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "gameinfoimpl.h" -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -GameInfoImpl::GameInfoImpl() -{ -} - -IGameInfo::Type GameInfoImpl::type() const -{ - switch (GameInfo::instance().getType()) { - case GameInfo::TYPE_OBLIVION: return IGameInfo::TYPE_OBLIVION; - case GameInfo::TYPE_FALLOUT3: return IGameInfo::TYPE_FALLOUT3; - case GameInfo::TYPE_FALLOUT4: return IGameInfo::TYPE_FALLOUT4; - case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV; - case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM; - case GameInfo::TYPE_SKYRIMSE: return IGameInfo::TYPE_SKYRIMSE; - default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType())); - } -} - - -QString GameInfoImpl::path() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); -} - -QString GameInfoImpl::binaryName() const -{ - return ToQString(GameInfo::instance().getBinaryName()); -} diff --git a/src/installdialog.ui b/src/installdialog.ui deleted file mode 100644 index 88019c77..00000000 --- a/src/installdialog.ui +++ /dev/null @@ -1,165 +0,0 @@ - - - InstallDialog - - - - 0 - 0 - 516 - 407 - - - - - 16777215 - 16777215 - - - - Install Mods - - - - - - false - - - - - - New Mod - - - - - - - - - 50 - 0 - - - - Name - - - - - - - Pick a name for the mod - - - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. - - - - - - - - - Content - - - - - - - Qt::CustomContextMenu - - - Content of the archive. You can change the directory structure by using drag&drop. Hint: Also try right clicking... - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - - - - - - true - - - QAbstractItemView::DragDrop - - - QAbstractItemView::ExtendedSelection - - - false - - - - 1 - - - - - - - - - - - - - - 12 - 75 - true - - - - Placeholder - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - OK - - - - - - - Cancel - - - - - - - - - - ArchiveTree - QTreeWidget -
    archivetree.h
    -
    -
    - - -
    diff --git a/src/simpleinstalldialog.ui b/src/simpleinstalldialog.ui deleted file mode 100644 index a9fdc1d5..00000000 --- a/src/simpleinstalldialog.ui +++ /dev/null @@ -1,82 +0,0 @@ - - - SimpleInstallDialog - - - - 0 - 0 - 400 - 71 - - - - Quick Install - - - - - - - - Name - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Opens a Dialog that allows custom modifications. - - - Opens a Dialog that allows custom modifications. - - - Manual - - - - - - - OK - - - true - - - - - - - Cancel - - - - - - - - - - -- cgit v1.3.1 From 474e3d1e1511188d5c2f9e1ea5685e062aef85f7 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 14:15:27 -0500 Subject: Force instance selection window on top --- src/instancemanager.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index b5130e8b..ddc2d067 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -217,6 +217,8 @@ QString InstanceManager::chooseInstance(const QStringList &instanceList) const QObject::tr("Delete an Instance."), static_cast(Special::Manage)); + selection.setWindowFlags(selection.windowFlags() | Qt::WindowStaysOnTopHint); + if (selection.exec() == QDialog::Rejected) { qDebug("rejected"); throw MOBase::MyException(QObject::tr("Canceled")); -- cgit v1.3.1 From fe678559074f179d74753b16d682cfb72160bb33 Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 14:21:31 -0500 Subject: Month update check should calculate by day of month instead of 30 days --- src/modinfo.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 585d4963..73ead71c 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -310,10 +310,10 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei } } - if (latest < QDateTime::currentDateTimeUtc().addDays(-30)) { + if (latest < QDateTime::currentDateTimeUtc().addMonths(-1)) { std::set> organizedGames; for (auto mod : s_Collection) { - if (mod->canBeUpdated() && mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addDays(-30)) { + if (mod->canBeUpdated() && mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addMonths(-1)) { organizedGames.insert(std::make_pair(mod->getGameName().toLower(), mod->getNexusID())); } } @@ -330,7 +330,7 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei for (auto game : organizedGames) NexusInterface::instance(pluginContainer)->requestUpdates(game.second, receiver, QVariant(), game.first, QString()); - } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-30)) { + } else if (earliest < QDateTime::currentDateTimeUtc().addMonths(-1)) { for (auto gameName : games) NexusInterface::instance(pluginContainer)->requestUpdateInfo(gameName, NexusInterface::UpdatePeriod::MONTH, receiver, QVariant(true), QString()); } else if (earliest < QDateTime::currentDateTimeUtc().addDays(-7)) { @@ -362,7 +362,7 @@ std::set> ModInfo::filteredMods(QString gameName, QVaria if (addOldMods) for (auto mod : s_Collection) - if (mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addDays(-30) && mod->getGameName().compare(gameName, Qt::CaseInsensitive) == 0) + if (mod->getLastNexusUpdate() < QDateTime::currentDateTimeUtc().addMonths(-1) && mod->getGameName().compare(gameName, Qt::CaseInsensitive) == 0) finalMods.insert(mod); if (markUpdated) { -- cgit v1.3.1 From 7926205ff201da92b77a3133b77d7f7a18e4ab6a Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 15:00:06 -0500 Subject: About dialog updates --- src/aboutdialog.cpp | 4 +- src/aboutdialog.h | 2 +- src/aboutdialog.ui | 63 +- src/organizer_en.ts | 2095 +++++++++++++++++++++++---------------------------- 4 files changed, 1003 insertions(+), 1161 deletions(-) (limited to 'src') diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 9d68fe2b..41fd24f7 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -115,7 +115,7 @@ void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QL } } -void AboutDialog::on_copyrightText_linkActivated(const QString &link) +void AboutDialog::on_sourceText_linkActivated(const QString &link) { emit linkClicked(link); -} \ No newline at end of file +} diff --git a/src/aboutdialog.h b/src/aboutdialog.h index 285d0066..9b9b6102 100644 --- a/src/aboutdialog.h +++ b/src/aboutdialog.h @@ -74,7 +74,7 @@ private: private slots: void on_creditsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_copyrightText_linkActivated(const QString &link); + void on_sourceText_linkActivated(const QString &link); private: diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 1b3f0ad2..c72a85f8 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -120,20 +120,43 @@ - <html><head/><body><p>Copyright 2011-2016 Sebastian Herbord</p><p>Copyright 2016-2018 Mod Organizer 2 contributors</p></body></html> + <html><head/><body><p>Copyright © 2011-2016 Sebastian Herbord<br/>Copyright © 2016-2019 Mod Organizer 2 Contributors</p></body></html> - <html><head/><body><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p><p>See the GNU General Public License for more details.</p><p>Source code can be found at <a href="https://github.com/Modorganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> + <html><head/><body><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p><p>See the GNU General Public License for more details.</p></body></html> true + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 40 + + + + + + + + <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> + + +
    @@ -157,7 +180,7 @@ - Lead Developers/ Maintainers + Lead Developers && Maintainers @@ -175,11 +198,6 @@ AL12 - - - erasmux - - Silarn @@ -198,7 +216,7 @@ - Mo2 devs and Contributors + MO2 Developers && Contributors @@ -213,12 +231,22 @@ - Project579 + erasmux + + + + + isanae - przester + Project579 + + + + + przester @@ -292,6 +320,11 @@ pndrev (German) + + + yohru (Japanese) + + Mordan (Greek) @@ -337,17 +370,17 @@ - Jax, Nubbie (Swedish) + Jax (Swedish) - yohru (Japanese) + Nubbie (Swedish) - ...and all other Transifex contributors! + ...and all other contributors! @@ -361,7 +394,7 @@ - Other Supporters & Contributors + Other Supporters && Contributors diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 283f2d3c..7e824cc2 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -15,114 +15,114 @@ - - Used Software - - - - - Thanks + + <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> + <html><head/><body><p>Source code can be found at <a href="https://github.com/Modorganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> - - Lead Developers/ Maintainers + + Used Software - - LePresidente (Project Lead) + + Thanks - - Mo2 devs and Contributors + + Lead Developers && Maintainers + Lead Developers/ Maintainers - - Project579 + + LePresidente (Project Lead) - - przester + + MO2 Developers && Contributors - + Translators - + Cyb3r (Dutch) - + fruttyx (French) - + Yoplala (French) - + Faron (German) - + Mordan (Greek) - + Yoosk (Polish) - + Brgodfx (Portuguese) - + zDas (Portuguese) - + Jax (Swedish) - Jax, Nubbie (Swedish) - - yohru (Japanese) + + Nubbie (Swedish) - - ...and all other Transifex contributors! + + ...and all other contributors! - + Other Supporters && Contributors - Other Supporters & Contributors - + + yohru (Japanese) + + + + Tannin (Original Creator) - + Close @@ -172,6 +172,24 @@ p, li { white-space: pre-wrap; }
    + + AdvancedConflictListModel + + + Overwrites + + + + + File + + + + + Overwritten By + + + BrowserDialog @@ -270,6 +288,39 @@ p, li { white-space: pre-wrap; } + + ConflictsTab + + + &Hide + + + + + &Unhide + + + + + &Open/Execute + + + + + &Preview + + + + + Open in &Explorer + + + + + &Go to... + + + CredentialsDialog @@ -539,9 +590,6 @@ p, li { white-space: pre-wrap; } This will permanently delete the selected download. Are you absolutely sure you want to proceed? - This will permanently delete the selected download. - -Are you absolutely sure you want to procede? @@ -549,9 +597,6 @@ Are you absolutely sure you want to procede? This will remove all finished downloads from this list and from disk. Are you absolutely sure you want to proceed? - This will remove all finished downloads from this list and from disk. - -Are you absolutely sure you want to procede? @@ -559,9 +604,6 @@ Are you absolutely sure you want to procede? This will remove all installed downloads from this list and from disk. Are you absolutely sure you want to proceed? - This will remove all installed downloads from this list and from disk. - -Are you absolutely sure you want to procede? @@ -569,9 +611,6 @@ Are you absolutely sure you want to procede? This will remove all uninstalled downloads from this list and from disk. Are you absolutely sure you want to proceed? - This will remove all uninstalled downloads from this list and from disk. - -Are you absolutely sure you want to procede? @@ -935,6 +974,30 @@ Canceling download "%2"... Executables + + + + + Add an executable + + + + + Add + + + + + + + Remove the selected executable + + + + + Remove + + @@ -1091,30 +1154,6 @@ Right now the only case I know of where this needs to be overwritten is for the (*) Profile Specific - - - - - Add an executable - - - - - Add - - - - - - - Remove the selected executable - - - - - Remove - - Reset plugin executables @@ -1140,6 +1179,11 @@ Right now the only case I know of where this needs to be overwritten is for the Executable (%1) + + + Select a directory + + Java (32-bit) required @@ -1150,9 +1194,78 @@ Right now the only case I know of where this needs to be overwritten is for the MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. + + + FileTreeTab + + + &New Folder + + - - Select a directory + + &Open/Execute + + + + + &Preview + + + + + Open in &Explorer + + + + + &Rename + + + + + &Delete + + + + + &Hide + + + + + &Unhide + + + + + + New Folder + + + + + Failed to create "%1" + + + + + Are you sure you want to delete "%1"? + + + + + Are you sure you want to delete the selected files? + + + + + Confirm + + + + + Failed to delete %1 @@ -1160,35 +1273,28 @@ Right now the only case I know of where this needs to be overwritten is for the FindDialog - Find - Find what: - - Search term - - Find next occurence from current file position. - &Find Next @@ -1196,9 +1302,6 @@ Right now the only case I know of where this needs to be overwritten is for the - - - Close @@ -1282,68 +1385,6 @@ Right now the only case I know of where this needs to be overwritten is for the
    - - InstallDialog - - - Install Mods - - - - - New Mod - - - - - Name - - - - - Pick a name for the mod - - - - - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. - - - - - Content - - - - - Content of the archive. You can change the directory structure by using drag&drop. Hint: Also try right clicking... - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - - - - - Placeholder - - - - - OK - - - - - Cancel - - - InstallationManager @@ -1492,14 +1533,12 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOApplication - an error occured: %1 - an error occurred: %1 + an error occurred: %1 - an error occured - an error occurred + an error occurred @@ -1590,6 +1629,16 @@ This is likely due to a corrupted or incompatible download or unrecognized archi Pick a module collection + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> + + Open list options... @@ -1614,7 +1663,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi - + Create Backup @@ -1790,8 +1839,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -1869,305 +1918,299 @@ p, li { white-space: pre-wrap; } - - &Settings... + + Main ToolBar - - - Visit &Nexus + + &File - - - Visit the Nexus website in your browser for more mods + + + &Tools - - - &Update Mod Organizer + + + + &Help - - &Notifications... + + &View - - - Open the notifications dialog + + &Toolbars - - This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. + + &Run - - - Show help options + + Install &Mod... - - - &Endorse ModOrganizer + + Install &Mod - - - Copy &Log + + + Install a new mod from an archive - - - Copy log to clipboard + + Ctrl+M - - &Change Game... + + &Profiles... - - &Change Game + + &Profiles - - - E&xit + + + Configure profiles - - - Exits Mod Organizer + + Ctrl+P - - M&ain Toolbar + + &Executables... - - &Small Icons + + &Executables - - Lar&ge Icons + + + Configure the executables that can be started through Mod Organizer - - &Icons Only + + Ctrl+E - - &Text Only + + &Tool Plugins - - I&cons and Text + + Tools - - M&edium Icons + + Ctrl+I - - &Menu + + &Settings... - - St&atus bar + + &Settings - - Install &Mod + + + Configure settings and workarounds - - - Install a new mod from an archive + + Ctrl+S - - Ctrl+M + + + Visit &Nexus - - &Profiles + + + Visit the Nexus website in your browser for more mods - - Ctrl+P + + Ctrl+N - - &Executables + + + &Update Mod Organizer - - - Configure the executables that can be started through Mod Organizer + + + Mod Organizer is up-to-date - - Ctrl+E + + &Notifications... - - Tools + + + Open the notifications dialog - - - - &Tools + + This button will be highlighted on the toolbar if MO discovered potential problems in your setup and provide tips on how to fix them. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + + + Show help options - - Main ToolBar + + Ctrl+H - - &File + + + &Endorse ModOrganizer - - - - &Help + + + + Endorse Mod Organizer - - &Edit + + + Copy &Log - - &View + + + Copy log to clipboard - - &Toolbars + + &Change Game... - - &Run + + &Change Game - - Install &Mod... + + + Open the Instance selection dialog to manage a different Game - - &Profiles... + + + E&xit - - - Configure profiles + + + Exits Mod Organizer - - &Executables... + + M&ain Toolbar - - Ctrl+I + + &Small Icons - - &Settings + + Lar&ge Icons - - - Configure settings and workarounds + + &Icons Only - - Ctrl+S + + &Text Only - - Ctrl+N + + I&cons and Text - - - Mod Organizer is up-to-date + + M&edium Icons - - Ctrl+H + + &Menu - - - - Endorse Mod Organizer + + St&atus bar - - - Open the Instance selection dialog to manage a different Game + + Toolbar and Menu @@ -2197,11 +2240,6 @@ p, li { white-space: pre-wrap; } Error: %1 - - - Toolbar and Menu - - There are notifications to read @@ -2214,8 +2252,8 @@ Error: %1 - - + + Endorse @@ -2310,757 +2348,730 @@ Error: %1 - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - - You need to be logged in with Nexus to resume a download - - - - - - - - You need to be logged in with Nexus to endorse - - - - - + Endorsing multiple mods will take a while. Please wait... - - + Unendorsing multiple mods will take a while. Please wait... - - - - You need to be logged in with Nexus to track - - - - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - - Nexus ID for this Mod is unknown + + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - - Web page for this mod is unknown - - - - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - - Visit web page + + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3068,12 +3079,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3081,22 +3092,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3104,348 +3115,348 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - - Remove '%1' from the toolbar - - - - - Errors occured - - - - - Backup of modlist created - - - - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + + Remove '%1' from the toolbar + + + + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + + Errors occurred + + + + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + + Backup of mod list created + + + + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3569,99 +3580,73 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - Textfiles + + Text Files - + A list of text-files in the mod directory. - + A list of text-files in the mod directory like readmes. - - - Save - - - - - INI-Files + + INI Files - + Ini Files - + This is a list of .ini files in the mod. - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. - - Ini Tweaks *This feature is non-functional* - - - - - This is a list of ini tweaks (ini modifications that can be toggled). - - - - - This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. - This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod whether the tweaks are really optional. - - - - - Save changes to the file. - - - - - Save changes to the file. This overwrites the original. There is no automatic backup! + + Images - - Images + + Show .dds files - - Images located in the mod. + + Open in Explorer - - This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. + + 0x0 - - + + Optional ESPs - + List of esps, esms, and esls that can not be loaded by the game. - + List of esps, esms, and esls contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. @@ -3669,503 +3654,262 @@ Most mods do not have optional esps, so chances are good you are looking at an e - - Make the selected mod in the lower list unavailable. - - - - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - - - - + Move a file to the data directory. - + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - - ESPs in the data directory and thus visible to the game. + + Make the selected mod in the lower list unavailable. - - These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. - These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - + Available ESPs - - Conflicts + + ESPs in the data directory and thus visible to the game. - - General + + <html><head/><body><p>These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window.</p></body></html> - - The following conflicted files are provided by this mod + + Conflicts - - - - - File + + General - - Overwritten Mods + + The following conflicted files are provided by this mod - + The following conflicted files are provided by other mods - - Providing Mod - - - - + The following files have no conflicts - + Advanced - - Overwrites - - - - - Overwritten by - - - - + Whether files that have no conflicts should be visible in the list - + Show files that have no conflicts - + Shows all mods overwriting or being overwritten by this mod - + Show all conflicting mods - + Shows only the nearest conflicting mods, in order of priority - + Show nearest conflicting mod - + Filter - - Categories - - - - - Primary Category - - - - - Nexus Info - - - - - Mod ID - - - - - Mod ID for this mod on Nexus. - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer2 on Nexus. Why not go there now and endorse us?</p></body></html> - - - - - Web page URL (only used if invalid NexusID) : - - - - - Source Game - - - - - Source game for this mod. - - - - - <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - - - - - Version - - - - - Refresh - - - - - Refresh all information from Nexus. - - - - - Description - - - - - about:blank - - - - - Endorse - - - - - Notes - - - - - - - Enter comments about the mod here. These are displayed in the notes column of the mod list. - - - - - - - Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - - - - - Filetree - - - - - Open Mod in Explorer - - - - - A directory view of this mod - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - - - - - Previous - - - - - Next - - - - - Close - - - - - &Delete - - - - - &Rename - - - - - &Hide - - - - - &Unhide - - - - - &Open - - - - - &New Folder - - - - - &Preview - - - - - - Save changes? - - - - - - Save changes to "%1"? - - - - - File Exists - - - - - A file with that name exists, please enter a new one - - - - - failed to move file - - - - - failed to create directory "optional" - - - - - Info requested, please wait - - - - - Main + + Categories - - Update + + Primary Category - - Optional + + Nexus Info - - Old + + Mod ID - - Miscellaneous + + Mod ID for this mod on Nexus. - - Deleted + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> - - Unknown + + Source Game - - Current Version: %1 + + Source game for this mod. - - No update available + + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - - <div style="text-align: center;"><h1>Uh oh!</h1><p>Sorry, there is no description available for this mod. :(</p></div> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - - <a href="%1">Visit on Nexus</a> + + Version - - Failed to delete %1 + + + Refresh - - - - - Confirm + + Refresh all information from Nexus. - - - Are sure you want to delete "%1"? + + + Open in Browser - - - Are sure you want to delete the selected files? + + Endorse - - Unhide + + Track - - - New Folder + + about:blank - - Failed to create "%1" + + Use Custom URL - - Hide + + Notes - - Open/Execute + + + + Enter comments about the mod here. These are displayed in the notes column of the mod list. - - Preview + + + + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. - - Go to... + + Filetree - - Name + + Open Mod in Explorer - - Please enter a name + + A directory view of this mod - - - Error + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - - Invalid name. Must be a valid file name + + Previous - - A tweak by that name exists + + Next - - Create Tweak + + Close
    @@ -4203,18 +3947,12 @@ p, li { white-space: pre-wrap; } ModInfoRegular - - - failed to write %1/meta.ini: error %2 - - - - + %1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -4698,6 +4436,46 @@ p, li { white-space: pre-wrap; } + + NexusTab + + + Current Version: %1 + + + + + No update available + + + + + Tracked + + + + + Untracked + + + + + + <div style="text-align: center;"> + <p>This mod does not have a valid Nexus ID. You can add a custom web + page for it in the "Custom URL" box below.</p> + </div> + + + + + NoConflictListModel + + + File + + + OrganizerCore @@ -4706,6 +4484,11 @@ p, li { white-space: pre-wrap; } Failed to write settings + + + An error occurred trying to update MO settings to %1: %2 + + File is write protected @@ -4721,6 +4504,11 @@ p, li { white-space: pre-wrap; } Unknown error %1 + + + An error occurred trying to write back MO settings to %1: %2 + + @@ -4776,36 +4564,6 @@ p, li { white-space: pre-wrap; } The mod was not installed completely. - - - Executable not found: %1 - - - - - Start Steam? - - - - - Steam is required to be running already to correctly start the game. Should MO try to start steam now? - - - - - Steam: Access Denied - - - - - An error occured trying to update MO settings to %1: %2 - - - - - An error occured trying to write back MO settings to %1: %2 - - file not found: %1 @@ -4836,9 +4594,29 @@ p, li { white-space: pre-wrap; } Failed to generate preview for %1 + + + Executable not found: %1 + + + + + Start Steam? + + + + + Steam is required to be running already to correctly start the game. Should MO try to start steam now? + + + + + Steam: Access Denied + + - MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely neccessary. + MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely necessary. Restart MO as administrator? @@ -4888,81 +4666,99 @@ Continue launching %1? - + + You need to be logged in with Nexus + + + + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. + + OverwriteConflictListModel + + + File + + + + + Overwritten Mods + + + OverwriteInfoDialog @@ -5021,13 +4817,13 @@ Continue? - Are sure you want to delete "%1"? + Are you sure you want to delete "%1"? - Are sure you want to delete the selected files? + Are you sure you want to delete the selected files? @@ -5042,6 +4838,19 @@ Continue? + + OverwrittenConflictListModel + + + File + + + + + Providing Mod + + + PluginContainer @@ -5624,29 +5433,29 @@ p, li { white-space: pre-wrap; } - + removal of "%1" failed: %2 - + removal of "%1" failed - + "%1" doesn't exist (remove) - - + + failed to create directory "%1" - - + + failed to copy "%1" to "%2" @@ -5709,8 +5518,39 @@ p, li { white-space: pre-wrap; } - - invalid game type %1 + + The hidden file "%1" already exists. Replace it? + + + + + The visible file "%1" already exists. Replace it? + + + + + Replace file? + + + + + + File operation failed + + + + + Failed to remove "%1". Maybe you lack the required file permissions? + + + + + failed to rename %1 to %2 + + + + + Filter @@ -5752,8 +5592,7 @@ p, li { white-space: pre-wrap; } - Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched. - Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untouched. + Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched. @@ -5791,7 +5630,7 @@ If the folder was still in use, restart MO and try again. - + Canceled @@ -5856,18 +5695,18 @@ If the folder was still in use, restart MO and try again. - + failed to create %1 - + Data directory created - + New data directory created at %1. If you don't want to store a lot of data there, reconfigure the storage directories via settings. @@ -5976,43 +5815,43 @@ If the folder was still in use, restart MO and try again. - + %1 not identified in "%2". The directory is required to contain the game binary. - - No game identified in "%1". The directory is required to contain the game binary. + + No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul> - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - - + + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance @@ -6022,45 +5861,45 @@ If the folder was still in use, restart MO and try again. - - + + <Manage...> - + failed to parse profile %1: %2 - - The hidden file "%1" already exists. Replace it? + + File Exists - - The visible file "%1" already exists. Replace it? + + A file with that name exists, please enter a new one - - Replace file? + + + Failed to move file - - - File operation failed + + Failed to create directory "optional" - - Failed to remove "%1". Maybe you lack the required file permissions? + + Save changes? - - failed to rename %1 to %2 + + Save changes to "%1"? @@ -6120,46 +5959,39 @@ If the folder was still in use, restart MO and try again. - + Script Extender - + Proxy DLL - + failed to spawn "%1" - + Elevation required - + This process requires elevation to run. -This is a potential security risk so I highly advice you to investigate if -"%1" -can be installed to work without elevation. - -Restart Mod Organizer as an elevated process? -You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually. - This process requires elevation to run. This is a potential security risk so I highly advise you to investigate if "%1" can be installed to work without elevation. Restart Mod Organizer as an elevated process? -You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually. +You will be asked if you want to allow helper.exe to make changes to the system. You will need to relaunch the process above manually. - - + + failed to spawn "%1": %2 @@ -6173,6 +6005,21 @@ You will be asked if you want to allow helper.exe to make changes to the system. Loading... + + + &Save + + + + + &Word wrap + + + + + &Open in Explorer + + QueryOverwriteDialog @@ -6348,39 +6195,39 @@ Select Show Details option to see the full change-log. Settings - + Failed - - - attempt to store setting for unknown plugin "%1" - - - - - Error + + Failed to start the helper application - - Failed to start the helper application + + + attempt to store setting for unknown plugin "%1" - + Restart Mod Organizer? - + In order to finish configuration changes, MO must be restarted. Restart it now? - + + Error + + + + Failed to create "%1", you may not have the necessary permission. path remains unchanged. @@ -6623,8 +6470,7 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - Important: All directories have to be writeable! - Important: All directories have to be writable! + Important: All directories have to be writable! @@ -7066,7 +6912,7 @@ programs you are intentionally running. Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. @@ -7168,40 +7014,6 @@ Example: - - SimpleInstallDialog - - - Quick Install - - - - - Name - - - - - - Opens a Dialog that allows custom modifications. - - - - - Manual - - - - - OK - - - - - Cancel - - - SingleInstance @@ -7454,8 +7266,7 @@ On Windows XP: - <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwrtten by another. You could as well disable it. - <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwritten by another. You could as well disable it. + <img src="qrc:///MO/gui/emblem_conflict_redundant" /> indicates that the mod is completely overwritten by another. You could as well disable it. @@ -7661,8 +7472,7 @@ There IS a notification now but you may want to hold off on clearing it until af - A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the indiviual mod. To do so, right-click the mod and select "Information". - A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the individual mod. To do so, right-click the mod and select "Information". + A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the individual mod. To do so, right-click the mod and select "Information". @@ -7696,8 +7506,7 @@ It's important you always start the game from inside MO, otherwise the mods - We may re-visit this screen in later tutorials. - We may revisit this screen in later tutorials. + We may revisit this screen in later tutorials. -- cgit v1.3.1 From 41c9dd9b722b5792e4540a830f5ae55b130fa7f5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 3 Jul 2019 17:26:08 -0400 Subject: preloadDll() would always think dlls were already loaded --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index c6c87a64..b7176ee1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -432,7 +432,7 @@ void preloadDll(const QString& filename) { qDebug().nospace() << "preloading " << filename; - if (!GetModuleHandleW(filename.toStdWString().c_str())) { + if (GetModuleHandleW(filename.toStdWString().c_str())) { // already loaded, this can happen when "restarting" MO by switching // instances, for example return; -- cgit v1.3.1 From 5a0b6a7cf65a9f3a88f05b5985ee134ee88b2b7f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 3 Jul 2019 19:08:57 -0400 Subject: fixed tab order not being saved --- src/modinfodialog.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 16690019..47ac84be 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -353,7 +353,11 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) // save the current order (if necessary) because some tabs will be removed and // others added - saveTabOrder(Settings::instance()); + if (!firstTime) { + // but don't do it the first time visibility is set because the tabs are + // in the default order, which will clobber the current settings + saveTabOrder(Settings::instance()); + } // remember selection, if any auto sel = ModInfoTabIDs::None; -- cgit v1.3.1 From dfd34bf9c0ad055e76c1b6f272c21eb0d79536ce Mon Sep 17 00:00:00 2001 From: Silarn Date: Wed, 3 Jul 2019 19:12:54 -0500 Subject: Update openssl library names --- src/main.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index b7176ee1..4a2f6035 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -459,8 +459,13 @@ void preloadDll(const QString& filename) void preloadSsl() { - preloadDll("libeay32.dll"); - preloadDll("ssleay32.dll"); +#if Q_PROCESSOR_WORDSIZE == 8 + preloadDll("libcrypto-1_1-x64.dll"); + preloadDll("libssl-1_1-x64.dll"); +#elif Q_PROCESSOR_WORDSIZE == 4 + preloadDll("libcrypto-1_1.dll"); + preloadDll("libssl-1_1.dll"); +#endif } static QString getVersionDisplayString() -- cgit v1.3.1 From 0596a7199094cc8d103dfe2cfdcd20046c04e85d Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 4 Jul 2019 23:14:15 -0500 Subject: Various fixes and updates to the tutorial system * Fix problem with main tutorial advancing past mod activation * Add various missing overlays for new and updated features * Restore BSA tab * Fix problem with positioning action button overlays (requires uibase) --- src/modlist.cpp | 3 +++ src/modlist.h | 5 +++++ src/tutorials/tutorial_firststeps_main.js | 4 ++-- src/tutorials/tutorial_primer_main.js | 32 ++++++++++++++++++++++++------- src/tutorials/tutorials.js | 3 ++- 5 files changed, 37 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/modlist.cpp b/src/modlist.cpp index e2398cd6..7b71355c 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -561,6 +561,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) m_Modified = true; m_LastCheck.restart(); emit modlistChanged(index, role); + emit tutorialModlistUpdate(); } result = true; emit dataChanged(index, index); @@ -596,6 +597,7 @@ bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) if (ok) { info->setNexusID(newID); emit modlistChanged(index, role); + emit tutorialModlistUpdate(); result = true; } else { result = false; @@ -1375,6 +1377,7 @@ bool ModList::toggleSelection(QAbstractItemView *itemView) m_Profile->setModsEnabled(modsToEnable, modsToDisable); emit modlistChanged(dirtyMods, 0); + emit tutorialModlistUpdate(); m_Modified = true; m_LastCheck.restart(); diff --git a/src/modlist.h b/src/modlist.h index 402d662f..5ce32f6e 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -249,6 +249,11 @@ signals: **/ void modlistChanged(const QModelIndexList &indicies, int role); + /** + * @brief QML seems to handle overloaded signals poorly - create unique signal for tutorials + */ + void tutorialModlistUpdate(); + /** * @brief emitted to have all selected mods deleted */ diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js index 35d1aa11..45e7e649 100644 --- a/src/tutorials/tutorial_firststeps_main.js +++ b/src/tutorials/tutorial_firststeps_main.js @@ -108,11 +108,11 @@ function getTutorialSteps() tutorial.text = qsTr("Install a few more mods if you want, then enable mods by checking them in the left pane. " + "Mods that aren't enabled have no effect on the game whatsoever. ") highlightItem("modList", true) - modList.modlist_changed.connect(nextStep) + modList.tutorialModlistUpdate.connect(nextStep) }, function() { - modList.modlist_changed.disconnect(nextStep) + modList.tutorialModlistUpdate.disconnect(nextStep) unhighlight() tutorial.text = qsTr("For some mods, enabling it on the left pane is all you have to do...") waitForClick() diff --git a/src/tutorials/tutorial_primer_main.js b/src/tutorials/tutorial_primer_main.js index 5af99237..dd305c0c 100644 --- a/src/tutorials/tutorial_primer_main.js +++ b/src/tutorials/tutorial_primer_main.js @@ -42,6 +42,7 @@ function finishCreation(component, widgetName, explanation, maxheight, clickable function tooltipAction(actionName, explanation, maxheight, clickable) { var rect = tutorialControl.getActionRect(actionName) + var offsetRect = tutorialControl.getMenuRect(actionName) var component = Qt.createComponent("TooltipArea.qml") if (typeof clickable === 'undefined') { clickable = false @@ -51,7 +52,7 @@ function tooltipAction(actionName, explanation, maxheight, clickable) { } var obj = component.createObject(tutToplevel, { "x" : rect.x, - "y" : rect.y, + "y" : rect.y + offsetRect.height, "width" : rect.width, "height" : maxheight }) @@ -70,6 +71,11 @@ function setupTooptips() { tooltipWidget("modList", qsTr("This window shows all the mods that are installed. The column headers can be used for sorting. Only checked mods are active in the current profile.")) tooltipWidget("profileBox", qsTr("Each profile is a separate set of enabled mods and ini settings.")) + tooltipWidget("listOptionsBtn", qsTr("Perform various actions on your mod list, such as refreshing data and checking for mod updates.")) + tooltipWidget("openFolderMenu", qsTr("Quick access to various directories, such as your MO2 mods, profiles, saves, and your active game location.")) + tooltipWidget("restoreModsButton", qsTr("Restore a mod list backup.")) + tooltipWidget("saveModsButton", qsTr("Create a backup of your current mod list.")) + tooltipWidget("activeModsCounter", qsTr("Running counter of your active mods. Hover to see a more detailed breakdown.")) tooltipWidget("groupCombo", qsTr("The dropdown allows various ways of grouping the mods shown in the mod list.")) tooltipWidget("displayCategoriesBtn", qsTr("Show/hide the category pane.")) tooltipWidget("modFilterEdit", qsTr("Quickly filter the mod list as you type.")) @@ -79,27 +85,39 @@ function setupTooptips() { tooltipWidget("startButton", qsTr("When this button is clicked, Mod Organizer creates a virtual directory structure then runs the program selected to the left.")) tooltipWidget("linkButton", qsTr("Will create a shortcut for quick access. The shortcut can be placed in the toolbar at the top, in the Start Menu or on the Windows Desktop.")) tooltipWidget("logList", qsTr("Log messages produced by MO. Please note that messages with a light bulb usually don't require your attention.")) - + tooltipWidget("apistats", qsTr("Indicator of your current NexusMods API request limits.")) + + tooltipAction("actionChange_Game", qsTr("Change/manage MO2 instances or switch to portable mode.")) + tooltipAction("actionInstallMod", qsTr("Browse to and manually install a mod from an archive on your computer.")) + tooltipAction("actionNexus", qsTr("Automatically open NexusMods to browse and install mods via the API.")) + tooltipAction("actionAdd_Profile", qsTr("Manage your MO2 profiles.")) + tooltipAction("actionModify_Executables", qsTr("Open the executable editor to add and modify applications you wish to run with MO2.")) + tooltipAction("actionTool", qsTr("Select from a collection of additional tools, such as an INI editor, integrated FNIS updater, and more.")) tooltipAction("actionSettings", qsTr("Configure Mod Organizer.")) + tooltipAction("actionEndorseMO", qsTr("See the status of and/or endorse MO2 on NexusMods.")) tooltipAction("actionNotifications", qsTr("Notifications about the current setup.")) tooltipAction("actionUpdate", qsTr("Activates if there is an update for MO. Please note that if, for any reason, MO can't communicate with NMM, this will not work either.")) + tooltipAction("actionHelp", qsTr("Access more information about MO2, including these tutorials, a link to the development discord, information about the devs and dependencies.")) switch (manager.findControl("tabWidget").currentIndex) { case 0: tooltipWidget("espList", qsTr("Plugins (esp/esm/esl files) of the mods in the current profile. They need to be checked to be loaded.")) tooltipWidget("bossButton", qsTr("Automatically sort plugins using the bundled LOOT application.")) + tooltipWidget("restoreButton", qsTr("Restore a backup of your plugin list order.")) + tooltipWidget("saveButton", qsTr("Save a backup of your plugin list order.")) + tooltipWidget("activePluginsCounter", qsTr("Counter of your total active plugins. Hover to see a breakdown of plugin types.")) tooltipWidget("espFilterEdit", qsTr("Quickly filter plugin list as you type.")) break - // case 1: - // tooltipWidget("bsaList", qsTr("All the asset archives (.bsa files) for all active mods.")) - // break case 1: - tooltipWidget("dataTree", qsTr("The directory tree and all files that the program will see.")) + tooltipWidget("bsaList", qsTr("All the asset archives (.bsa files) for all active mods.")) break case 2: - tooltipWidget("savegameList", qsTr("Save game browser. Shows all the saves for the current profile and whether or not the current mod-load status is correct.")) + tooltipWidget("dataTree", qsTr("The directory tree and all files that the program will see.")) break case 3: + tooltipWidget("savegameList", qsTr("Save game browser. Shows all the saves for the current profile and whether or not the current mod-load status is correct.")) + break + case 4: tooltipWidget("downloadView", qsTr("Shows the mods that have been downloaded and if they’ve been installed.")) break } diff --git a/src/tutorials/tutorials.js b/src/tutorials/tutorials.js index eb23eab7..cdb16d59 100644 --- a/src/tutorials/tutorials.js +++ b/src/tutorials/tutorials.js @@ -18,8 +18,9 @@ function highlightItem(widgetName, click) { function highlightAction(actionName, click) { var rect = tutorialControl.getActionRect(actionName) + var offsetRect = tutorialControl.getMenuRect(actionName) highlight.x = rect.x - 1 - highlight.y = rect.y - 1 + highlight.y = rect.y + offsetRect.height highlight.width = rect.width + 2 highlight.height = rect.height + 2 if (click) { -- cgit v1.3.1 From 1a280f37a7c04e016ab9b73ccd1643762a2060c2 Mon Sep 17 00:00:00 2001 From: Silarn Date: Thu, 4 Jul 2019 23:25:14 -0500 Subject: Various translation updates and additions --- src/organizer_en.ts | 208 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 148 insertions(+), 60 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 7e824cc2..4e6575ea 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -17,7 +17,6 @@ <html><head/><body><p>Source code can be found at <a href="https://github.com/ModOrganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> - <html><head/><body><p>Source code can be found at <a href="https://github.com/Modorganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> @@ -33,7 +32,6 @@ Lead Developers && Maintainers - Lead Developers/ Maintainers @@ -71,6 +69,11 @@ Faron (German) + + + yohru (Japanese) + + Mordan (Greek) @@ -111,11 +114,6 @@ Other Supporters && Contributors - - - yohru (Japanese) - - Tannin (Original Creator) @@ -1573,7 +1571,7 @@ This is likely due to a corrupted or incompatible download or unrecognized archi MOBase::TutorialControl - + Tutorial failed to start, please check "mo_interface.log" for details. @@ -4163,123 +4161,123 @@ p, li { white-space: pre-wrap; } - + drag&drop failed: %1 - + Confirm - + Are you sure you want to remove "%1"? - + Flags - + Content - + Mod Name - + Version - + Priority - + Category - + Source Game - + Nexus ID - + Installation - + Notes - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + The source game which was the origin of this mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Depicts the content of the mod:<br><table cellspacing=7><tr><td><img src=":/MO/gui/content/plugin" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr><tr><td><img src=":/MO/gui/content/interface" width=32/></td><td>Interface</td></tr><tr><td><img src=":/MO/gui/content/mesh" width=32/></td><td>Meshes</td></tr><tr><td><img src=":/MO/gui/content/bsa" width=32/></td><td>BSA</td></tr><tr><td><img src=":/MO/gui/content/texture" width=32/></td><td>Textures</td></tr><tr><td><img src=":/MO/gui/content/sound" width=32/></td><td>Sounds</td></tr><tr><td><img src=":/MO/gui/content/music" width=32/></td><td>Music</td></tr><tr><td><img src=":/MO/gui/content/string" width=32/></td><td>Strings</td></tr><tr><td><img src=":/MO/gui/content/script" width=32/></td><td>Scripts (Papyrus)</td></tr><tr><td><img src=":/MO/gui/content/skse" width=32/></td><td>Script Extender plugins</td></tr><tr><td><img src=":/MO/gui/content/skyproc" width=32/></td><td>SkyProc Patcher</td></tr><tr><td><img src=":/MO/gui/content/menu" width=32/></td><td>Mod Configuration Menu</td></tr><tr><td><img src=":/MO/gui/content/inifile" width=32/></td><td>INI files</td></tr><tr><td><img src=":/MO/gui/content/modgroup" width=32/></td><td>ModGroup files</td></tr></table> - + Time this mod was installed - + User notes about the mod @@ -5825,33 +5823,33 @@ If the folder was still in use, restart MO and try again. - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance @@ -7532,107 +7530,197 @@ Please open the "Nexus"-tab tutorial_primer_main - + This window shows all the mods that are installed. The column headers can be used for sorting. Only checked mods are active in the current profile. - + Each profile is a separate set of enabled mods and ini settings. - + + Perform various actions on your mod list, such as refreshing data and checking for mod updates. + + + + + Quick access to various directories, such as your MO2 mods, profiles, saves, and your active game location. + + + + + Restore a mod list backup. + + + + + Create a backup of your current mod list. + + + + + Running counter of your active mods. Hover to see a more detailed breakdown. + + + + The dropdown allows various ways of grouping the mods shown in the mod list. - + Show/hide the category pane. - + Quickly filter the mod list as you type. - + Switch between information views. - + This shows mod categories and some meta categories (in angle-brackets). Select some to filter the mod list. For example select "<Checked>" to show only active mods. - + Customizable list for choosing the program to run. - + When this button is clicked, Mod Organizer creates a virtual directory structure then runs the program selected to the left. - + Will create a shortcut for quick access. The shortcut can be placed in the toolbar at the top, in the Start Menu or on the Windows Desktop. - + Log messages produced by MO. Please note that messages with a light bulb usually don't require your attention. - + + Indicator of your current NexusMods API request limits. + + + + + Change/manage MO2 instances or switch to portable mode. + + + + + Browse to and manually install a mod from an archive on your computer. + + + + + Automatically open NexusMods to browse and install mods via the API. + + + + + Manage your MO2 profiles. + + + + + Open the executable editor to add and modify applications you wish to run with MO2. + + + + + Select from a collection of additional tools, such as an INI editor, integrated FNIS updater, and more. + + + + Configure Mod Organizer. - + + See the status of and/or endorse MO2 on NexusMods. + + + + Notifications about the current setup. - + Activates if there is an update for MO. Please note that if, for any reason, MO can't communicate with NMM, this will not work either. - + + Access more information about MO2, including these tutorials, a link to the development discord, information about the devs and dependencies. + + + + Plugins (esp/esm/esl files) of the mods in the current profile. They need to be checked to be loaded. - + Automatically sort plugins using the bundled LOOT application. - + + Restore a backup of your plugin list order. + + + + + Save a backup of your plugin list order. + + + + + Counter of your total active plugins. Hover to see a breakdown of plugin types. + + + + Quickly filter plugin list as you type. - + + All the asset archives (.bsa files) for all active mods. + + + + The directory tree and all files that the program will see. - + Save game browser. Shows all the saves for the current profile and whether or not the current mod-load status is correct. - + Shows the mods that have been downloaded and if they’ve been installed. - + Click to quit -- cgit v1.3.1 From 6b8105883190b9d36d27875e4a1914ec8aa670e8 Mon Sep 17 00:00:00 2001 From: Al Date: Fri, 5 Jul 2019 16:28:59 +0200 Subject: Added "Downloads" since ex NMM users though it mean the installed Mods. It also gives more difference with "Hide" so that it's less likely to use one instead of the other. --- src/downloadlistwidget.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index be3adc5f..c75ecdd2 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -235,9 +235,9 @@ void DownloadListWidget::onCustomContextMenu(const QPoint &point) menu.addSeparator(); } - menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); - menu.addAction(tr("Delete Uninstalled..."), this, SLOT(issueDeleteUninstalled())); - menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); + menu.addAction(tr("Delete Installed Downloads..."), this, SLOT(issueDeleteCompleted())); + menu.addAction(tr("Delete Uninstalled Downloads..."), this, SLOT(issueDeleteUninstalled())); + menu.addAction(tr("Delete All Downloads..."), this, SLOT(issueDeleteAll())); menu.addSeparator(); if (!hidden) { -- cgit v1.3.1 From 9f4a9c913fc6f79d5a754bc872443c1c057096e7 Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 5 Jul 2019 16:19:52 -0500 Subject: Rework methods to detect the current tab to use the object name --- src/organizer_en.ts | 693 +++++++++++---------- src/tutorials/tutorial_conflictresolution_main.js | 4 +- .../tutorial_conflictresolution_modinfo.js | 2 +- src/tutorials/tutorial_firststeps_main.js | 2 +- src/tutorials/tutorial_firststeps_settings.js | 10 +- src/tutorials/tutorial_primer_main.js | 12 +- 6 files changed, 365 insertions(+), 358 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 4e6575ea..341bc1be 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -74,6 +74,11 @@ yohru (Japanese) + + + yohru (Japanese) + + Mordan (Greek) @@ -173,17 +178,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -201,12 +206,12 @@ p, li { white-space: pre-wrap; } - + new - + failed to start download @@ -289,32 +294,32 @@ p, li { white-space: pre-wrap; } ConflictsTab - + &Hide - + &Unhide - + &Open/Execute - + &Preview - + Open in &Explorer - + &Go to... @@ -845,114 +850,114 @@ File %3: %4 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1661,7 +1666,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1837,8 +1842,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2117,7 +2122,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2207,869 +2212,869 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3077,12 +3082,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3090,22 +3095,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3113,348 +3118,348 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occurred - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4306,12 +4311,12 @@ p, li { white-space: pre-wrap; } MyFileSystemModel - + Overwrites - + not implemented @@ -4469,7 +4474,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4747,12 +4752,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -4775,63 +4780,63 @@ Continue? - + &Delete - + &Rename - + &Open - + &New Folder - + mod not found: %1 - + Failed to delete "%1" - - - - + + + + Confirm - - + + Are you sure you want to delete "%1"? - - + + Are you sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" @@ -4839,12 +4844,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -5838,34 +5843,34 @@ If the folder was still in use, restart MO and try again. - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 diff --git a/src/tutorials/tutorial_conflictresolution_main.js b/src/tutorials/tutorial_conflictresolution_main.js index 404afe5d..a2aa278d 100644 --- a/src/tutorials/tutorial_conflictresolution_main.js +++ b/src/tutorials/tutorial_conflictresolution_main.js @@ -65,7 +65,7 @@ function getTutorialSteps() { }, function() { tutorial.text = qsTr("Option A: Switch to the \"Data\"-tab if necessary") - if (!tutorialControl.waitForTabOpen("tabWidget", 2)) { + if (!tutorialControl.waitForTabOpen("tabWidget", "dataTab")) { highlightItem("tabWidget", false) waitForClick() } else { @@ -107,7 +107,7 @@ function getTutorialSteps() { function() { tutorial.text = qsTr("Please open the \"Plugins\"-tab...") highlightItem("tabWidget", true) - if (!tutorialControl.waitForTabOpen("tabWidget", 0)) { + if (!tutorialControl.waitForTabOpen("tabWidget", "espTab")) { nextStep() } }, diff --git a/src/tutorials/tutorial_conflictresolution_modinfo.js b/src/tutorials/tutorial_conflictresolution_modinfo.js index b5ef461e..04691ef6 100644 --- a/src/tutorials/tutorial_conflictresolution_modinfo.js +++ b/src/tutorials/tutorial_conflictresolution_modinfo.js @@ -3,7 +3,7 @@ function getTutorialSteps() { function() { tutorial.text = qsTr("Please switch to the \"Conflicts\"-Tab.") highlightItem("tabWidget", true) - if (!tutorialControl.waitForTabOpen("tabWidget", 4)) { + if (!tutorialControl.waitForTabOpen("tabWidget", "tabConflicts")) { nextStep() } }, diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js index 45e7e649..47545d24 100644 --- a/src/tutorials/tutorial_firststeps_main.js +++ b/src/tutorials/tutorial_firststeps_main.js @@ -122,7 +122,7 @@ function getTutorialSteps() tutorial.text = qsTr("...but most contain plugins. These are plugins for the game and are required " +"to add stuff to the game (new weapons, armors, quests, areas, ...). " +"Please open the \"Plugins\"-tab to get a list of plugins.") - if (tutorialControl.waitForTabOpen("tabWidget", 0)) { + if (tutorialControl.waitForTabOpen("tabWidget", "espTab")) { highlightItem("tabWidget", true) } else { waitForClick() diff --git a/src/tutorials/tutorial_firststeps_settings.js b/src/tutorials/tutorial_firststeps_settings.js index 1dd77d2e..ed49d97f 100644 --- a/src/tutorials/tutorial_firststeps_settings.js +++ b/src/tutorials/tutorial_firststeps_settings.js @@ -4,7 +4,7 @@ function getTutorialSteps() function() { highlightItem("tabWidget", true) tutorial.text = qsTr("You can use your regular browser to download from Nexus.\nPlease open the \"Nexus\"-tab") - tutorialControl.waitForTabOpen("tabWidget", 2) + tutorialControl.waitForTabOpen("tabWidget", "tabNexus") }, function() { @@ -16,9 +16,11 @@ function getTutorialSteps() function() { highlightItem("nexusBox", false) - tutorial.text = qsTr("You can also store your Nexus-credentials " - +"here for automatic login. The password is " - +"stored unencrypted on your disk!") + tutorial.text = qsTr("Use this interface to obtain an API key from NexusMods." + +"This is used for all API connections - downloads, updates" + +"etc. MO2 uses the Windows Credential Manager to store" + +"this data securely. If the SSO page on Nexus is failing," + +"use the manual entry and copy the API key from your profile.") waitForClick() } ] diff --git a/src/tutorials/tutorial_primer_main.js b/src/tutorials/tutorial_primer_main.js index dd305c0c..7972cca4 100644 --- a/src/tutorials/tutorial_primer_main.js +++ b/src/tutorials/tutorial_primer_main.js @@ -99,8 +99,8 @@ function setupTooptips() { tooltipAction("actionUpdate", qsTr("Activates if there is an update for MO. Please note that if, for any reason, MO can't communicate with NMM, this will not work either.")) tooltipAction("actionHelp", qsTr("Access more information about MO2, including these tutorials, a link to the development discord, information about the devs and dependencies.")) - switch (manager.findControl("tabWidget").currentIndex) { - case 0: + switch (tutorialControl.getTabName("tabWidget")) { + case "espTab": tooltipWidget("espList", qsTr("Plugins (esp/esm/esl files) of the mods in the current profile. They need to be checked to be loaded.")) tooltipWidget("bossButton", qsTr("Automatically sort plugins using the bundled LOOT application.")) tooltipWidget("restoreButton", qsTr("Restore a backup of your plugin list order.")) @@ -108,16 +108,16 @@ function setupTooptips() { tooltipWidget("activePluginsCounter", qsTr("Counter of your total active plugins. Hover to see a breakdown of plugin types.")) tooltipWidget("espFilterEdit", qsTr("Quickly filter plugin list as you type.")) break - case 1: + case "bsaTab": tooltipWidget("bsaList", qsTr("All the asset archives (.bsa files) for all active mods.")) break - case 2: + case "dataTab": tooltipWidget("dataTree", qsTr("The directory tree and all files that the program will see.")) break - case 3: + case "savesTab": tooltipWidget("savegameList", qsTr("Save game browser. Shows all the saves for the current profile and whether or not the current mod-load status is correct.")) break - case 4: + case "downloadTab": tooltipWidget("downloadView", qsTr("Shows the mods that have been downloaded and if they’ve been installed.")) break } -- cgit v1.3.1 From 7e760c700a89114909e607bd94a7c4ea5ed8319c Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 5 Jul 2019 16:28:01 -0500 Subject: Update translations --- src/organizer_en.ts | 704 ++++++++++++++++++++++++++-------------------------- 1 file changed, 351 insertions(+), 353 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 341bc1be..2a47fc18 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -74,11 +74,6 @@ yohru (Japanese) - - - yohru (Japanese) - - Mordan (Greek) @@ -178,17 +173,17 @@ p, li { white-space: pre-wrap; } AdvancedConflictListModel - + Overwrites - + File - + Overwritten By @@ -206,12 +201,12 @@ p, li { white-space: pre-wrap; } - + new - + failed to start download @@ -294,32 +289,32 @@ p, li { white-space: pre-wrap; } ConflictsTab - + &Hide - + &Unhide - + &Open/Execute - + &Preview - + Open in &Explorer - + &Go to... @@ -547,17 +542,20 @@ p, li { white-space: pre-wrap; } - Delete Installed... + Delete Installed Downloads... + Delete Installed... - Delete Uninstalled... + Delete Uninstalled Downloads... + Delete Uninstalled... - Delete All... + Delete All Downloads... + Delete All... @@ -850,114 +848,114 @@ File %3: %4 - + file size: invalid download index %1 - + progress: invalid download index %1 - + state: invalid download index %1 - + infocomplete: invalid download index %1 - - + + mod id: invalid download index %1 - + ishidden: invalid download index %1 - + file info: invalid download index %1 - + mark installed: invalid download index %1 - + mark uninstalled: invalid download index %1 - + Memory allocation error (in processing progress event). - + Memory allocation error (in processing downloaded data). - + Information updated - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - + No download server available. Please try again later. - + Failed to request file info from nexus: %1 - + Warning: Content type is: %1 - + Download header content length: %1 downloaded file size: %2 - + Download failed: %1 (%2) - + We were unable to download the file due to errors after four retries. There may be an issue with the Nexus servers. - + failed to re-open %1 - + Unable to write download to drive (return %1). Check the drive's available storage. @@ -1666,7 +1664,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1842,8 +1840,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2122,7 +2120,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2212,869 +2210,869 @@ p, li { white-space: pre-wrap; } - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3082,12 +3080,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3095,22 +3093,22 @@ You can also use online editors and converters instead. - + failed to remove %1 - + failed to create %1 - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3118,348 +3116,348 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occurred - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4311,12 +4309,12 @@ p, li { white-space: pre-wrap; } MyFileSystemModel - + Overwrites - + not implemented @@ -4474,7 +4472,7 @@ p, li { white-space: pre-wrap; } NoConflictListModel - + File @@ -4752,12 +4750,12 @@ Continue? OverwriteConflictListModel - + File - + Overwritten Mods @@ -4780,63 +4778,63 @@ Continue? - + &Delete - + &Rename - + &Open - + &New Folder - + mod not found: %1 - + Failed to delete "%1" - - - - + + + + Confirm - - + + Are you sure you want to delete "%1"? - - + + Are you sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" @@ -4844,12 +4842,12 @@ Continue? OverwrittenConflictListModel - + File - + Providing Mod @@ -5843,34 +5841,34 @@ If the folder was still in use, restart MO and try again. - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -7528,7 +7526,7 @@ Please open the "Nexus"-tab - You can also store your Nexus-credentials here for automatic login. The password is stored unencrypted on your disk! + Use this interface to obtain an API key from NexusMods.This is used for all API connections - downloads, updatesetc. MO2 uses the Windows Credential Manager to storethis data securely. If the SSO page on Nexus is failing,use the manual entry and copy the API key from your profile. -- cgit v1.3.1 From e8d14d1ea527608bdaae4fe8f83c949082067b16 Mon Sep 17 00:00:00 2001 From: Silarn Date: Fri, 5 Jul 2019 18:39:42 -0500 Subject: Fix the help button tutorial step and a typo --- src/tutorials/tutorial_firststeps_main.js | 5 +++-- src/tutorials/tutorial_firststeps_settings.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js index 47545d24..96bfd0e7 100644 --- a/src/tutorials/tutorial_firststeps_main.js +++ b/src/tutorials/tutorial_firststeps_main.js @@ -27,8 +27,8 @@ function getTutorialSteps() function() { console.log("next") tutorial.text = qsTr("This button provides multiple sources of information and further tutorials.") - if (tutorialControl.waitForButton("actionHelp")) { - highlightItem("actionHelp", true) + if (tutorialControl.waitForAction("actionHelp")) { + highlightAction("actionHelp", true) } else { console.error("help button broken") waitForClick() @@ -36,6 +36,7 @@ function getTutorialSteps() }, function() { + unhighlight() tutorial.text = qsTr("Finally there are tooltips on almost every part of Mod Organizer. If there is a control " + "in MO you don't understand, please try hovering over it to get a short description or " + "use \"Help on UI\" from the help menu to get a longer explanation") diff --git a/src/tutorials/tutorial_firststeps_settings.js b/src/tutorials/tutorial_firststeps_settings.js index ed49d97f..792712be 100644 --- a/src/tutorials/tutorial_firststeps_settings.js +++ b/src/tutorials/tutorial_firststeps_settings.js @@ -4,7 +4,7 @@ function getTutorialSteps() function() { highlightItem("tabWidget", true) tutorial.text = qsTr("You can use your regular browser to download from Nexus.\nPlease open the \"Nexus\"-tab") - tutorialControl.waitForTabOpen("tabWidget", "tabNexus") + tutorialControl.waitForTabOpen("tabWidget", "nexusTab") }, function() { -- 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') 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 From f7c844fcf7684cbde0a3290b1950c52f88236be3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 19:23:52 -0400 Subject: put the error message in the ShellLinkException instead added more debug logging when creating and deleting shortcuts --- src/shared/util.cpp | 111 ++++++++++++++++++++++++++++++++++++++-------------- src/shared/util.h | 13 ++++++ 2 files changed, 95 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 2bf1dba6..17df3b92 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -314,7 +314,22 @@ template using COMPtr = std::unique_ptr; -class ShellLinkException {}; +class ShellLinkException +{ +public: + ShellLinkException(QString s) + : m_what(std::move(s)) + { + } + + const QString& what() const + { + return m_what; + } + +private: + QString m_what; +}; // just a wrapper around IShellLink operations that throws ShellLinkException // on errors @@ -331,8 +346,7 @@ public: void setPath(const QString& s) { if (s.isEmpty()) { - critical() << "path cannot be empty"; - throw ShellLinkException(); + throw ShellLinkException("path cannot be empty"); } const auto r = m_link->SetPath(s.toStdWString().c_str()); @@ -385,16 +399,12 @@ 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(); + throw ShellLinkException(QString("%1, %2") + .arg(s) + .arg(formatSystemMessageQ(r))); } } @@ -409,8 +419,7 @@ private: throwOnFail(r, "failed to create IShellLink instance"); if (!link) { - critical() << "creating IShellLink worked, but pointer is null"; - throw ShellLinkException(); + throw ShellLinkException("creating IShellLink worked, pointer is null"); } return COMPtr(static_cast(link)); @@ -424,8 +433,7 @@ private: throwOnFail(r, "failed to get IPersistFile interface"); if (!file) { - critical() << "querying IPersistFile worked, but pointer is null"; - throw ShellLinkException(); + throw ShellLinkException("querying IPersistFile worked, pointer is null"); } return COMPtr(static_cast(file)); @@ -515,16 +523,27 @@ bool Shortcut::toggle(Locations loc) bool Shortcut::add(Locations loc) { - const auto path = shortcutPath(loc); - if (path.isEmpty()) { + debug() + << "adding shortcut to " << toString(loc) << ":\n" + << " . name: '" << m_name << "'\n" + << " . target: '" << m_target << "'\n" + << " . arguments: '" << m_arguments << "'\n" + << " . description: '" << m_description << "'\n" + << " . icon: '" << m_icon << "' @ " << m_iconIndex << "\n" + << " . working directory: '" << m_workingDirectory << "'"; + + if (m_target.isEmpty()) { + critical() << "target is empty"; return false; } - if (m_target.isEmpty()) { - qCritical() << "system shortcut: target is empty"; + const auto path = shortcutPath(loc); + if (path.isEmpty()) { return false; } + debug() << "shorcut file will be saved at '" << path << "'"; + try { ShellLinkWrapper link; @@ -539,8 +558,9 @@ bool Shortcut::add(Locations loc) return true; } - catch(ShellLinkException&) + catch(ShellLinkException& e) { + critical() << e.what() << "\nshortcut file was not saved"; } return false; @@ -548,21 +568,26 @@ bool Shortcut::add(Locations loc) bool Shortcut::remove(Locations loc) { + debug() << "removing shortcut for '" << m_name << "' from " << toString(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"; + debug() << "path to shortcut file is '" << path << "'"; + if (!QFile::exists(path)) { + critical() << "can't remove '" << path << "', file not found"; return false; } - if (!QFile::remove(path)) { - qCritical().nospace().noquote() - << "system shortcut: failed to remove '" << path << "'"; + if (!MOBase::shellDelete({path})) { + const auto e = ::GetLastError(); + + critical() + << "failed to remove '" << path << "', " + << formatSystemMessageQ(e); return false; } @@ -603,13 +628,12 @@ QString Shortcut::shortcutDirectory(Locations loc) const case None: default: - qCritical() << "system shortcut: bad location " << loc; - return {}; + critical() << "bad location " << loc; + break; } } catch(std::exception&) { - return {}; } return QDir::toNativeSeparators(dir); @@ -618,13 +642,42 @@ QString Shortcut::shortcutDirectory(Locations loc) const QString Shortcut::shortcutFilename() const { if (m_name.isEmpty()) { - qCritical() << "system shortcut: name is empty"; + critical() << "name is empty"; return {}; } return m_name + ".lnk"; } +QDebug Shortcut::debug() const +{ + return qDebug().noquote().nospace() << "system shortcut: "; +} + +QDebug Shortcut::critical() const +{ + return qCritical().noquote().nospace() << "system shortcut: "; +} + + +QString toString(Shortcut::Locations loc) +{ + switch (loc) + { + case Shortcut::None: + return "none"; + + case Shortcut::Desktop: + return "desktop"; + + case Shortcut::StartMenu: + return "start menu"; + + default: + return QString("? (%1)").arg(static_cast(loc)); + } +} + class WMI diff --git a/src/shared/util.h b/src/shared/util.h index a40fa201..c4a2ed7d 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -133,6 +133,14 @@ private: int m_iconIndex; QString m_workingDirectory; + // returns a qCritical() logger with a prefix already logged + // + QDebug critical() const; + + // returns a qDebug() logger with a prefix already logged + // + QDebug debug() const; + // returns the path where the shortcut file should be saved // @@ -148,6 +156,11 @@ private: }; +// returns a string representation of the given location +// +QString toString(Shortcut::Locations loc); + + // represents one module // class Module -- cgit v1.3.1 From b5019f586e08d4ae83ebc33b622591f23c73a80e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 20:02:16 -0400 Subject: FileDialogMemory didn't actually use the cached directory --- src/filedialogmemory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index 554a6235..0e3e9793 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -66,7 +66,7 @@ QString FileDialogMemory::getOpenFileName( if (currentDir.isEmpty()) { auto itor = instance().m_Cache.find(dirID); if (itor != instance().m_Cache.end()) { - currentDir = itor->first; + currentDir = itor->second; } } @@ -90,7 +90,7 @@ QString FileDialogMemory::getExistingDirectory( if (currentDir.isEmpty()) { auto itor = instance().m_Cache.find(dirID); if (itor != instance().m_Cache.end()) { - currentDir = itor->first; + currentDir = itor->second; } } -- cgit v1.3.1 From 6378a80ad00aef4b8ae78c189fc032eac8d9e78e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 20:03:01 -0400 Subject: fixed duplicate mnemonic for status bar --- src/mainwindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 6a816262..70d1cf39 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1733,7 +1733,7 @@ p, li { white-space: pre-wrap; } true - St&atus bar + Status &bar -- cgit v1.3.1 From 9c00d7b7f648fe6a7a84a66714641bd18abf6989 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 20:05:27 -0400 Subject: fixed context menu being offset vertically in the overwrite dialog --- src/overwriteinfodialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 0a884ac9..5ee8d76c 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -290,5 +290,6 @@ void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint & m_FileSelection.clear(); m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); } - menu.exec(ui->filesView->mapToGlobal(pos)); + + menu.exec(ui->filesView->viewport()->mapToGlobal(pos)); } -- cgit v1.3.1 From f0c5f6f67298f4009bfb0b1804c6c620d42577e1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 6 Jul 2019 20:15:04 -0400 Subject: fixed "not logged in" in the statusbar not respecting theme --- src/statusbar.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/statusbar.cpp b/src/statusbar.cpp index 8ad5bea1..e9a6e658 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -23,7 +23,7 @@ StatusBar::StatusBar(QStatusBar* bar, Ui::MainWindow* ui) : m_bar->addPermanentWidget(m_notifications); m_bar->addPermanentWidget(m_update); m_bar->addPermanentWidget(m_api); - + m_progress->setTextVisible(true); m_progress->setRange(0, 100); @@ -72,8 +72,8 @@ void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) if (user.type() == APIUserAccountTypes::None) { text = "API: not logged in"; - textColor = "initial"; - backgroundColor = "transparent"; + textColor = ""; + backgroundColor = ""; } else { text = QString("API: Queued: %1 | Daily: %2 | Hourly: %3") .arg(stats.requestsQueued) @@ -94,20 +94,25 @@ void StatusBar::setAPI(const APIStats& stats, const APIUserAccount& user) m_api->setText(text); - m_api->setStyleSheet(QString(R"( + QString ss(R"( QLabel { padding-left: 0.1em; padding-right: 0.1em; padding-top: 0; - padding-bottom: 0; - color: %1; - background-color: %2; - } - )") - .arg(textColor) - .arg(backgroundColor)); + padding-bottom: 0;)"); + + if (!textColor.isEmpty()) { + ss += QString("\ncolor: %1;").arg(textColor); + } + + if (!backgroundColor.isEmpty()) { + ss += QString("\nbackground-color: %2;").arg(backgroundColor); + } + + ss += "\n}"; + m_api->setStyleSheet(ss); m_api->setAutoFillBackground(true); } -- cgit v1.3.1 From 37aa5e07f473e0377ef59d6e2f53f7373b11aecd Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 8 Jul 2019 13:19:52 -0500 Subject: Add some basic checks for symlinks --- src/main.cpp | 18 +++++++++++++++++- src/organizercore.cpp | 17 ++++++++++++++++- src/organizercore.h | 1 + src/selectiondialog.cpp | 8 ++++++++ src/selectiondialog.h | 1 + 5 files changed, 43 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 4a2f6035..f2571685 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -303,6 +303,11 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett gamePath = game->gameDirectory().absolutePath(); } QDir gameDir(gamePath); + QFileInfo directoryInfo(gameDir.path()); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } if (game->looksValid(gameDir)) { return selectGame(settings, gameDir, game); } @@ -335,15 +340,26 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett while (selection.exec() != QDialog::Rejected) { IPluginGame * game = selection.getChoiceData().value(); + QString gamePath = selection.getChoiceDescription(); + QFileInfo directoryInfo(gamePath); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } if (game != nullptr) { return selectGame(settings, game->gameDirectory(), game); } - QString gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) + gamePath = QFileDialog::getExistingDirectory(nullptr, gameConfigured ? QObject::tr("Please select the installation of %1 to manage").arg(gameName) : QObject::tr("Please select the game to manage"), QString(), QFileDialog::ShowDirsOnly); if (!gamePath.isEmpty()) { QDir gameDir(gamePath); + QFileInfo directoryInfo(gamePath); + if (directoryInfo.isSymLink()) { + reportError(QObject::tr("The configured path to the game directory (%1) appears to be a symbolic (or other) link. " + "This setup is incompatible with MO2's VFS and will not run correctly.").arg(gamePath)); + } QList possibleGames; for (IPluginGame * const game : plugins.plugins()) { //If a game is already configured, skip any plugins that are not for that game diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 99ddda1d..81ec7f43 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -691,12 +691,27 @@ bool OrganizerCore::createDirectory(const QString &path) { } } +bool OrganizerCore::checkPathSymlinks() { + bool hasSymlink = (QFileInfo(m_Settings.getProfileDirectory()).isSymLink() || + QFileInfo(m_Settings.getModDirectory()).isSymLink() || + QFileInfo(m_Settings.getOverwriteDirectory()).isSymLink()); + if (hasSymlink) { + QMessageBox::critical(nullptr, QObject::tr("Error"), + QObject::tr("One of the configured MO2 directories (profiles, mods, or overwrite) " + "is on a path containing a symbolic (or other) link. This is incompatible " + "with MO2's VFS system.")); + return false; + } + return true; +} + bool OrganizerCore::bootstrap() { return createDirectory(m_Settings.getProfileDirectory()) && createDirectory(m_Settings.getModDirectory()) && createDirectory(m_Settings.getDownloadDirectory()) && createDirectory(m_Settings.getOverwriteDirectory()) && - createDirectory(QString::fromStdWString(crashDumpsPath())) && cycleDiagnostics(); + createDirectory(QString::fromStdWString(crashDumpsPath())) && + checkPathSymlinks() && cycleDiagnostics(); } void OrganizerCore::createDefaultProfile() diff --git a/src/organizercore.h b/src/organizercore.h index 4dd11831..99b1c5f2 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -183,6 +183,7 @@ public: void loginFailedUpdate(const QString &message); static bool createAndMakeWritable(const QString &path); + bool checkPathSymlinks(); bool bootstrap(); void createDefaultProfile(); diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index 55728751..089760f9 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -79,6 +79,14 @@ QString SelectionDialog::getChoiceString() } } +QString SelectionDialog::getChoiceDescription() +{ + if (m_Choice == nullptr) + return QString(); + else + return m_Choice->accessibleDescription(); +} + void SelectionDialog::disableCancel() { ui->cancelButton->setEnabled(false); diff --git a/src/selectiondialog.h b/src/selectiondialog.h index 3c4b25df..5a70aa5e 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -52,6 +52,7 @@ public: QVariant getChoiceData(); QString getChoiceString(); + QString getChoiceDescription(); void disableCancel(); -- cgit v1.3.1 From 4bfb1a0d7666cbde7b10f59feaccde0af723a9e1 Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 8 Jul 2019 13:20:41 -0500 Subject: Fix missing spaces in tutorial message --- src/tutorials/tutorial_firststeps_settings.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/tutorials/tutorial_firststeps_settings.js b/src/tutorials/tutorial_firststeps_settings.js index 792712be..b0e0c3c3 100644 --- a/src/tutorials/tutorial_firststeps_settings.js +++ b/src/tutorials/tutorial_firststeps_settings.js @@ -16,10 +16,10 @@ function getTutorialSteps() function() { highlightItem("nexusBox", false) - tutorial.text = qsTr("Use this interface to obtain an API key from NexusMods." - +"This is used for all API connections - downloads, updates" - +"etc. MO2 uses the Windows Credential Manager to store" - +"this data securely. If the SSO page on Nexus is failing," + tutorial.text = qsTr("Use this interface to obtain an API key from NexusMods. " + +"This is used for all API connections - downloads, updates " + +"etc. MO2 uses the Windows Credential Manager to store " + +"this data securely. If the SSO page on Nexus is failing, " +"use the manual entry and copy the API key from your profile.") waitForClick() } -- cgit v1.3.1 From df6cb649bdb6795cac8d465527fa4d685d8ec245 Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 9 Jul 2019 11:33:13 -0500 Subject: 2.2.1 RC Translation Updates --- src/organizer_en.ts | 767 ++++++++++++++++++++++++++-------------------------- 1 file changed, 386 insertions(+), 381 deletions(-) (limited to 'src') diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 2a47fc18..4fedd25f 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1664,7 +1664,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1840,8 +1840,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2120,7 +2120,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2206,873 +2206,874 @@ p, li { white-space: pre-wrap; } - St&atus bar + Status &bar + St&atus bar - + Toolbar and Menu - + Desktop - + Start Menu - + There is no supported sort mechanism for this game. You will probably have to use a third-party tool. - + Crash on exit - + MO crashed while exiting. Some settings may not be saved. Error: %1 - + There are notifications to read - + There are no notifications - - - + + + Endorse - + Won't Endorse - + Help on UI - + Documentation - + Chat on Discord - + Report Issue - + Tutorials - + About - + About Qt - + Name - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + Browse Mod Page - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - + Notice: Your current MO version (%1) is lower than the previously used one (%2). The GUI may not downgrade gracefully, so you may experience oddities. However, there should be no serious issues. - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3080,12 +3081,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3093,22 +3094,12 @@ You can also use online editors and converters instead. - - failed to remove %1 - - - - - failed to create %1 - - - - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3116,348 +3107,348 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occurred - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -4522,227 +4513,227 @@ p, li { white-space: pre-wrap; } - - - - + + + + Installation cancelled - - + + Another installation is currently in progress. - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod not found: %1 - - + + The mod was not installed completely. - + file not found: %1 - + failed to generate preview for %1 - + Sorry - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + File '%1' not found. - + Failed to generate preview for %1 - + Executable not found: %1 - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Steam: Access Denied - + MO was denied access to the Steam process. This normally indicates that Steam is being run as administrator while MO is not. This can cause issues launching the game. It is recommended to not run Steam as administrator unless absolutely necessary. Restart MO as administrator? - + Error - + Windows Event Log Error - + The Windows Event Log service is disabled and/or not running. This prevents USVFS from running properly. Your mods may not be working in the executable that you are launching. Note that you may have to restart MO and/or your PC after the service is fixed. Continue launching %1? - + Blacklisted Executable - + The executable you are attempted to launch is blacklisted in the virtual file system. This will likely prevent the executable, and any executables that are launched by this one, from seeing any mods. This could extend to INI files, save games and any other virtualized files. Continue launching %1? - + No profile set - + Failed to refresh list of esps: %1 - + Multiple esps/esls activated, please check that they don't conflict. - + You need to be logged in with Nexus - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - - + + failed to update mod list: %1 - - + + login successful - + Login failed - + Login failed, try again? - + login failed: %1. Download will not be associated with an account - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + MO1 "Script Extender" load mechanism has left hook.dll in your game folder - - + + Description missing - + <a href="%1">hook.dll</a> has been found in your game folder (right click to copy the full path). This is most likely a leftover of setting the ModOrganizer 1 load mechanism to "Script Extender", in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or manually removing the file, otherwise the game is likely to crash and burn. - + failed to save load order: %1 - + The designated write target "%1" is not enabled. @@ -5425,6 +5416,7 @@ p, li { white-space: pre-wrap; } + Error @@ -5787,88 +5779,95 @@ If the folder was still in use, restart MO and try again. - + + + + The configured path to the game directory (%1) appears to be a symbolic (or other) link. This setup is incompatible with MO2's VFS and will not run correctly. + + + + Could not use configuration settings for game "%1", path "%2". - - - + + + Please select the installation of %1 to manage - - - + + + Please select the game to manage - + Canceled finding %1 in "%2". - + Canceled finding game in "%1". - + %1 not identified in "%2". The directory is required to contain the game binary. - + No game identified in "%1". The directory is required to contain the game binary.<br><br><b>These are the games supported by Mod Organizer:</b><ul>%2</ul> - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + failed to start shortcut: %1 - + failed to start application: %1 - + Mod Organizer - + An instance of Mod Organizer is already running - + Failed to set up instance - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -5919,12 +5918,17 @@ If the folder was still in use, restart MO and try again. - + + One of the configured MO2 directories (profiles, mods, or overwrite) is on a path containing a symbolic (or other) link. This is incompatible with MO2's VFS system. + + + + Select binary - + Binary @@ -7407,88 +7411,88 @@ There IS a notification now but you may want to hold off on clearing it until af - + Finally there are tooltips on almost every part of Mod Organizer. If there is a control in MO you don't understand, please try hovering over it to get a short description or use "Help on UI" from the help menu to get a longer explanation - + This list displays all mods installed through MO. It also displays installed DLCs and some mods installed outside MO but you have limited control over those in MO. - + Before we start installing mods, let's have a quick look at the settings. - + Now it's time to install a few mods!Please go along with this because we need a few mods installed to demonstrate other features - + There are a few ways to get mods into ModOrganizer. If you associated MO with NXM links in the settings you can now use your regular browser to send downloads from Nexus to MO. Click on "Nexus" to open nexus, find a mod and click the green download buttons on Nexus saying "Download with Manager". - + You can also install mods from disk using the "Install Mod" button. - + Downloads will appear on the "Downloads"-tab here. You have to download and install at least one mod to proceed. - + Great, you just installed your first mod. Please note that the installation procedure may differ based on how a mod was packaged. - + Now you know all about downloading and installing mods but they are not enabled yet... - + Install a few more mods if you want, then enable mods by checking them in the left pane. Mods that aren't enabled have no effect on the game whatsoever. - + For some mods, enabling it on the left pane is all you have to do... - + ...but most contain plugins. These are plugins for the game and are required to add stuff to the game (new weapons, armors, quests, areas, ...). Please open the "Plugins"-tab to get a list of plugins. - + You will notice some plugins are grayed out. These are part of the main game and can't be disabled. - + A single mod may contain zero, one or multiple esps. Some or all may be optional. If in doubt, please consult the documentation of the individual mod. To do so, right-click the mod and select "Information". - + Now you know how to download, install and enable mods. It's important you always start the game from inside MO, otherwise the mods you installed here won't work. - + This combobox lets you choose <i>what</i> to start. This way you can start the game, Launcher, Script Extender, the Creation Kit or other tools. If you miss a tool you can also configure this list but that is an advanced topic. - + This completes the basic tutorial. As homework go play a bit! After you have installed more mods you may want to read the tutorial on conflict resolution. @@ -7526,7 +7530,8 @@ Please open the "Nexus"-tab - Use this interface to obtain an API key from NexusMods.This is used for all API connections - downloads, updatesetc. MO2 uses the Windows Credential Manager to storethis data securely. If the SSO page on Nexus is failing,use the manual entry and copy the API key from your profile. + Use this interface to obtain an API key from NexusMods. This is used for all API connections - downloads, updates etc. MO2 uses the Windows Credential Manager to store this data securely. If the SSO page on Nexus is failing, use the manual entry and copy the API key from your profile. + Use this interface to obtain an API key from NexusMods.This is used for all API connections - downloads, updatesetc. MO2 uses the Windows Credential Manager to storethis data securely. If the SSO page on Nexus is failing,use the manual entry and copy the API key from your profile. -- cgit v1.3.1 From 63f835628be1b82b3d5cc940b8e7797f7ccdf1be Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 9 Jul 2019 11:34:32 -0500 Subject: 2.2.1 RC Version --- src/version.rc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/version.rc b/src/version.rc index 04259d7d..92370853 100644 --- a/src/version.rc +++ b/src/version.rc @@ -4,7 +4,7 @@ // Otherwise, if letters are used in VER_FILEVERSION_STR, uses the full MOBase::VersionInfo parser // Otherwise, uses the numbers from VER_FILEVERSION and sets the release type as pre-alpha #define VER_FILEVERSION 2,2,1 -#define VER_FILEVERSION_STR "2.2.1alpha1\0" +#define VER_FILEVERSION_STR "2.2.1rc1\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1 From f7a4ff9f5aa4e5a7b7ea3b6c0a4601947f8344d7 Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 10 Jul 2019 02:36:30 +0200 Subject: Avoid refreshing Data tab each time directoryStructure is refreshed if the tab is not active. --- src/mainwindow.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f9e4138e..451ec688 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2563,8 +2563,14 @@ void MainWindow::directory_refreshed() { // some problem-reports may rely on the virtual directory tree so they need to be updated // now - refreshDataTreeKeepExpandedNodes(); updateProblemsButton(); + + + //Some better check for the current tab is needed. + if (ui->tabWidget->currentIndex() == 2) { + refreshDataTreeKeepExpandedNodes(); + } + } void MainWindow::esplist_changed() -- cgit v1.3.1 From e73c309f08eff98f0dbd2590f594a83b67431eac Mon Sep 17 00:00:00 2001 From: Silarn Date: Tue, 9 Jul 2019 22:31:34 -0500 Subject: Update description for month instead of 30 days --- src/modinfo.cpp | 2 +- src/organizer_en.ts | 483 ++++++++++++++++++++++++++-------------------------- 2 files changed, 243 insertions(+), 242 deletions(-) (limited to 'src') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 73ead71c..3484b644 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -323,7 +323,7 @@ bool ModInfo::checkAllForUpdate(PluginContainer *pluginContainer, QObject *recei updatesAvailable = false; } else { qInfo() << tr( - "You have mods that haven't been checked within 30 days using the new API. These mods must be checked before we can use the bulk update API. " + "You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. " "This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods." ); } diff --git a/src/organizer_en.ts b/src/organizer_en.ts index 4fedd25f..1aa831a0 100644 --- a/src/organizer_en.ts +++ b/src/organizer_en.ts @@ -1664,7 +1664,7 @@ p, li { white-space: pre-wrap; } - + Create Backup @@ -1840,8 +1840,8 @@ p, li { white-space: pre-wrap; } - - + + Refresh @@ -2120,7 +2120,7 @@ p, li { white-space: pre-wrap; } - + Endorse Mod Organizer @@ -2254,8 +2254,8 @@ Error: %1 - - + + Endorse @@ -2400,680 +2400,680 @@ Error: %1 - + failed to spawn notepad.exe: %1 - + failed to change origin name: %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Contains %1> - + <Checked> - + <Unchecked> - + <Update> - + <Mod Backup> - + <Managed by MO> - + <Managed outside MO> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" - - - - + + + + Confirm - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - - + + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - + Failed to create backup. - + Endorsing multiple mods will take a while. Please wait... - + Unendorsing multiple mods will take a while. Please wait... - + Failed to display overwrite dialog: %1 - + Opening Nexus Links - + You are trying to open %1 links to Nexus Mods. Are you sure you want to do this? - + Nexus ID for this mod is unknown - + Opening Web Pages - + You are trying to open %1 Web Pages. Are you sure you want to do this? - + <table cellspacing="5"><tr><th>Type</th><th>All</th><th>Visible</th><tr><td>Enabled mods:&emsp;</td><td align=right>%1 / %2</td><td align=right>%3 / %4</td></tr><tr><td>Unmanaged/DLCs:&emsp;</td><td align=right>%5</td><td align=right>%6</td></tr><tr><td>Mod backups:&emsp;</td><td align=right>%7</td><td align=right>%8</td></tr><tr><td>Separators:&emsp;</td><td align=right>%9</td><td align=right>%10</td></tr></table> - + <table cellspacing="6"><tr><th>Type</th><th>Active </th><th>Total</th></tr><tr><td>All plugins:</td><td align=right>%1 </td><td align=right>%2</td></tr><tr><td>ESMs:</td><td align=right>%3 </td><td align=right>%4</td></tr><tr><td>ESPs:</td><td align=right>%7 </td><td align=right>%8</td></tr><tr><td>ESMs+ESPs:</td><td align=right>%9 </td><td align=right>%10</td></tr><tr><td>ESLs:</td><td align=right>%5 </td><td align=right>%6</td></tr></table> - - - + + + Create Mod... - + This will create an empty mod. Please enter a name: - - + + A mod with this name already exists - + Create Separator... - + This will create a new separator. Please enter a name: - + A separator with this name already exists - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Move successful. - - + + Are you sure? - + About to recursively delete: - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Export to csv - + CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet. You can also use online editors and converters instead. - + Select what mods you want export: - + All installed mods - + Only active (checked) mods from your current profile - + All currently visible mods in the mod list - + Choose what Columns to export: - + Mod_Priority - + Mod_Name - + Notes_column - + Mod_Status - + Primary_Category - + Nexus_ID - + Mod_Nexus_URL - + Mod_Version - + Install_Date - + Download_File_Name - + export failed: %1 - + Open Game folder - + Open MyGames folder - + Open INIs folder - + Open Instance folder - + Open Mods folder - + Open Profile folder - + Open Downloads folder - + Open MO2 Install folder - + Open MO2 Plugins folder - + Open MO2 Logs folder - + Install Mod... - + Create empty mod - + Create Separator - + Enable all visible - + Disable all visible - + Check for updates - + Export to csv... - - + + Send to - - + + Top - - + + Bottom - - + + Priority... - + Separator... - + All Mods - + Sync to Mods... - + Move content to Mod... - + Clear Overwrite... - - + + Open in Explorer - + Restore Backup - + Remove Backup... - - + + Change Categories - - + + Primary Category - + Rename Separator... - + Remove Separator... - + Select Color... - + Reset Color - + Change versioning scheme - + Force-check updates - + Un-ignore update - + Ignore update - - + + Enable selected - - + + Disable selected - + Rename Mod... - + Reinstall Mod - + Remove Mod... - + Un-Endorse - + Won't endorse - + Endorsement state unknown - + Start tracking - + Stop tracking - + Tracked state unknown - + Ignore missing data - + Mark as converted/working - + Visit on Nexus - + Visit on %1 - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - + %1 more - + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. @@ -3081,12 +3081,12 @@ You can also use online editors and converters instead. - + Enable Mods... - + Delete %n save(s) @@ -3094,12 +3094,12 @@ You can also use online editors and converters instead. - + Restarting MO - + Changing the managed game directory requires restarting MO. Any pending downloads will be paused. @@ -3107,348 +3107,348 @@ Click OK to restart MO now. - + Can't change download directory while downloads are in progress! - + failed to write to file %1 - + %1 written - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - - + + Set Priority - + Set the priority of the selected plugins - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Abstain from Endorsing Mod Organizer - + Are you sure you want to abstain from endorsing Mod Organizer 2? You will have to visit the mod page on the %1 Nexus site to change your mind. - - + + Thank you for endorsing MO2! :) - - + + Please reconsider endorsing MO2 on Nexus! - + Thank you! - + Thank you for your endorsement! - + Okay. - + This mod will not be endorsed and will no longer ask you to endorse. - + Mod ID %1 no longer seems to be available on Nexus. - + Request to Nexus failed: %1 - - + + failed to read %1: %2 - - + + Error - + failed to extract %1 (errorcode %2) - + Extract BSA - + This archive contains invalid hashes. Some files may be broken. - + Extract... - + This will restart MO, continue? - + Edit Categories... - + Deselect filter - + Remove '%1' from the toolbar - + Enable all - + Disable all - + Unlock load order - + Lock load order - + Open Origin in Explorer - + Open Origin Info... - + depends on missing "%1" - + incompatible with "%1" - + Please wait while LOOT is running - + loot failed. Exit code was: %1 - + failed to start loot - + failed to run loot: %1 - + Errors occurred - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of mod list created - + A file with the same name has already been downloaded. What would you like to do? - + Overwrite - + Rename new file - + Ignore file - + Set the priority of the selected mods @@ -3551,7 +3551,8 @@ You will have to visit the mod page on the %1 Nexus site to change your mind. - You have mods that haven't been checked within 30 days using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. + You have mods that haven't been checked within the last month using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. + You have mods that haven't been checked within 30 days using the new API. These mods must be checked before we can use the bulk update API. This will consume significantly more API requests than usual. You will need to rerun the update check once complete in order to parse the remaining mods. @@ -5862,7 +5863,7 @@ If the folder was still in use, restart MO and try again. - + <Manage...> -- cgit v1.3.1